将 Compose 添加到基于 View 的应用

1. 准备工作

从一开始,Jetpack Compose 就采用了具备 View 互操作性的设计,这意味着 Compose 和 View 系统可以共享资源并协同工作,从而显示界面。借助此功能,您可以将 Compose 添加到基于 View 的现有应用中。这意味着,Compose 和 View 可以在代码库中共存,直到整个应用完全使用 Compose。

在此 Codelab 中,您需要将 Juice Tracker 应用中基于 View 的列表项更改为 Compose。如果您愿意,可以自行转换 Juice Tracker 的其他视图。

如果您的应用界面是基于 View 系统,您可能不想一次全部重写整个界面。此 Codelab 将帮助您将基于 View 的界面中的单个 View 转换为 Compose 元素。

前提条件

  • 熟悉基于 View 的界面。
  • 具备有关如何使用基于 View 的界面构建应用的知识。
  • 有使用 Kotlin 语法(包括 lambda)的经验。
  • 具备有关如何在 Jetpack Compose 中构建应用的知识。

学习内容

  • 如何将 Compose 添加到使用 Android View 构建的现有界面。
  • 如何预览添加到基于 View 的应用的可组合项。

构建内容

  • 您要将 Juice Tracker 应用中基于 View 的列表项转换为 Compose。

2. 起始应用概览

此 Codelab 将使用 View 构建 Android 应用中的 Juice Tracker 应用解决方案代码作为起始代码。起始应用已在使用 Room 持久性库保存数据。用户可以将果汁信息添加到应用数据库中,例如果汁名称、说明、颜色和评分。

显示果汁项及相关详情和评分的手机屏幕

在此 Codelab 中,您要将基于 View 的列表项转换为 Compose。

包含果汁详细信息的列表项

下载此 Codelab 的起始代码

首先,请下载起始代码:

或者,您也可以克隆该代码的 GitHub 代码库:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

您可以在 JuiceTracker GitHub 代码库中浏览该代码。

3. 添加 Jetpack Compose 库

Recollect、Compose 和 View 可以同时存在于给定界面上;您可以使用 Compose 构建一些界面元素,并使用 View 系统构建其他界面元素。例如,您可以仅使用 Compose 构建列表,而使用 View 系统构建界面的其余部分。

如需将 Compose 库添加到 Juice Tracker 应用,请完成以下步骤。

  1. 在 Android Studio 中,打开 Juice Tracker。
  2. 打开应用级 build.gradle.kts
  3. buildFeatures 代码块内,添加 compose = true 标记。
buildFeatures {
    //...
    // Enable Jetpack Compose for this module
    compose = true
}

此标记使 Android Studio 能够运行 Compose。您未在之前的 Codelab 中完成过此步骤,这是因为在您创建新的 Android Studio Compose 模板项目时,Android Studio 会自动生成此代码。

  1. buildFeatures 下,添加 composeOptions 代码块。
  2. 在代码块内,将 kotlinCompilerExtensionVersion 设置为 "1.5.1" 以设置 Kotlin 编译器版本。
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.1"
}
  1. dependencies 部分中,添加 Compose 依赖项。您需要以下依赖项才能将 Compose 添加到基于 View 的应用。这些依赖项有助于将 Compose 与 Activity 集成,添加 Compose 设计组件库,支持 Compose Jetpack 主题,并且提供相关工具实现更好的 IDE 支持。
dependencies {
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    // other dependencies
    // Compose
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation("androidx.compose.material3:material3")
    implementation("com.google.accompanist:accompanist-themeadapter-material3:0.28.0")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

添加 ComposeView

ComposeView 是一种可以托管 Jetpack Compose 界面内容的 Android View。使用 setContent 为 View 提供内容可组合函数。

  1. 打开 layout/list_item.xml,然后在 Split 标签页中查看预览。

在此 Codelab 结束时,您会将此 View 替换为可组合项。

f85c6002df3265e0.png

  1. JuiceListAdapter.kt 中,如需解决此错误,请从所有位置移除 ListItemBinding。在 JuiceListViewHolder 类中,将 binding.root 替换为 composeView
import androidx.compose.ui.platform.ComposeView

class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
): RecyclerView.ViewHolder(composeView)

如需解决此错误,您需要从所有位置移除 ListItemBinding

  1. onCreateViewHolder() 文件夹中,将 return() 函数更新为与以下代码相符:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JuiceListViewHolder {
   return JuiceListViewHolder(
       ComposeView(parent.context),
       onEdit,
       onDelete
   )
}
  1. JuiceListViewHolder 类中,删除所有 private 变量,并移除 bind() 函数中的所有代码。现在,您的 JuiceListViewHolder 类将类似于以下代码:
