1. 开始之前
简介
在本课程的这个阶段,您已经熟练掌握了使用 Compose 构建应用程序,并且对使用 XML、视图、视图绑定和片段构建应用程序有一定的了解。在使用视图构建应用程序后,您可能会体会到使用 Compose 这样的声明式 UI 构建应用程序的便利性。但是,在某些情况下,使用视图而不是 Compose 更有意义。在本 Codelab 中,您将学习如何使用视图互操作性将视图组件添加到现代 Compose 应用程序中。
在撰写本 Codelab 时,您即将创建的 UI 组件在 Compose 中尚不可用。这是利用视图互操作性的绝佳机会!
先决条件
- 完成 使用 Compose 的 Android 基础知识 教程,以及 使用视图构建 Android 应用程序 Codelab。
您需要什么
- 一台连接互联网的电脑和 Android Studio
- 一台设备或模拟器
- Juice Tracker 应用程序的起始代码
您将构建什么
在本 Codelab 中,您需要将三个视图集成到 Compose UI 中以完成 Juice Tracker 应用程序的 UI;一个 Spinner、一个 RatingBar 和一个 AdView。要构建这些组件,您将使用视图互操作性(简称视图互操作)。使用视图互操作,您可以通过将视图包装在 Composable 中来实际将视图添加到您的应用程序中。
代码演练
在本 Codelab 中,您将使用与 使用视图构建 Android 应用程序 和 将 Compose 添加到基于视图的应用程序 Codelab 中相同的 JuiceTracker 应用程序。此版本的不同之处在于提供的起始代码完全在 Compose 中。该应用程序目前缺少输入对话框中的颜色和评分输入,以及列表屏幕顶部的广告横幅。
bottomsheet
目录包含与输入对话框相关的全部 UI 组件。创建这些组件后,此包应包含颜色和评分输入的 UI 组件。
homescreen
包含主屏幕托管的 UI 组件,包括 JuiceTracker 列表。创建广告横幅后,此包最终应包含广告横幅。
主要 UI 组件(例如底部工作表和果汁列表)托管在 JuiceTrackerApp.kt
文件中。
2. 获取起始代码
要开始,请下载起始代码
或者,您可以克隆代码的 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 compose-starter
- 在 Android Studio 中,打开
basic-android-kotlin-compose-training-juice-tracker
文件夹。 - 在 Android Studio 中打开 Juice Tracker 应用程序代码。
3. Gradle 配置
将 play services ads 依赖项添加到应用程序 build.gradle.kts
文件中。
app/build.gradle.kts
android {
...
dependencies {
...
implementation("com.google.android.gms:play-services-ads:22.2.0")
}
}
4. 设置
将以下值添加到 Android 清单中,位于 activity
标签上方,以启用用于测试的广告横幅
AndroidManifest.xml
...
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
...
5. 完成输入对话框
在本节中,您将通过创建颜色微调器和评分条来完成输入对话框。颜色微调器是允许您选择颜色的组件,而评分条允许您为果汁选择评分。请参阅下面的设计
创建颜色微调器
要在 Compose 中实现微调器,必须使用 Spinner
类。Spinner
是一个视图组件,而不是一个 Composable,因此必须使用互操作性来实现它。
- 在
bottomsheet
目录中,创建一个名为ColorSpinnerRow.kt
的新文件。 - 在文件中创建一个名为
SpinnerAdapter
的新类。 - 在
SpinnerAdapter
的构造函数中,定义一个名为onColorChange
的回调参数,该参数接受一个Int
参数。SpinnerAdapter
处理Spinner
的回调函数。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
- 实现
AdapterView.OnItemSelectedListener
接口。
实现此接口可以让您定义微调器的点击行为。稍后您将在 Composable 中设置此适配器。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
}
- 实现
AdapterView.OnItemSelectedListener
成员函数:onItemSelected()
和onNothingSelected()
。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
TODO("Not yet implemented")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
- 修改
onItemSelected()
函数以调用onColorChange()
回调函数,以便当您选择颜色时,应用程序会更新 UI 中所选的值。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
onColorChange(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
- 修改
onNothingSelected()
函数以将颜色设置为0
,以便当您不选择任何内容时,默认颜色为第一个颜色,红色。
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
onColorChange(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
onColorChange(0)
}
}
SpinnerAdapter
(通过回调函数定义微调器的行为)已构建完成。现在您需要构建微调器的内容并使用数据填充它。
- 在
ColorSpinnerRow.kt
文件内,但在SpinnerAdapter
类之外,创建一个名为ColorSpinnerRow
的新 Composable。 - 在
ColorSpinnerRow()
的方法签名中,添加一个用于微调器位置的Int
参数,一个带Int
参数的回调函数和一个修饰符。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
}
- 在函数内,使用
JuiceColor
枚举创建果汁颜色字符串资源数组。此数组用作将填充微调器的内容。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
}
- 添加一个
InputRow()
Composable 并传递输入标签的颜色字符串资源和一个修饰符,该修饰符定义Spinner
出现的输入行。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
}
}
接下来,您将创建 Spinner
!由于 Spinner
是一个视图类,因此必须利用 Compose 的视图互操作性 API 将其包装到 Composable 中。这是通过 AndroidView
Composable 实现的。
- 要在 Compose 中使用
Spinner
,请在InputRow
lambda 主体中创建一个AndroidView()
Composable。AndroidView()
Composable 在 Composable 中创建视图元素或层次结构。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
AndroidView()
}
}
AndroidView
**Composable** 接受三个参数
- **
factory
** **lambda**,这是一个创建视图的函数。 - **
update
** **回调**,在factory
中创建的视图膨胀时调用。 - 一个 **Composable**
modifier
。
- 要实现
AndroidView
,首先传递一个修饰符并填充屏幕的最大宽度。 - 为
factory
参数传递一个 lambda。 factory
lambda 接受Context
作为参数。创建一个Spinner
类并传递上下文。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context)
}
)
}
}
就像 RecyclerView.Adapter
为 RecyclerView
提供数据一样,ArrayAdapter
为 Spinner
提供数据。Spinner
需要一个适配器来保存颜色数组。
- 使用
ArrayAdapter
设置适配器。ArrayAdapter
需要一个上下文、一个 XML 布局和一个数组。为布局传递simple_spinner_dropdown_item
;此布局作为 Android 的默认值提供。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context).apply {
adapter =
ArrayAdapter(
context,
android.R.layout.simple_spinner_dropdown_item,
juiceColorArray
)
}
}
)
}
}
factory
回调返回在其内部创建的视图的实例。update
是一个回调,它接受与 factory
回调返回的类型相同的参数。此参数是 factory
膨胀的视图的实例。在本例中,由于在 factory 中创建了一个 Spinner
,因此可以在 update
lambda 主体中访问该 Spinner
的实例。
- 添加一个
update
回调,该回调传递一个spinner
。使用update
中提供的回调来调用setSelection()
方法。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
//...
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
- 使用前面创建的
SpinnerAdapter
在update
中设置onItemSelectedListener()
回调。
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
// ...
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
颜色微调器组件的代码现在已完成。
- 添加以下实用程序函数以获取
JuiceColor
的枚举索引。您将在下一步中使用它。
private fun findColorIndex(color: String): Int {
val juiceColor = JuiceColor.valueOf(color)
return JuiceColor.values().indexOf(juiceColor)
}
- 在
EntryBottomSheet.kt
文件中的SheetForm
Composable 中实现ColorSpinnerRow
。将颜色微调器放在“描述”文本之后,按钮上方。
bottomsheet/EntryBottomSheet.kt
...
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
...
TextInputRow(
inputLabel = stringResource(R.string.juice_description),
fieldValue = juice.description,
onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) },
modifier = Modifier.fillMaxWidth()
)
ColorSpinnerRow(
colorSpinnerPosition = findColorIndex(juice.color),
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
ButtonRow(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = dimensionResource(R.dimen.padding_medium)),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
}
创建评分输入
- 在
bottomsheet
目录中创建一个名为RatingInputRow.kt
的新文件。 - 在
RatingInputRow.kt
文件中,创建一个名为RatingInputRow()
的新 Composable。 - 在方法签名中,为评分传递一个
Int
,一个带Int
参数的回调函数来处理选择更改,以及一个修饰符。
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
}
- 像
ColorSpinnerRow
一样,向包含AndroidView
的 Composable 添加一个InputRow
,如下面的示例代码所示。
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = {},
update = {}
)
}
}
- 在
factory
lambda 主体中,创建一个RatingBar
类的实例,该实例提供此设计所需的评分条类型。将stepSize
设置为1f
以强制评分仅为整数。
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = { context ->
RatingBar(context).apply {
stepSize = 1f
}
},
update = {}
)
}
}
膨胀视图时,将设置评分。请记住,factory
将 RatingBar
的实例返回到更新回调。
- 使用传递给 Composable 的评分来设置
RatingBar
实例在update
lambda 体中的评分。 - 当设置新的评分时,使用
RatingBar
回调来调用onRatingChange()
回调函数以更新 UI 中的评分。
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = { context ->
RatingBar(context).apply {
stepSize = 1f
}
},
update = { ratingBar ->
ratingBar.rating = rating.toFloat()
ratingBar.setOnRatingBarChangeListener { _, _, _ ->
onRatingChange(ratingBar.rating.toInt())
}
}
)
}
}
评分输入组合组件现在已完成。
- 在
EntryBottomSheet
中使用RatingInputRow()
组合组件。将其放在颜色选择器之后,按钮之上。
bottomsheet/EntryBottomSheet.kt
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
...
ColorSpinnerRow(
colorSpinnerPosition = findColorIndex(juice.color),
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
RatingInputRow(
rating = juice.rating,
onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) }
)
ButtonRow(
modifier = Modifier.align(Alignment.CenterHorizontally),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
}
创建广告横幅
- 在
homescreen
包中,创建一个名为AdBanner.kt
的新文件。 - 在
AdBanner.kt
文件中,创建一个名为AdBanner()
的新组合组件。
与您之前创建的组合组件不同,AdBanner
不需要输入。因此,您无需将其包装在InputRow
组合组件中。但是,它需要一个AndroidView
。
- 尝试使用
AdView
类自行构建横幅。确保将广告尺寸设置为AdSize.BANNER
,并将广告单元 ID 设置为"ca-app-pub-3940256099942544/6300978111"
。 - 当
AdView
膨胀时,使用AdRequest Builder
加载广告。
homescreen/AdBanner.kt
@Composable
fun AdBanner(modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier,
factory = { context ->
AdView(context).apply {
setAdSize(AdSize.BANNER)
// Use test ad unit ID
adUnitId = "ca-app-pub-3940256099942544/6300978111"
}
},
update = { adView ->
adView.loadAd(AdRequest.Builder().build())
}
)
}
- 在
JuiceTrackerApp
中,将AdBanner
放在JuiceTrackerList
之前。JuiceTrackerList
声明在第 83 行。
ui/JuiceTrackerApp.kt
...
AdBanner(
Modifier
.fillMaxWidth()
.padding(
top = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_small)
)
)
JuiceTrackerList(
juices = trackerState,
onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
onUpdate = { juice ->
juiceTrackerViewModel.updateCurrentJuice(juice)
scope.launch {
bottomSheetScaffoldState.bottomSheetState.expand()
}
},
)
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 compose-with-views
或者,您可以将存储库下载为 zip 文件,解压缩它,然后在 Android Studio 中打开它。
如果您想查看解决方案代码,请在 GitHub 上查看。
7. 了解更多
8. 结束语!
本课程可能到此结束,但这只是您 Android 应用开发之旅的开始!
在本课程中,您学习了如何使用 Jetpack Compose 构建应用程序,Jetpack Compose 是用于构建原生 Android 应用程序的现代 UI 工具包。在本课程中,您构建了带有列表、单个或多个屏幕的应用程序,并在它们之间进行了导航。您学习了如何创建交互式应用程序,使您的应用程序响应用户输入并更新 UI。您应用了 Material Design 并使用颜色、形状和排版来为您的应用程序设置主题。您还使用了 Jetpack 和其他第三方库来安排任务、从远程服务器检索数据、本地持久化数据等等。
完成本课程后,您不仅对如何使用 Jetpack Compose 创建漂亮且响应迅速的应用程序有了很好的理解,而且还掌握了创建高效、可维护和视觉上吸引人的 Android 应用程序所需的知识和技能。这个基础将帮助您继续学习和提高您在现代 Android 开发和 Compose 中的技能。
我们要感谢大家参与并完成本课程!我们鼓励大家继续学习,并通过其他资源来扩展您的技能,例如Android 开发者文档、面向 Android 开发人员的 Jetpack Compose 课程、现代 Android 应用架构、Android 开发者博客、其他codelab和示例项目。
最后,别忘了在社交媒体上分享您构建的内容,并使用主题标签 #AndroidBasics,以便我们和 Android 开发者社区的其他成员也能关注您的学习之旅!
快乐创作!!