在基于 View 的应用中添加 Compose

1. 开始之前

从一开始,Jetpack Compose 就是与 View 互操作性一起设计的,这意味着 Compose 和 View 系统可以共享资源并并排工作以显示 UI。此功能使您能够将 Compose 添加到现有的基于 View 的应用中。这意味着 Compose 和 View 可以共存于您的代码库中,直到您的整个应用完全使用 Compose。

在此代码实验室中,您将 **Juice Tracker** 应用中的基于 View 的列表项更改为 Compose。如果愿意,您可以自行转换 Juice Tracker 的其余 View。

如果您的应用使用基于 View 的 UI,您可能不想立即重写整个 UI。此代码实验室可帮助您将基于 View 的 UI 中的单个 View 转换为 Compose 元素。

先决条件

  • 熟悉基于 View 的 UI。
  • 了解如何使用基于 View 的 UI 构建应用。
  • 了解 Kotlin 语法,包括 Lambda 表达式。
  • 了解如何在 Jetpack Compose 中构建应用。

您将学到什么

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

您将构建什么

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

2. 初始应用概述

此代码实验室使用来自 使用 View 构建 Android 应用 的 **Juice Tracker** 应用解决方案代码作为初始代码。初始应用已使用 Room 持久性库保存数据。用户可以将果汁信息添加到应用数据库中,例如果汁名称、描述、颜色和评分。

36bd5542e97fee2e.png

在此代码实验室中,您将基于 View 的列表项转换为 Compose。

List item with juice details

下载此代码实验室的初始代码

要开始,请下载初始代码

或者,您可以克隆代码的 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 库

回想一下,Compose 和 View 可以一起存在于给定的屏幕上;您可以在 Compose 中放置一些 UI 元素,而在 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 协同工作。在之前的代码实验室中,您没有执行此步骤,因为当您创建新的 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 UI 内容的 Android View。使用 setContent 为 View 提供内容可组合函数。

  1. 打开 layout/list_item.xml 并在 **拆分** 选项卡中查看预览。

在本代码实验室结束时,您将用可组合项替换此 View。

7a2df616fde1ec56.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) 
  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. 在 **删除** 对话框中选择 **确定**。

2954ed44c5827571.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
) {
}

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

4ec7f82371c6bc15.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")
}

c016198f82a5d199.png

创建果汁详细信息可组合项

JuiceListAdapter.kt 中,您需要添加另一个可组合函数以显示果汁详细信息。您还需要一个列布局以显示名称和描述的两个 Text 可组合项以及评分指示器。为此,请完成以下步骤

  1. 添加一个名为 JuiceDetails() 的可组合函数,该函数接受 Juice 对象和 Modifier,以及用于果汁名称的文本可组合项和用于果汁描述的可组合项,如下面的代码所示
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() 的可组合函数。

536030e2ecb01a4e.png

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

  1. 定义 RatingDisplay() 函数以根据评分显示星级。此可组合函数根据评分显示星级数量。

Rating bar with four stars

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. 在 **项目** 窗格中,右键单击 **drawable > 新建 > 矢量资源**。

201431ca3d212113.png

  1. 在 **资源工作室** 对话框中,搜索星形图标。选择填充的星形图标。

9956ed24371f61ac.png

5a79bac6f3982b72.png

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

44d4bdfa93bc369a.png

  1. 单击 **下一步 > 完成**。
  2. 请注意,可绘制对象出现在 res/drawable 文件夹中。

64bb8d9f05019229.png

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

with juice name juice description and star rating bar

创建删除按钮可组合项

  1. JuiceListAdapter.kt 中,添加另一个名为 DeleteButton() 的可组合函数,该函数接受 Lambda 回调函数和 Modifier。
  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 preview of the delete icon

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 list item preview with sweet beet juice details

  1. ListItem 可组合项绑定到 View Holder。在 clickable() Lambda 函数内部调用 onEdit(input) 以在单击列表项时打开编辑对话框。

JuiceListViewHolder 类中,在 bind() 函数内,您需要托管可组合项。您使用 ComposeView,它是一个可以使用其 setContent 方法托管 Compose UI 内容的 Android View。

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. 运行应用。添加您最喜欢的果汁。请注意闪亮的可组合列表项。

aadccf32ab952d0f.png. 8aa751f4cf63bf98.png

恭喜!您刚刚创建了第一个 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 [中级]