一、Hello Compose!
在职务详情界面中,我们需要将对植物的说明迁移到Compose,同时让界面的总体结构保持完好。这时,您需要遵循"规划迁移"部分中提到的"搭配使用Compose和View"迁移策略。
Compose需要有宿主activity或fragment才能呈现界面。在Sunflower中,所有界面都使用fragment,因此您需要使用ComposeView
:这一AndroidView可以使用其setContent方法托管Compose界面内容。
1.1、移除XML代码
我们先从迁移开始!打开fragment_plant_detail.xml
并执行以下操作:
- 切换到代码视图
- 移除
NestedScrollView
中的ConstraintLayout
代码和嵌套的TextView
- 添加一个ComposeView,它会改为托管Compose代码,并以
compose_view
作为视图ID
fragment_plant_detail.xml
ini
<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">
// Step 2) Comment out ConstraintLayout and its children
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
<TextView
android:id="@+id/plant_detail_name"
...
</androidx.constraintlayout.widget.ConstraintLayout>
// End Step 2) Comment out until here
// Step 3) Add a ComposeView to host Compose code
<androidx.compose.ui.plantform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.core.widget.NestedScrollView>
1.2、添加Compose代码
现在,您可以开始将植物详情界面迁移到Compose了!
整个操作中,您都需要将Compose代码添加到plantdetail
文件夹下的PlantDetailDescription.kt
文件中。打开该文件,看看项目中是否有占位符"Hello Compose!"
文本。
plantdetail/PlantDetailDescription.kt
kotlin
@Composable
fun PlantDetailDescription() {
Text("Hello Compose")
}
我们从在上一步中添加的ComposeView
中调用此可组合项,即可在界面上显示此内容。打开plantdetail/PlantDetailFragment.kt
。
界面使用的是数据绑定,因此您可以直接访问composeView
并调用setContent
,以便在界面上显示Compose代码。您需要在MaterialTheme
内调用PlantDetailDescription
可组合项,因为Sunflower使用的是Material Design。
plantdetail/PlantDetailFragment.kt
kotlin
class PlantDetailFragment: Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(inflater, R.layout.fragmebt_plant_detail, container, false).apply {
...
composeView.setContent {
// You're in Compose world!
MaterialTheme {
PlantDetailDecription()
}
}
}
...
}
}
如果您运行该应用,界面上会显示"Hello Compose!"
二、使用XML创建可组合项
我们首先迁移植物的名称。更确切地说,就是您在fragment_plant_detail.xml
中移除的ID为@+id/plant_detail_name
的TextView
。XML代码如下:
ini
<TextView
android:id="@+id/plant_detail_name"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@{viewModel.plant.name}"
androiid:textAppearance="?attr/textAppearanceHeadline5"
... />
请查看它是否为textAppearanceHeadline5
样式,水平外边距为8.dp
,以及是否在界面上水平居中。不过,要显示的标题是由代码库层的PlantDetailViewModel
公开的LiveData
中观察到的。
如何观察LiveData
将在稍后介绍,因此先假设我们有可用的名称,并以参数形式将其传递到我们在PlantDetailDescription.kt
文件中创建的新PlantName
可组合项。稍后,将从PlantDetailDescription
可组合项调用此可组合项。
PlantDetailDescription.kt
kotlin
@Composable
private fun PlantName(name: String) {
Text(
text = name,
style = MaterialTheme.typography.h5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Preview
@Composable
private fun PlantNamePreview() {
MaterialTheme {
PlantName("Apple")
}
}
其中:
-
Text
的样式为MaterialTheme.typography.h5
,它从XML代码映射到textAppearanceHeadline5
-
修饰符会修饰Text,以将其调整为类似于XML版本:
-
fillMaxWidth
修饰符对应于XML代码中的android:layout_width="match_parent"
-
margin_small
的水平padding
,其值是使用。dimensionResource
辅助函数从View系统获取的。 -
wrapContentWidth
水平对其Text
。注意:Compose提供了从
dimens.xml
和strings.xml
文件获取值的简单方法,即dimensionResource(id)
和stringResource(id)
。由此一来,您可以将View系统视为可信来源。
三、ViewModel和LiveData
现在,我们将标题链接到界面。如需执行此操作,您需要使用PlantDetailViewModel
加载数据。为此,Compose集成了ViewModel 和 LiveData。
3.1、ViewModel
由于fragment中使用了PlantDetailViewModel
的实例,因此我们可以将其作为参数传递给PlantDetailDescription
,就这么简单。
注意:如果您遇到了ViewModel无法使用的情况,或者您不许忘将该依赖项传递给可组合项,则可以在可组合项中使用viewmodel函数,以获取ViewModel的实例。
可组合项没有自己的ViewModel实例,相应的实例将在可组合项和托管Compose代码的生命周期所有者(activity或fragment)之间共享。
打开PlantDetailDescription.kt
文件,然后将PlantDetailViewModel
参数添加到PlantDetailDescription:
PlantDetailDescription.kt
kotlin
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
...
}
现在,请在从fragment调用此可组合项时传递ViewModel实例:
PlantDetailFragment.kt
kotlin
class PlantDetailFragment: Fragment() {
...
override fun onCreateView(...): View? {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
3.2、LiveData
有了LiveData,您已有权访问PlantDetailViewModel
的LiveData<Plant>
字段,以获取植物的名称。
如需从可组合项观察LiveData,请使用LiveData.observeAsState()
函数。
注意: LiveData.observeAsState()
开始观察LiveData,并通过State
对象表示它的值。每次向LiveData发布一个新值时,返回的State
都会更新,这会导致所有State.value
用法重组。
由于LiveData发出的值可以为null,因此您需要将其用法封装在null检查中。有鉴于此,以及为了实现可重用性,最好将LiveData的使用和监听拆分到不同的可组合项中。因此,请创建一个名为PlantDetailContent
的新可组合项,用于显示Plant
信息。
基于以上原因,添加LiveData观察后,PlantDetailDescription.kt
文件将如下所示。
PlantDetailDescription.kt
kotlin
@Composable
fun PlantDetailDescription(plantDetaiViewModel: PlantDetailViewModel) {
// Observes values coming from the VM's LiveData<Plant> field
val plant by PlantDetailViewModel.plant.observeAsState()
// If plant is not null, display the content
plant?.let {
PlantDetailContent(it)
}
}
@Composable
fun PlantDetailContent(plant: Plant) {
PlantName(plant.name)
}
@Preview
@Composable
private fun PlantDetailCOntentPreview() {
val plant = Plant("id", "Apple", "description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
预览与PlantNamePreview
相同,因为PlantDetailContent
目前只调用PlantName
:
现在,您已完成在Compose中显示植物名称所需的所有ViewModel链接。在接下来的几部分中,您将构建其余可组合项,并以类似的方式将它们里链接到ViewModel。