将 Compose 添加到基于 View 的应用

1. 准备工作

Jetpack Compose 从设计之初就考虑了与 View 的互操作性,这意味着 Compose 和 View 系统可以共享资源并协同工作以显示 UI。此功能使您可以将 Compose 添加到现有的基于 View 的应用中。这意味着 Compose 和 Views 可以共存于您的代码库中,直到您的整个应用完全迁移到 Compose。

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

如果您的应用具有基于 View 的 UI,您可能不想一次性重写其整个 UI。本 Codelab 将帮助您将基于 View 的 UI 中的单个视图转换为 Compose 元素。

前提条件

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

您将学到什么

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

您将构建什么

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

2. 初始应用概览

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

36bd5542e97fee2e.png

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

List item with juice details

下载本 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 库

回想一下,Compose 和 Views 可以共存于给定的屏幕上;您可以让一些 UI 元素在 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 是一个 Android View,可以托管 Jetpack Compose UI 内容。使用 setContent 为视图提供内容可组合函数。

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

在本 Codelab 结束时,您将用一个可组合项替换此视图。

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. Delete 对话框中选择 OK

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() 的可组合函数,它接受一个 color 和一个 Modifier
@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 中创建星形 drawable,您需要创建星形矢量资源。

  1. Project 窗格中,右键点击 drawable > New > Vector Asset

201431ca3d212113.png

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

9956ed24371f61ac.png

5a79bac6f3982b72.png

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

44d4bdfa93bc369a.png

  1. 点击 Next > Finish
  2. 请注意,res/drawable 文件夹中会显示一个 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 和带有顶部对齐方式的 Modifier 作为参数传递给 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 可组合项绑定到视图持有者。在 clickable() lambda 函数内部调用 onEdit(input),以便在点击列表项时打开编辑对话框。

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

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

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 [中级]