Jeptpack Compose 官網(wǎng)教程學(xué)習(xí)筆記(七)View 遷移至 Compose

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,比如 MapViewAdView

準(zhǔn)備工作

官網(wǎng)示例下載

因?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í)行以下操作:

  1. 切換到code視圖
  2. 移除 NestedScrollView 中的 ConstraintLayout 代碼和嵌套的 TextView(建議使用代碼注釋的方式,方便進(jìn)行比較和遷移)
  3. 添加一個(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!

應(yīng)用界面

從XML映射到可組合項(xiàng)

我們首先遷移植物的名稱。更確切地說,是在 fragment_plant_detail.xml 中 ID 為 @+id/plant_detail_nameTextView

<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")
    }
}
預(yù)覽圖

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可以通過PlantDetailViewModelLiveData<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_headerplant_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 的 MaterialThemeplant_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ù)覽

預(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.fromHtmlTextView 上設(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 類型(例如 ComposeViewAbstractComposeView)使用定義此行為的 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-adapterMdcTheme 函數(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)
                }
            }
        }
    }
    ...
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容