View 遷移至 Compose
主要學(xué)習(xí)內(nèi)容
- 如何逐步將應(yīng)用遷移到 Compose
- 如何將 Compose 添加到使用 Android View 構(gòu)建的現(xiàn)有界面
- 如何在 Compose 中使用 Android View
- 如何在 Compose 中使用 View 系統(tǒng)中的主題
原理
我們使用 Compose 都是在ComponentActivity
中調(diào)用setContent
方法,那么與Activity的setContextView
有什么區(qū)別呢?
查看setContent
源碼:
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
//獲取decorView下的第一個(gè)子VIew
//顯然第一次啟動(dòng)時(shí) getChildAt(0) 返回null
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
if (existingComposeView != null) with(existingComposeView) {
setParentCompositionContext(parent)
setContent(content)
} else {
//會(huì)進(jìn)入該分支
//創(chuàng)建ComposeView實(shí)例
ComposeView(this).apply {
// 在 setContentView 之前設(shè)置內(nèi)容和父項(xiàng)
// 讓 ComposeView 在 attach 時(shí)創(chuàng)建 composition
setParentCompositionContext(parent)
setContent(content)
// 在設(shè)置內(nèi)容視圖之前設(shè)置視圖樹Owner
// 以便 inflation process 和 attach listeners 能感知到它們存在
setOwners()
setContentView(this, DefaultActivityContentLayoutParams)
}
}
}
可以看到在setContent
方法中會(huì)去獲取ComposeView
對(duì)象,最后會(huì)去調(diào)用setContentView
,而這個(gè)setContentView
就是Activity的setContentView
方法
androidx.activity.ComponentActivity
@Override
public void addContentView(@SuppressLint({"UnknownNullness", "MissingNullability"}) View view,
@SuppressLint({"UnknownNullness", "MissingNullability"})
ViewGroup.LayoutParams params) {
initViewTreeOwners();
super.addContentView(view, params);
}
那么ComposeView
又是什么呢?
class ComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr){ ... }
abstract class AbstractComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr){ ... }
可以看到ComposeView
其實(shí)本質(zhì)上就是個(gè)ViewGroup
,也就是說 Compose 中設(shè)置的界面最終會(huì)在ComposeView
上進(jìn)行展示,是 View和 Compose 的交界點(diǎn)
并不是說 Compose 可組合項(xiàng)最終都會(huì)變?yōu)閂iew,而是可組合項(xiàng)形成的界面會(huì)在
ComposeView
上進(jìn)行繪制顯示
在 View 中要使用 Compose ,只需要通過創(chuàng)建ComposeView
,在ComposeView
中通過setContent
調(diào)用可組合項(xiàng)就可以了
而在 Compose 中調(diào)用 View就會(huì)稍微麻煩一點(diǎn),會(huì)在之后進(jìn)行介紹
遷移規(guī)劃
要將 Jetpack Compose 集成到現(xiàn)有 Android 應(yīng)用中,有多種不同的方法。常用的兩種遷移策略為:
-
完全使用 Compose 開發(fā)一個(gè)新界面
在重構(gòu)應(yīng)用代碼以適應(yīng)新技術(shù)時(shí),一種常用的方法是只在應(yīng)用構(gòu)建的新功能中采用新技術(shù),比較適合在創(chuàng)建新的界面使用該方法,原本的應(yīng)用部分繼續(xù)使用 View 體系
-
選取一個(gè)現(xiàn)有界面,然后逐步遷移其中的各個(gè)組件
-
View作為外部布局
將部分界面遷移到 Compose,讓其他部分保留在 View 系統(tǒng)中,例如:遷移
RecyclerView
,同時(shí)將界面的其余部分保留在 View 系統(tǒng)中 -
Compose 作為外部布局
使用 Compose 作為外部布局,并使用 Compose 中可能沒有的一些現(xiàn)有 View,比如
MapView
或AdView
-
準(zhǔn)備工作
因?yàn)橹蟮拇a都是基于其中的項(xiàng)目進(jìn)行的,而且遷移的學(xué)習(xí)是基于一個(gè)較完善的項(xiàng)目中進(jìn)行,存在多個(gè)界面之間的切換
所以建議下載示例,并通過
Import Project
方式導(dǎo)入其中的MigrationCodelab
項(xiàng)目
在解壓文件中的MigrationCodelab
目錄中存放本次學(xué)習(xí)的案例代碼
在此學(xué)習(xí)中,我們將逐步把 Sunflower
的植物詳情界面遷移到 Compose,并 Compose 和 View 結(jié)合起來
添加 Compose 依賴
android {
...
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
...
compose true
}
composeOptions {
kotlinCompilerExtensionVersion rootProject.composeVersion
}
}
dependencies {
...
// Compose
implementation "androidx.compose.runtime:runtime:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation-layout:$rootProject.composeVersion"
implementation "androidx.compose.material:material:$rootProject.composeVersion"
implementation "androidx.compose.runtime:runtime-livedata:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion"
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
初步遷移 Compose
在植物詳情界面中,我們需要將對(duì)植物的說明遷移到 Compose,同時(shí)讓界面的總體結(jié)構(gòu)保持完好
Compose 需要有 activity 或 fragment 才能呈現(xiàn)界面。在 Sunflower 中,所有界面都使用 fragment,因此我們需要使用 ComposeView
ComposeView
可以使用setContent
方法托管 Compose 界面內(nèi)容
這里我們選取植物詳情界面,然后逐步遷移其中的各個(gè)組件
移除XML代碼
打開 fragment_plant_detail.xml
并執(zhí)行以下操作:
- 切換到
code
視圖 - 移除
NestedScrollView
中的ConstraintLayout
代碼和嵌套的TextView
(建議使用代碼注釋的方式,方便進(jìn)行比較和遷移) - 添加一個(gè)
ComposeView
,它會(huì)改為托管 Compose 代碼,并以compose_view
作為 id
fragment_plant_detail.xml
<androidx.core.widget.NestedScrollView
android:id="@+id/plant_detail_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_bottom_padding"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- 其余部分全部注釋掉 -->
<!-- 這里就不展示注釋部分 -->
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
添加Compose代碼
在該項(xiàng)目中,我們可以將 Compose 代碼添加到 plantdetail
文件夾下的 PlantDetailDescription.kt
文件中
plantdetail/PlantDetailDescription.kt
//文件中原本存在的可組合項(xiàng)函數(shù)
@Composable
fun PlantDetailDescription() {
Surface {
Text("Hello Compose")
}
}
我們要在ComposeView
中調(diào)用PlantDetailDescription
可組合項(xiàng)
在plantdetail/PlantDetailFragment.kt
中,訪問 composeView
并調(diào)用 setContent
,以便在界面上顯示 Compose 代碼
Sunflower 項(xiàng)目中使用
DataBinding
的方式,我們可以直接訪問composeView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription()
}
}
}
setHasOptionsMenu(true)
return binding.root
}
運(yùn)行該應(yīng)用,界面上會(huì)顯示“Hello Compose!
”
從XML映射到可組合項(xiàng)
我們首先遷移植物的名稱。更確切地說,是在 fragment_plant_detail.xml
中 ID 為 @+id/plant_detail_name
的 TextView
<TextView
android:id="@+id/plant_detail_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@{viewModel.plant.name}"
android:textAppearance="?attr/textAppearanceHeadline5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Apple" />
我們根據(jù)XML中的樣式創(chuàng)建的新 PlantName
可組合項(xiàng)與之對(duì)應(yīng)
@Composable
private fun PlantName(name: String) {
Text(
text = name,
style = MaterialTheme.typography.h5,
modifier = Modifier
.fillMaxWidth()
.padding(
start = dimensionResource(id = R.dimen.margin_small),
end = dimensionResource(id = R.dimen.margin_small)
)
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
映射關(guān)系:
-
Text
的樣式為MaterialTheme.typography.h5
,從 XML 代碼映射到textAppearanceHeadline5
。 - 修飾符會(huì)修飾 Text,以將其調(diào)整為類似于 XML 版本:
-
fillMaxWidth
修飾符對(duì)應(yīng)于 XML 代碼中的android:layout_width="match_parent"
-
margin_small
的水平padding
,其值是使用dimensionResource
輔助函數(shù)從 View 系統(tǒng)獲取的 -
wrapContentWidth
水平對(duì)齊Text
-
如何觀察
LiveData
將在稍后介紹,因此先假設(shè)我們有可用的名稱
通過預(yù)覽查看效果
@Preview(showBackground = true, backgroundColor = 0XFFFFFF)
@Composable
private fun PlantNamePreview() {
MaterialTheme {
PlantName("Apple")
}
}
ViewModel和LiveData
現(xiàn)在,我們將PlantName
顯示到界面。如需執(zhí)行此操作,我們需要使用 PlantDetailViewModel
加載數(shù)據(jù)
ViewModel
由于 fragment 中使用了 PlantDetailViewModel
的實(shí)例,因此我們可以將其作為參數(shù)傳遞給 PlantDetailDescription
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
...
}
在 fragment 調(diào)用此可組合項(xiàng)時(shí)傳遞 ViewModel
實(shí)例:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
...
}
可組合項(xiàng)沒有自己的
ViewModel
實(shí)例,相應(yīng)的實(shí)例將在可組合項(xiàng)和托管 Compose 代碼的生命周期所有者(activity 或 fragment)之間共享
如果您遇到了 ViewModel 無法使用的情況,或者您不希望將該依賴項(xiàng)傳遞給可組合項(xiàng),則可以在可組合項(xiàng)中使用 viewModel
函數(shù),以獲取 ViewModel 的實(shí)例
如需使用
viewModel()
函數(shù),請(qǐng)將androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1
依賴項(xiàng)添加到build.gradle
文件中
class ExampleViewModel : ViewModel() { /*...*/ }
@Composable
fun MyExample(
viewModel: ExampleViewModel = viewModel()
) {
...
}
viewModel()
會(huì)返回一個(gè)現(xiàn)有的 ViewModel
,或在給定作用域內(nèi)創(chuàng)建一個(gè)新的 ViewModel。只要該作用域處于有效狀態(tài),就會(huì)保留 ViewModel
例如,如果在某個(gè) Activity 中使用了可組合項(xiàng),則在該 Activity 完成或進(jìn)程終止之前,viewModel()
會(huì)返回同一實(shí)例
如果 ViewModel 具有依賴項(xiàng),則 viewModel()
會(huì)將可選的 ViewModelProvider.Factory
作為參數(shù)
如需詳細(xì)了解 Compose 中的
ViewModel
以及實(shí)例如何與 Compose Navigation 庫或 activity 和 fragment 一起使用,請(qǐng)參閱互操作性文檔
LiveData
PlantDetailDescription
可以通過PlantDetailViewModel
的 LiveData<Plant>
字段,以獲取植物的名稱
如需從可組合項(xiàng)觀察 LiveData,請(qǐng)使用 LiveData.observeAsState()
函數(shù)
LiveData.observeAsState()
開始觀察LiveData
,并通過State
對(duì)象表示它的值。每次向LiveData
發(fā)布一個(gè)新值時(shí),返回的State
都會(huì)更新,這會(huì)導(dǎo)致所有State.value
用法重組
由于 LiveData 發(fā)出的值可以為 null,因此我們需要將其用法封裝在 null 檢查中。有鑒于此,以及為了實(shí)現(xiàn)可重用性,最好將 LiveData 的使用和監(jiān)聽拆分到不同的可組合項(xiàng)中
因此,我們創(chuàng)建 PlantDetailDescription
的新可組合項(xiàng),用于顯示 Plant
信息
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
val plant by plantDetailViewModel.plant.observeAsState()
plant?.let {
PlantDetailContent(it)
}
}
@Composable
fun PlantDetailContent(plant: Plant) {
PlantName(plant.name)
}
遷移更多XML代碼
現(xiàn)在我們繼續(xù)遷移ConstraintLayout
中的 View:澆水信息和植物說明
fragment_plant_detail.xml
中澆水信息 XML 代碼由兩個(gè) ID 為 plant_watering_header
和 plant_watering
的 TextView 組成
<TextView
android:id="@+id/plant_watering_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@string/watering_needs_prefix"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/plant_detail_name" />
<TextView
android:id="@+id/plant_watering"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/plant_watering_header"
app:wateringText="@{viewModel.plant.wateringInterval}"
tools:text="every 7 days" />
和之前的操作類似,創(chuàng)建 PlantWatering
新可組合項(xiàng),以在界面上顯示澆水信息:
@Composable
fun PlantWatering(wateringInterval: Int) {
Column(Modifier.fillMaxWidth()) {
val centerWithPaddingModifier = Modifier
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.align(Alignment.CenterHorizontally)
val normalPadding = dimensionResource(R.dimen.margin_normal)
Text(
text = stringResource(R.string.watering_needs_prefix),
color = MaterialTheme.colors.primaryVariant,
fontWeight = FontWeight.Bold,
modifier = centerWithPaddingModifier.padding(top = normalPadding)
)
val wateringIntervalText = LocalContext.current.resources.getQuantityString(
R.plurals.watering_needs_suffix, wateringInterval, wateringInterval
)
Text(
text = wateringIntervalText,
modifier = centerWithPaddingModifier.padding(bottom = normalPadding)
)
}
}
由于
Text
可組合項(xiàng)會(huì)共享水平內(nèi)邊距和對(duì)齊修飾,因此您可以將修飾符分配給局部變量(即centerWithPaddingModifier
),以重復(fù)使用修飾符。修飾符是標(biāo)準(zhǔn)的 Kotlin 對(duì)象,因此可以重復(fù)使用Compose 的
MaterialTheme
與plant_watering_header
中使用的colorAccent
不完全匹配。現(xiàn)在,我們可以使用將在主題設(shè)置部分中加以改進(jìn)的MaterialTheme.colors.primaryVariant
我們將各個(gè)部分組合在一起,然后同樣在 PlantDetailContent
中調(diào)用 PlantWatering
,因?yàn)?code>ConstraintLayout還有 margin
值,我們還需要將值添加到 Compose 代碼中
為了確保背景顏色和所用的文本顏色均合適,我們需要添加 Surface
用于處理這種設(shè)置
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
}
}
}
@Preview(showBackground = true, backgroundColor = 0XFFFFFF)
@Composable
private fun PlantNamePreview() {
val plant = Plant("id", "Apple", "description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
刷新預(yù)覽
Compose 中調(diào)用View
接下來,我們來遷移植物說明
<TextView
android:id="@+id/plant_description"
style="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:minHeight="@dimen/plant_description_min_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/plant_watering"
app:renderHtml="@{viewModel.plant.description}"
tools:text="Details about the plant" />
TextView中包含了app:renderHtml="@{viewModel.plant.description}"
,renderHtml
是一個(gè)綁定適配器,可在 PlantDetailBindingAdapters.kt
文件中找到。該實(shí)現(xiàn)使用 HtmlCompat.fromHtml
在 TextView
上設(shè)置文本
@BindingAdapter("wateringText")
fun bindWateringText(textView: TextView, wateringInterval: Int) {
val resources = textView.context.resources
val quantityString = resources.getQuantityString(
R.plurals.watering_needs_suffix,
wateringInterval, wateringInterval
)
textView.text = quantityString
}
但是,Compose 目前不支持 Spanned
類,也不支持顯示 HTML 格式的文本。因此,我們需要在 Compose 代碼中使用 View 系統(tǒng)中的 TextView
來繞過此限制
由于 Compose 目前還無法呈現(xiàn) HTML 代碼,因此您需要使用 AndroidView
API 程序化地創(chuàng)建一個(gè) TextView
,從而實(shí)現(xiàn)此目的
AndroidView
接受程序化地創(chuàng)建的 View。如果您想嵌入 XML 文件,可以結(jié)合使用視圖綁定與androidx.compose.ui:ui-viewbinding
庫中的AndroidViewBinding
API
創(chuàng)建 PlantDescription
可組合項(xiàng)。此可組合項(xiàng)中使用 AndroidView
創(chuàng)建TextView
。在 factory
回調(diào)中,請(qǐng)初始化使用給定 Context
來回應(yīng) HTML 交互的 TextView
。在 update
回調(diào)中,用已保存的 HTML 格式的說明設(shè)置文本
@Composable
private fun PlantDescription(description: String) {
// Remembers the HTML formatted description. Re-executes on a new description
val htmlDescription = remember(description) {
//使用 HtmlCompat 解析 html
//HtmlCompat 內(nèi)部做了版本適配
HtmlCompat.fromHtml(description, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
// 在屏幕上顯示 TextView 并在 inflate 時(shí)使用 HTML 描述進(jìn)行更新
// 對(duì) htmlDescription 的更新將使 AndroidView 重新組合并更新文本
AndroidView(
factory = { context ->
TextView(context).apply {
//對(duì)于鏈接點(diǎn)擊的處理,若不設(shè)置movementMethod,則鏈接無效
movementMethod = LinkMovementMethod.getInstance()
}
},
update = {
it.text = htmlDescription
}
)
}
remember
中將description
作為 key ,如果 description
參數(shù)發(fā)生變化,系統(tǒng)會(huì)再次執(zhí)行 remember
中的 htmlDescription
代碼
同樣,如果 htmlDescription
發(fā)生變化,AndroidView
更新回調(diào)會(huì)重組。在回調(diào)中讀取的任何狀態(tài)都會(huì)導(dǎo)致重組
我們將 PlantDescription
添加到 PlantDetailContent
可組合項(xiàng)
@Composable
fun PlantDetailContent(plant: Plant) {
Column(Modifier.padding(dimensionResource(id = R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
PlantDescription(plant.description)
}
}
現(xiàn)在,我們就將原始 ConstraintLayout
中的所有內(nèi)容遷移到 Compose了
ViewCompositionStrategy
默認(rèn)情況下,只要 ComposeView
與窗口分離,Compose 就會(huì)處理組合。Compose 界面 View
類型(例如 ComposeView
和 AbstractComposeView
)使用定義此行為的 ViewCompositionStrategy
默認(rèn)情況下,Compose 使用 DisposeOnDetachedFromWindow
策略。但是,在 Compose 界面 View 類型用于以下各項(xiàng)的部分情況下,默認(rèn)策略可能不太合適:
- Fragment。Compose 界面
View
類型應(yīng)該遵循 fragment 的視圖生命周期去保存狀態(tài) - Transitions 動(dòng)畫。當(dāng) Transitions 過程中使用 Compose 界面
View
類型,系統(tǒng)會(huì)在轉(zhuǎn)換開始(而不是轉(zhuǎn)換結(jié)束)時(shí)將其與窗口分離,從而導(dǎo)致可組合項(xiàng)在它仍然在屏幕上時(shí)就開始 detach -
RecyclerView
或者帶有生命周期管理的自定義控件
在上述某些情況下,除非您手動(dòng)調(diào)用 AbstractComposeView.disposeComposition
,否則應(yīng)用可能會(huì)因?yàn)榻M合實(shí)例泄漏內(nèi)存
如需在不再需要組合時(shí)自動(dòng)處理組合,請(qǐng)通過調(diào)用 setViewCompositionStrategy
方法設(shè)置其他策略或創(chuàng)建自己的策略
例如,DisposeOnLifecycleDestroyed
策略會(huì)在 lifecycle
被銷毀時(shí)處理組合
此策略適用于與已知的 LifecycleOwner
具有一對(duì)一關(guān)系的 Compose 界面 View
類型
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(this@PlantDetailFragment.lifecycle))
setContent {
PlantDetailDescription(plantDetailViewModel)
}
}
}
...
}
當(dāng) LifecycleOwner
未知時(shí),可以使用 DisposeOnViewTreeLifecycleDestroyed
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
PlantDetailDescription(plantDetailViewModel)
}
}
}
...
}
如需了解如何使用此 API,請(qǐng)參閱“Fragment 中的 ComposeView”部分
互操作性主題設(shè)置
我們已將植物詳情的文本內(nèi)容遷移到 Compose。不過,Compose 使用的主題顏色有誤。當(dāng)植物名稱應(yīng)該使用綠色時(shí),它使用的是紫色
顯然 View 體系使用的主題和MaterialTheme
并沒有進(jìn)行關(guān)聯(lián),我們需要 Compose 繼承 View 系統(tǒng)中可用的主題,而不是從頭開始在 Compose 中重新編寫您自己的 Material 主題
<style name="Base.Theme.Sunflower" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/sunflower_green_500</item>
<item name="colorPrimaryVariant">@color/sunflower_green_700</item>
<item name="colorOnPrimary">@color/sunflower_black</item>
<item name="colorPrimaryDark">@color/sunflower_green_700</item>
<item name="colorAccent">?attr/colorPrimaryVariant</item>
<item name="colorSecondary">@color/sunflower_yellow_500</item>
<item name="colorOnSecondary">@color/sunflower_black</item>
<item name="android:colorBackground">@color/sunflower_green_500</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:navigationBarColor">?colorOnSurface</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
如需在 Compose 中重復(fù)使用 View 系統(tǒng)的 Material Design 組件 (MDC) 主題,您可以使用 compose-theme-adapter
。MdcTheme
函數(shù)將自動(dòng)讀取主機(jī)上下文的 MDC 主題,并代表您將它們傳遞給 MaterialTheme
,以用于淺色和深色主題
dependencies {
...
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
如需使用此庫,請(qǐng)將 MaterialTheme
的用法替換為 MdcTheme
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
...
setContent {
MdcTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
...
}