class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
) : RecyclerView.ViewHolder(composeView) {

   fun bind(juice: Juice) {

   }
}
  1. 此时,您可以删除 com.example.juicetracker.databinding.ListItemBindingandroid.view.LayoutInflater 导入项。
// Delete
import com.example.juicetracker.databinding.ListItemBinding
import android.view.LayoutInflater
  1. 删除 layout/list_item.xml 文件。
  2. Delete 对话框中,选择 OK

86dd7cba7e181e54.png

4. 添加可组合函数

接下来,您将创建一个用于发出列表项的可组合项。该可组合项接受 Juice 和两个回调函数,用于修改和删除列表项。

  1. JuiceListAdapter.kt 中的 JuiceListAdapter 类定义后面,创建一个名为 ListItem() 的可组合函数。
  2. 使 ListItem() 函数接受 Juice 对象和用于删除的 lambda 回调。
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun ListItem(
    input: Juice,
    onDelete: (Juice) -> Unit,
    modifier: Modifier = Modifier
) {
}

查看您要创建的列表项的预览。请注意,它包含果汁图标、果汁详细信息和“删除”按钮图标。您很快就会实现这些组件。

cf3b235dcb93e998.png

创建果汁图标可组合项

  1. JuiceListAdapter.kt 中的 ListItem() 可组合项后面,再创建一个名为 JuiceIcon() 的可组合函数,该函数接受 colorModifier
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {

}
  1. JuiceIcon() 函数内,为 color 和内容说明添加变量,如以下代码所示:
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {
   val colorLabelMap = JuiceColor.values().associateBy { stringResource(it.label) }
   val selectedColor = colorLabelMap[color]?.let { Color(it.color) }
   val juiceIconContentDescription = stringResource(R.string.juice_color, color)

}

使用 colorLabelMapselectedColor 变量,您可以检索与用户选择相关联的颜色资源。

  1. 添加 Box 布局,使两个图标 ic_juice_coloric_juice_clear 互相堆叠显示。ic_juice_color 图标具有色调,与中心对齐。
import androidx.compose.foundation.layout.Box

Box(
   modifier.semantics {
       contentDescription = juiceIconContentDescription
   }
) {
   Icon(
       painter = painterResource(R.drawable.ic_juice_color),
       contentDescription = null,
       tint = selectedColor ?: Color.Red,
       modifier = Modifier.align(Alignment.Center)
   )
   Icon(painter = painterResource(R.drawable.ic_juice_clear), contentDescription = null)
}

由于您熟悉可组合项的实现,因此我们未提供有关其实现方式的详细信息。

  1. 添加一个函数以预览 JuiceIcon()。将颜色作为 Yellow 传递。
import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
fun PreviewJuiceIcon() {
    JuiceIcon("Yellow")
}

颜色为黄色的果汁图标预览

创建果汁详情可组合项

JuiceListAdapter.kt 中,您需要再添加一个可组合函数来显示果汁详情。此外,您还需要一个列布局,用于显示名称和说明对应的两个 Text 可组合项,以及一个评分指示器。为此,请完成以下步骤:

  1. 添加一个名为 JuiceDetails() 的可组合函数,该函数接受 Juice 对象和 Modifier,以及用于果汁名称的 Text 可组合项和用于果汁说明的可组合项,如以下代码所示:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight

@Composable
fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) {
   Column(modifier, verticalArrangement = Arrangement.Top) {
       Text(
           text = juice.name,
           style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
       )
       Text(juice.description)
       RatingDisplay(rating = juice.rating, modifier = Modifier.padding(top = 8.dp))
   }
}
  1. 如需解决未解决的引用错误,请创建一个名为 RatingDisplay() 的可组合函数。

4018a1be2b3e7399.png

在 View 系统中,您可以使用 RatingBar 来显示以下评分栏。Compose 没有评分栏可组合项,因此您需要从头开始实现此元素。

  1. 定义 RatingDisplay() 函数,用于根据评分显示星形。此可组合函数会根据评分显示星数。

四星评分栏

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource

@Composable
fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) {
   val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating)
   Row(
       // Content description is added here to support accessibility
       modifier.semantics {
           contentDescription = displayDescription
       }
   ) {
       repeat(rating) {
           // Star [contentDescription] is null as the image is for illustrative purpose
           Image(
               modifier = Modifier.size(32.dp),
               painter = painterResource(R.drawable.star),
               contentDescription = null
           )
       }
   }
}

