Çalışma zamanı kaynak yer paylaşımlarını (RRO'lar) kullanmak yerine, Car UI kitaplığındaki bileşen özelleştirmelerinin eksiksiz uygulamalarını oluşturmak için Car UI kitaplığı eklentilerini kullanın. RRO'lar, yalnızca Araba kullanıcı arayüzü kitaplığı bileşenlerinin XML kaynaklarını değiştirmenize olanak tanır. Bu da özelleştirebileceğiniz kapsamı sınırlar.
Eklenti oluşturma
Araba kullanıcı arayüzü kitaplığı eklentisi, bir dizi eklenti API'si uygulayan sınıfları içeren bir APK'dır. Eklenti API'leri, statik kitaplık olarak bir eklentiye derlenebilir.
Soong ve Gradle'daki örnekleri inceleyin:
Soong
Aşağıdaki Soong örneğini inceleyin:
android_app {
name: "my-plugin",
min_sdk_version: "28",
target_sdk_version: "30",
aaptflags: ["--shared-lib"],
sdk_version: "current",
manifest: "src/main/AndroidManifest.xml",
srcs: ["src/main/java/**/*.java"],
resource_dirs: ["src/main/res"],
static_libs: [
"car-ui-lib-oem-apis",
],
// Disable optimization is mandatory to prevent R.java class from being
// stripped out
optimize: {
enabled: false,
},
certificate: ":my-plugin-certificate",
}
Gradle
Şu build.gradle
dosyasına bakın:
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 28
targetSdkVersion 30
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
signingConfigs {
debug {
storeFile file('chassis_upload_key.jks')
storePassword 'chassis'
keyAlias 'chassis'
keyPassword 'chassis'
}
}
}
dependencies {
implementation project(':oem-apis')
// Or use the following if you'd like to use the maven artifact
// implementation 'com.android.car.ui:car-ui-lib-plugin-apis:1.0.0'
}
Settings.gradle
:
// You can remove the ':oem-apis' if you're using the maven artifact.
include ':oem-apis'
project(':oem-apis').projectDir = new File('./path/to/oem-apis')
include ':my-plugin'
project(':my-plugin').projectDir = new File('./my-plugin')
Eklentinin manifestinde, aşağıdaki özelliklere sahip bir içerik sağlayıcı bildirilmelidir:
android:authorities="com.android.car.ui.plugin"
android:enabled="true"
android:exported="true"
android:authorities="com.android.car.ui.plugin"
, eklentinin Car UI kitaplığı tarafından bulunmasını sağlar. Sağlayıcının, çalışma zamanında sorgulanabilmesi için dışa aktarılması gerekir. Ayrıca, enabled
özelliği false
olarak ayarlanırsa eklenti uygulaması yerine varsayılan uygulama kullanılır. İçerik sağlayıcı sınıfının mevcut olması gerekmez. Bu durumda, sağlayıcı tanımına tools:ignore="MissingClass"
eklediğinizden emin olun. Aşağıdaki örnek manifest girişine bakın:
<application>
<provider
android:name="com.android.car.ui.plugin.PluginNameProvider"
android:authorities="com.android.car.ui.plugin"
android:enabled="false"
android:exported="true"
tools:ignore="MissingClass"/>
</application>
Son olarak, güvenlik önlemi olarak uygulamanızı imzalayın.
Paylaşılan kitaplık olarak eklentiler
Doğrudan uygulamalara derlenen Android statik kitaplıklarının aksine, Android paylaşılan kitaplıkları, çalışma zamanında diğer uygulamalar tarafından referans verilen bağımsız bir APK'ya derlenir.
Android paylaşılan kitaplığı olarak uygulanan eklentilerin sınıfları, uygulamalar arasındaki paylaşılan sınıf yükleyiciye otomatik olarak eklenir. Araba kullanıcı arayüzü kitaplığını kullanan bir uygulama, eklenti paylaşılan kitaplığında çalışma zamanı bağımlılığı belirttiğinde sınıf yükleyicisi, eklenti paylaşılan kitaplığının sınıflarına erişebilir. Normal Android uygulamaları (paylaşılan kitaplık değil) olarak uygulanan eklentiler, uygulamanın soğuk başlatma sürelerini olumsuz etkileyebilir.
Paylaşılan kitaplıkları uygulama ve oluşturma
Android paylaşılan kitaplıklarıyla geliştirme, birkaç temel fark dışında normal Android uygulamalarıyla geliştirmeye çok benzer.
- Eklentinizin uygulama manifestinde eklenti paket adıyla birlikte
application
etiketi altındalibrary
etiketini kullanın:
<application>
<library android:name="com.chassis.car.ui.plugin" />
...
</application>
- Paylaşılan bir kitaplık oluşturmak için kullanılan AAPT işareti
shared-lib
ile Soongandroid_app
derleme kuralınızı (Android.bp
) yapılandırın:
android_app {
...
aaptflags: ["--shared-lib"],
...
}
Paylaşılan kitaplıklara bağımlılıklar
Sistemdeki Araba kullanıcı arayüzü kitaplığını kullanan her uygulama için, eklenti paket adıyla birlikte application
etiketi altındaki uygulama manifestine uses-library
etiketini ekleyin:
<manifest>
<application
android:name=".MyApp"
...>
<uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
...
</application>
</manifest>
Eklenti yükleme
Modül PRODUCT_PACKAGES
'e dahil edilerek eklentiler sistem bölümüne önceden yüklenmelidir. Önceden yüklenmiş paket, diğer yüklü uygulamalar gibi güncellenebilir.
Sistemdeki mevcut bir eklentiyi güncelliyorsanız bu eklentiyi kullanan tüm uygulamalar otomatik olarak kapanır. Kullanıcı tarafından yeniden açıldığında güncellenmiş değişiklikler gösterilir. Uygulama çalışmıyorsa bir sonraki başlatma işleminde güncellenmiş eklentiye sahip olur.
Android Studio ile eklenti yüklerken dikkate almanız gereken bazı ek noktalar vardır. Bu makalenin yazıldığı sırada, Android Studio uygulama yükleme işleminde bir hata vardı. Bu hata, eklentilerdeki güncellemelerin geçerli olmamasına neden oluyordu. Bu sorun, eklentinin derleme yapılandırmasında Always install with package manager (disables deploy optimizations on Android 11 and later) (Her zaman paket yöneticisiyle yükle (Android 11 ve sonraki sürümlerde dağıtım optimizasyonlarını devre dışı bırakır)) seçeneği belirlenerek düzeltilebilir.
Ayrıca, eklenti yüklenirken Android Studio, başlatılacak bir ana etkinlik bulamadığına dair bir hata bildiriyor. Eklentide herhangi bir etkinlik (bir amacı çözmek için kullanılan boş amaç hariç) olmadığından bu durum beklenir. Hatayı gidermek için derleme yapılandırmasında Başlat seçeneğini Hiçbir şey olarak değiştirin.
1. Şekil. Eklenti Android Studio yapılandırması
Proxy eklentisi
Car UI kitaplığını kullanan uygulamaların özelleştirilmesi, uygulamalar arasında özelleştirmeler aynı olsa bile, değiştirilecek her bir uygulamayı hedefleyen bir RRO gerektirir. Bu, uygulama başına RRO gerektiği anlamına gelir. Araba kullanıcı arayüzü kitaplığını kullanan uygulamaları görün.
Araba kullanıcı arayüzü kitaplığı proxy eklentisi, bileşen uygulamalarını Araba kullanıcı arayüzü kitaplığının statik sürümüne devreden paylaşılan bir eklenti kitaplığı örneğidir. Bu eklenti, işlevsel bir eklenti uygulamaya gerek kalmadan Car UI kitaplığını kullanan uygulamalar için tek bir özelleştirme noktası olarak kullanılabilen bir RRO ile hedeflenebilir. RRO'lar hakkında daha fazla bilgi için Bir uygulamanın kaynaklarının değerini çalışma zamanında değiştirme başlıklı makaleyi inceleyin.
Proxy eklentisi, eklenti kullanarak özelleştirme yapmaya yönelik yalnızca bir örnek ve başlangıç noktasıdır. RRO'ların ötesinde özelleştirme için eklenti bileşenlerinin bir alt kümesi uygulanabilir ve geri kalan için proxy eklentisi kullanılabilir veya tüm eklenti bileşenleri tamamen sıfırdan uygulanabilir.
Proxy eklentisi, uygulamalar için tek bir RRO özelleştirme noktası sağlasa da eklentiyi kullanmayı devre dışı bırakan uygulamalar, doğrudan uygulamayı hedefleyen bir RRO gerektirir.
Eklenti API'lerini uygulama
Eklentinin ana giriş noktası com.android.car.ui.plugin.PluginVersionProviderImpl
sınıfıdır. Tüm eklentiler, bu ad ve paket adıyla tam olarak eşleşen bir sınıf içermelidir. Bu sınıfın varsayılan bir oluşturucusu olmalı ve PluginVersionProviderOEMV1
arayüzünü uygulamalıdır.
CarUi eklentileri, eklentiden eski veya yeni uygulamalarla çalışmalıdır. Bunu kolaylaştırmak için tüm eklenti API'leri, sınıf adlarının sonunda V#
ile sürüm oluşturulur. Araba kullanıcı arayüzü kitaplığının yeni özellikler içeren yeni bir sürümü yayınlanırsa bu özellikler bileşenin V2
sürümünde yer alır. Araba kullanıcı arayüzü kitaplığı, yeni özelliklerin eski bir eklenti bileşeni kapsamında çalışması için elinden geleni yapar.
Örneğin, araç çubuğundaki yeni bir düğme türünü MenuItems
simgesine dönüştürerek.
Ancak, Car UI kitaplığının eski bir sürümünü kullanan uygulamalar, daha yeni API'lere göre yazılmış yeni bir eklentiye uyum sağlayamaz. Bu sorunu çözmek için eklentilerin, uygulamalar tarafından desteklenen OEM API sürümüne göre kendilerinin farklı uygulamalarını döndürmesine izin veriyoruz.
PluginVersionProviderOEMV1
içinde bir yöntem vardır:
Object getPluginFactory(int maxVersion, Context context, String packageName);
Bu yöntem, eklenti tarafından desteklenen PluginFactoryOEMV#
sürümünün en yükseğini uygulayan ancak yine de maxVersion
değerinden küçük veya bu değere eşit olan bir nesne döndürür. Bir eklentide bu kadar eski bir PluginFactory
uygulaması yoksa null
döndürebilir. Bu durumda, CarUi bileşenlerinin statik olarak bağlı uygulaması kullanılır.
Statik Car Ui kitaplığının eski sürümlerine göre derlenen uygulamalarla geriye dönük uyumluluğu korumak için PluginVersionProvider
sınıfının eklentinizin uygulamasında maxVersion
, 2, 5 ve daha yüksek değerlerin desteklenmesi önerilir. 1, 3 ve 4. sürümler desteklenmez. Daha fazla bilgi için PluginVersionProviderImpl
konusuna bakın.
PluginFactory
, diğer tüm CarUi bileşenlerini oluşturan arayüzdür. Ayrıca, arayüzlerinin hangi sürümünün kullanılacağını da tanımlar. Eklenti bu bileşenlerden herhangi birini uygulamaya çalışmıyorsa oluşturma işlevinde null
döndürebilir (ayrı bir customizesBaseLayout()
işlevi olan araç çubuğu hariç).
pluginFactory
, CarUi bileşenlerinin hangi sürümlerinin birlikte kullanılabileceğini sınırlar. Örneğin, bileşenlerin çok çeşitli sürümlerinin birlikte çalışacağına dair çok az garanti olacağından, Toolbar
'in 100. sürümünü ve RecyclerView
'ın 1. sürümünü oluşturabilecek bir pluginFactory
asla olmayacaktır. Geliştiricilerin araç çubuğu sürüm 100'ü kullanabilmesi için pluginFactory
sürümünün, araç çubuğu sürüm 100'ü oluşturan bir uygulamasını sağlaması gerekir. Bu uygulama, oluşturulabilecek diğer bileşenlerin sürümlerindeki seçenekleri sınırlar. Diğer bileşenlerin sürümleri eşit olmayabilir. Örneğin, pluginFactoryOEMV100
, ToolbarControllerOEMV100
ve RecyclerViewOEMV70
oluşturabilir.
Araç Çubuğu
Temel düzen
Araç çubuğu ve "temel düzen" çok yakından ilişkilidir. Bu nedenle, araç çubuğunu oluşturan işlev installBaseLayoutAround
olarak adlandırılır. Temel düzen, araç çubuğunun uygulamanın içeriğinin etrafında herhangi bir yere yerleştirilmesine olanak tanıyan bir kavramdır. Bu sayede, uygulamanın üst/alt kısmında, kenarlarında dikey olarak veya hatta tüm uygulamayı çevreleyen dairesel bir araç çubuğu oluşturulabilir. Bu, araç çubuğu/temel düzenin sarmalanması için installBaseLayoutAround
öğesine bir görünüm iletilerek gerçekleştirilir.
Eklenti, sağlanan görünümü almalı, üst öğesinden ayırmalı, eklentinin kendi düzenini üst öğenin aynı dizininde ve yeni ayrılan görünümle aynı LayoutParams
ile genişletmeli, ardından görünümü yeni genişletilen düzenin bir yerine yeniden eklemelidir. Şişirilmiş düzende, uygulama tarafından istenirse araç çubuğu bulunur.
Uygulama, araç çubuğu olmayan bir temel düzen isteyebilir. Bu durumda,
installBaseLayoutAround
null değerini döndürmelidir. Çoğu eklenti için bu yeterlidir ancak eklenti yazarı, örneğin uygulamanın kenarına bir süsleme uygulamak isterse bu işlem temel düzenle yapılabilir. Bu süslemeler, özellikle dikdörtgen olmayan ekranlara sahip cihazlarda kullanışlıdır. Uygulamayı dikdörtgen bir alana itebilir ve dikdörtgen olmayan alana temiz geçişler ekleyebilirler.
installBaseLayoutAround
da Consumer<InsetsOEMV1>
olarak iletilir. Bu tüketici, eklentinin uygulamanın içeriğini kısmen kapladığını (araç çubuğuyla veya başka bir şekilde) uygulamaya bildirmek için kullanılabilir. Uygulama, bu alanda çizim yapmaya devam etmesi gerektiğini ancak kullanıcı etkileşimine açık önemli bileşenleri bu alanın dışında tutması gerektiğini anlar. Bu efekt, araç çubuğunu yarı şeffaf hale getirmek ve listelerin araç çubuğunun altında kaydırılmasını sağlamak için referans tasarımımızda kullanılır. Bu özellik uygulanmasaydı listedeki ilk öğe araç çubuğunun altında kalır ve tıklanamazdı. Bu efekt gerekmiyorsa eklenti, Consumer'ı yoksayabilir.
Şekil 2. Araç çubuğunun altında içerik kaydırma
Uygulama açısından, eklenti yeni yerleşimler gönderdiğinde bunları InsetsChangedListener
uygulayan etkinliklerden veya parçalardan alır. Bir etkinlik veya parça InsetsChangedListener
öğesini uygulamıyorsa Car Ui kitaplığı, parçayı içeren Activity
veya FragmentActivity
öğesine dolgu olarak ekler uygulayarak ekleri varsayılan olarak işler. Kitaplık, varsayılan olarak parçalara ekler uygulamamaktadır. Uygulamada, RecyclerView
üzerinde dolgu olarak iç kısımları uygulayan bir uygulama snippet'i örneğini aşağıda bulabilirsiniz:
public class MainActivity extends Activity implements InsetsChangedListener {
@Override
public void onCarUiInsetsChanged(Insets insets) {
CarUiRecyclerView rv = requireViewById(R.id.recyclerview);
rv.setPadding(insets.getLeft(), insets.getTop(),
insets.getRight(), insets.getBottom());
}
}
Son olarak, eklentiye, sarmalanması gereken görünümün uygulamanın tamamını mı yoksa yalnızca küçük bir bölümünü mü kapladığını belirtmek için kullanılan bir fullscreen
ipucu verilir.
Bu, yalnızca ekranın kenarında göründüklerinde anlamlı olan bazı süslemelerin kenar boyunca uygulanmasını önlemek için kullanılabilir. Tam ekran olmayan temel düzenleri kullanan bir örnek uygulama olarak Ayarlar'ı verebiliriz. Bu uygulamada, iki panelli düzenin her bölmesinin kendi araç çubuğu vardır.
installBaseLayoutAround
, toolbarEnabled
false
olduğunda null değerini döndürmesi beklendiğinden, eklentinin temel düzeni özelleştirmek istemediğini belirtmesi için customizesBaseLayout
öğesinden false
değerini döndürmesi gerekir.
Temel düzende, döner kontrollerin tam olarak desteklenmesi için bir FocusParkingView
ve bir FocusArea
bulunmalıdır. Döner kontrolü desteklemeyen cihazlarda bu görünümler atlanabilir. FocusParkingView/FocusAreas
, statik CarUi kitaplığında uygulanır. Bu nedenle, bağlamlardan görünümler oluşturmak için fabrikalar sağlamak üzere setRotaryFactories
kullanılır.
Odaklanma görünümlerini oluşturmak için kullanılan bağlamlar, eklentinin bağlamı değil kaynak bağlam olmalıdır. Kullanıcıya görünür bir odaklanma olmaması gerektiğinde odaklanılan öğe olduğundan, FocusParkingView
, ağaçtaki ilk görünüme mümkün olduğunca yakın olmalıdır. FocusArea
, döner dokunma bölgesi olduğunu belirtmek için araç çubuğunu temel düzende sarmalamalıdır. FocusArea
sağlanmazsa kullanıcı, döner denetleyiciyle araç çubuğundaki düğmelere gidemez.
Araç çubuğu denetleyicisi
Döndürülen gerçek ToolbarController
, temel düzenden çok daha kolay uygulanmalıdır. Görevi, ayarlayıcılarına iletilen bilgileri alıp temel düzende göstermektir. Çoğu yöntemle ilgili bilgi için Javadoc'a bakın. Daha karmaşık yöntemlerden bazıları aşağıda ele alınmıştır.
getImeSearchInterface
, IME (klavye) penceresinde arama sonuçlarını göstermek için kullanılır. Örneğin, klavye ekranın yalnızca yarısını kaplıyorsa bu, arama sonuçlarını klavyeyle birlikte göstermek/canlandırmak için yararlı olabilir. İşlevlerin çoğu statik CarUi kitaplığında uygulanır. Eklentideki arama arayüzü, statik kitaplığın TextView
ve onPrivateIMECommand
geri çağırmalarını alması için yalnızca yöntemler sağlar. Bunu desteklemek için eklenti, TextView
sınıfını geçersiz kılan ve çağrıyı, sağlanan dinleyiciye arama çubuğunun TextView
olarak ileten bir TextView
alt sınıfı kullanmalıdır.onPrivateIMECommand
setMenuItems
yalnızca MenuItems'ı ekranda gösterir ancak şaşırtıcı derecede sık çağrılır. MenuItems için eklenti API'si değişmez olduğundan bir MenuItem her değiştirildiğinde tamamen yeni bir setMenuItems
çağrısı yapılır. Bu durum, kullanıcının bir anahtar MenuItem'i tıklaması ve bu tıklamanın anahtarın açılıp kapanmasına neden olması gibi basit bir olayda meydana gelebilir. Bu nedenle, hem performans hem de animasyon açısından eski ve yeni MenuItems listesi arasındaki farkın hesaplanması ve yalnızca gerçekten değişen görünümlerin güncellenmesi önerilir. MenuItems, bu konuda yardımcı olabilecek bir key
alanı sağlar. Anahtar, aynı MenuItem için setMenuItems
'ye yapılan farklı çağrılarda aynı olmalıdır.
AppStyledView
AppStyledView
, hiç özelleştirilmemiş bir görünümün kapsayıcısıdır. Uygulamanın geri kalanından ayrışmasını sağlayan ve kullanıcıya bunun farklı bir arayüz olduğunu belirten bir kenarlık sağlamak için kullanılabilir. AppStyledView tarafından sarmalanan görünüm, setContent
içinde verilir. Uygulamanın isteği doğrultusunda AppStyledView
öğesinde geri veya kapat düğmesi de olabilir.
AppStyledView
, installBaseLayoutAround
gibi görünümlerini görünüm hiyerarşisine hemen eklemez. Bunun yerine, görünümünü getView
aracılığıyla statik kitaplığa döndürür. Ekleme işlemi daha sonra yapılır. AppStyledView
konum ve boyutu, getDialogWindowLayoutParam
uygulanarak da kontrol edilebilir.
Bağlamlar
Eklenti, hem eklenti hem de "kaynak" bağlamları olduğundan bağlamları kullanırken dikkatli olmalıdır. Eklenti bağlamı, getPluginFactory
için bağımsız değişken olarak verilir ve eklentinin kaynaklarını içeren tek bağlamdır. Bu, eklentide düzenleri genişletmek için kullanılabilecek tek bağlam olduğu anlamına gelir.
Ancak eklenti bağlamında doğru yapılandırma ayarlanmamış olabilir. Doğru yapılandırmayı elde etmek için bileşen oluşturan yöntemlerde kaynak bağlamları sağlıyoruz. Kaynak bağlam genellikle bir etkinliktir ancak bazı durumlarda bir hizmet veya başka bir Android bileşeni de olabilir. Kaynak bağlamındaki yapılandırmayı eklenti bağlamındaki kaynaklarla kullanmak için createConfigurationContext
kullanılarak yeni bir bağlam oluşturulmalıdır. Doğru yapılandırma kullanılmazsa Android katı modu ihlali olur ve şişirilmiş görünümlerin boyutları doğru olmayabilir.
Context layoutInflationContext = pluginContext.createConfigurationContext(
sourceContext.getResources().getConfiguration());
Mod değişiklikleri
Bazı eklentiler, bileşenleri için birden fazla modu destekleyebilir. Örneğin, görsel olarak farklı görünen bir spor modu veya eko modu. CarUi'de bu tür işlevler için yerleşik destek yoktur ancak eklentinin bu işlevi tamamen dahili olarak uygulamasına engel olan bir durum yoktur. Eklenti, modlar arasında ne zaman geçiş yapacağını belirlemek için yayınları dinlemek gibi istediği koşulları izleyebilir. Eklenti, modları değiştirmek için yapılandırma değişikliğini tetikleyemez. Ancak her bileşenin görünümünü manuel olarak güncellemek kullanıcı için daha sorunsuz olduğundan ve yapılandırma değişiklikleriyle mümkün olmayan geçişlere izin verdiğinden yapılandırma değişikliklerine güvenmeniz önerilmez.
Jetpack Compose
Eklentiler Jetpack Compose kullanılarak uygulanabilir ancak bu, alfa düzeyinde bir özelliktir ve kararlı olarak kabul edilmemelidir.
Eklentiler, oluşturma özellikli bir yüzey oluşturmak için ComposeView
kullanabilir. Bu ComposeView
, bileşenlerdeki getView
yönteminde uygulamaya döndürülen değerdir.
ComposeView
kullanmayla ilgili önemli bir sorun, hiyerarşideki farklı ComposeView'lar arasında paylaşılan global değişkenleri depolamak için düzenin kök görünümünde etiketler ayarlamasıdır. Eklentinin kaynak kimlikleri, uygulamanın kaynak kimliklerinden ayrı olarak adlandırılmadığından hem uygulama hem de eklenti aynı görünümde etiket ayarladığında çakışmalar oluşabilir. Bu genel değişkenleri ComposeViewWithLifecycle
konumuna taşıyan özel bir ComposeViewWithLifecycle
aşağıda verilmiştir.ComposeView
Bu sürümün de kararlı sürüm olarak kabul edilmemesi gerekir.
ComposeViewWithLifecycle
:
class ComposeViewWithLifecycle @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
private val lifeCycle = LifecycleRegistry(this)
private val modelStore = ViewModelStore()
private val savedStateRegistryController = SavedStateRegistryController.create(this)
private var composeView: ComposeView? = null
private var content = @Composable {}
init {
ViewTreeLifecycleOwner.set(this, this)
ViewTreeViewModelStoreOwner.set(this, this)
ViewTreeSavedStateRegistryOwner.set(this, this)
compositionContext = createCompositionContext()
}
fun setContent(content: @Composable () -> Unit) {
this.content = content
composeView?.setContent(content)
}
override fun getLifecycle(): Lifecycle {
return lifeCycle
}
override fun getViewModelStore(): ViewModelStore {
return modelStore
}
override fun getSavedStateRegistry(): SavedStateRegistry {
return savedStateRegistryController.savedStateRegistry
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
savedStateRegistryController.performRestore(Bundle())
lifeCycle.currentState = Lifecycle.State.RESUMED
composeView = ComposeView(context)
composeView?.setContent(content)
addView(composeView, LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
lifeCycle.currentState = Lifecycle.State.DESTROYED
modelStore.clear()
removeAllViews()
composeView = null
}
// Exact copy of View.createCompositionContext() in androidx's WindowRecomposer.android.kt
private fun createCompositionContext(): CompositionContext {
val currentThreadContext = AndroidUiDispatcher.CurrentThread
val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
PausableMonotonicFrameClock(it).apply { pause() }
}
val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
val recomposer = Recomposer(contextWithClock)
val runRecomposeScope = CoroutineScope(contextWithClock)
val viewTreeLifecycleOwner = checkNotNull(ViewTreeLifecycleOwner.get(this)) {
"ViewTreeLifecycleOwner not found from $this"
}
viewTreeLifecycleOwner.lifecycle.addObserver(
LifecycleEventObserver { _, event ->
@Suppress("NON_EXHAUSTIVE_WHEN")
when (event) {
Lifecycle.Event.ON_CREATE ->
// Undispatched launch since we've configured this scope
// to be on the UI thread
runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
Lifecycle.Event.ON_START -> pausableClock?.resume()
Lifecycle.Event.ON_STOP -> pausableClock?.pause()
Lifecycle.Event.ON_DESTROY -> {
recomposer.cancel()
}
}
}
)
return recomposer
}
// TODO: ComposeViewWithLifecycle should handle saving state and other lifecycle things
// override fun onSaveInstanceState(): Parcelable? {
// val superState = super.onSaveInstanceState()
// val bundle = Bundle()
// savedStateRegistryController.performSave(bundle)
// }
}