如需在 Compose 中创建星形可绘制对象,您需要创建星形矢量资源。

  1. Project 窗格中,右键点击“drawable”,然后依次选择 New > Vector Asset

e3b2bd6a495bc9.png

  1. Asset Studio 对话框中,搜索星形图标。选择已填充的星形图标。

“Select Icon”对话框,其中的星形图标处于选中状态

  1. 将星形的颜色值更改为 625B71

包含“Configure Vector Asset”和颜色的“Asset Studio”对话框

  1. 依次点击 Next > Finish
  2. 请注意,可绘制对象会显示在 res/drawable 文件夹中。

Android Studio 中的项目窗格,指向 res 下的 drawable 文件夹

  1. 添加一个预览可组合项,用于预览 JuiceDetails 可组合项。
@Preview
@Composable
fun PreviewJuiceDetails() {
    JuiceDetails(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4))
}

其中包含果汁名称、果汁说明和星级栏

创建“删除”按钮可组合项

  1. JuiceListAdapter.kt 中,再添加一个名为 DeleteButton() 的可组合函数,该函数接受 lambda 回调函数和修饰符。
  2. 将 lambda 设置为 onClick 参数,并传入 Icon(),如以下代码所示:
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

@Composable
fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(
        onClick = { onDelete() },
        modifier = modifier
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_delete),
            contentDescription = stringResource(R.string.delete)
        )
    }
}
  1. 添加一个预览函数,用于预览“删除”按钮。
@Preview
@Composable
fun PreviewDeleteIcon() {
    DeleteButton({})
}

Android Studio 中“删除”图标的预览

5. 实现 ListItem 函数

现在,您已经拥有了显示列表项所需的所有可组合项,接下来可以将它们排列在布局中了。请注意您在上一步中定义的 ListItem() 函数。

@Composable
fun ListItem(
   input: Juice,
   onEdit: (Juice) -> Unit,
   onDelete: (Juice) -> Unit,
   modifier: Modifier = Modifier
) {
}

JuiceListAdapter.kt 中,完成以下步骤以实现 ListItem() 函数。

  1. Mdc3Theme {} lambda 内添加 Row 布局。
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import com.google.accompanist.themeadapter.material3.Mdc3Theme

Mdc3Theme {
   Row(
       modifier = modifier,
       horizontalArrangement = Arrangement.SpaceBetween
   ) {

   }
}
  1. Row lambda 内,调用您作为子元素创建的 JuiceIconJuiceDetailsDeleteButton 这三个可组合项。
JuiceIcon(input.color)
JuiceDetails(input, Modifier.weight(1f))
DeleteButton({})

Modifier.weight(1f) 传递给 JuiceDetails() 可组合项可确保在测量未加权的子元素后,果汁详情会占用剩余的水平空间。

  1. 将采用顶部对齐方式的 onDelete(input) lambda 和修饰符作为参数传递给 DeleteButton 可组合项。
DeleteButton(
   onDelete = {
       onDelete(input)
   },
   modifier = Modifier.align(Alignment.Top)
)
  1. 编写一个预览函数,用于预览 ListItem 可组合项。
@Preview
@Composable
fun PreviewListItem() {
   ListItem(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4), {})
}

Android Studio 列表项预览,其中包含甜菜汁详细信息

  1. ListItem 可组合项绑定到 ViewHolder。当列表项被点击时,调用 clickable() lambda 函数中的 onEdit(input),以便打开编辑对话框。

JuiceListViewHolder 类中的 bind() 函数内,您需要托管可组合项。您将使用 ComposeView,这种 Android View 可以使用其 setContent 方法托管 Compose 界面内容。

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. 运行应用。添加您喜爱的果汁。注意酷炫的 Compose 列表项。

手机屏幕,在输入对话框中填写了果汁详细信息. 手机屏幕,在列表中列出了一种果汁

恭喜!您刚刚创建了您的第一个 Compose 互操作应用,该应用在基于 View 的应用中使用 Compose 元素。

6. 获取解决方案代码

如需下载完成后的 Codelab 代码,您可以使用以下 Git 命令:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views-with-compose

或者,您也可以下载 ZIP 文件形式的代码库,将其解压缩并在 Android Studio 中打开。

如果您想查看解决方案代码,请前往 GitHub 查看

7. 了解更多内容

Android 开发者文档

Codelab [中级]