添加可滚动列表

1. 开始之前

在本 Codelab 中,您将学习如何使用 Jetpack Compose 在您的应用中创建可滚动列表。

您将使用 **Affirmations** 应用,该应用显示一系列积极肯定的语句,并配以精美的图片,为您的日常生活带来积极能量!

数据已经准备就绪,您只需要将这些数据显示在 UI 上。

先决条件

  • 熟悉 Kotlin 中的列表
  • 使用 Jetpack Compose 构建布局的经验
  • 在设备或模拟器上运行应用的经验

您将学习的内容

  • 如何使用 Jetpack Compose 创建 Material Design 卡片
  • 如何使用 Jetpack Compose 创建可滚动列表

您将构建的内容

  • 您将采用现有的应用程序,并在 UI 中添加一个可滚动的列表。

最终产品将如下所示

286f5132aa155fa6.png

您需要的内容

  • 一台可以连接互联网的电脑、一个网络浏览器和 Android Studio
  • 访问 GitHub

下载初始代码

在 Android Studio 中,打开 basic-android-kotlin-compose-training-affirmations 文件夹。

starter 分支代码构建时,应用预计将显示空白屏幕。

3beea0789e2eeaba.png

2. 创建列表项数据类

为肯定语句创建数据类

在 Android 应用中,列表由列表项组成。对于单个数据,这可能很简单,例如字符串或整数。对于具有多个数据片段的列表项(例如图像和文本),您将需要一个包含所有这些属性的类。数据类是一种只包含属性的类,它可以提供一些实用方法来处理这些属性。

  1. 在 **com.example.affirmations** 下创建一个新包。

89c8d8485c685fac.png

将新包命名为 **model**。model 包将包含由数据类表示的数据模型。数据类将由表示与“肯定语句”相关信息的属性组成,“肯定语句”将包含字符串资源和图像资源。包是包含类甚至其他目录的目录。

b54fb6bf57de44c8.png

  1. 在 **com.example.affirmations.model** 包中创建一个新类。

58510a651bd49100.png

将新类命名为 **Affirmation** 并将其设为 **数据类**。

7f94b65ee3d8407f.png

  1. 每个 Affirmation 包含一个图像和一个字符串。在 Affirmation 数据类中创建两个 val 属性。一个应该命名为 stringResourceId,另一个命名为 imageResourceId。它们都应该是整数。

Affirmation.kt

data class Affirmation(
    val stringResourceId: Int,
    val imageResourceId: Int
)
  1. 使用 @StringRes 注解对 stringResourceId 属性进行注解,并使用 @DrawableRes 注解对 imageResourceId 进行注解。stringResourceId 代表存储在字符串资源中的肯定语句文本的 ID。imageResourceId 代表存储在可绘制资源中的肯定语句图像的 ID。

Affirmation.kt

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class Affirmation(
    @StringRes val stringResourceId: Int,
    @DrawableRes val imageResourceId: Int
)
  1. 在 **com.example.affirmations.data** 包中,打开 **Datasource.kt** 文件,取消注释两个 import 语句和 Datasource 类的内容。

Datasource.kt

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource() {
    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1, R.drawable.image1),
            Affirmation(R.string.affirmation2, R.drawable.image2),
            Affirmation(R.string.affirmation3, R.drawable.image3),
            Affirmation(R.string.affirmation4, R.drawable.image4),
            Affirmation(R.string.affirmation5, R.drawable.image5),
            Affirmation(R.string.affirmation6, R.drawable.image6),
            Affirmation(R.string.affirmation7, R.drawable.image7),
            Affirmation(R.string.affirmation8, R.drawable.image8),
            Affirmation(R.string.affirmation9, R.drawable.image9),
            Affirmation(R.string.affirmation10, R.drawable.image10))
    }
}

3. 向您的应用添加列表

创建列表项卡片

此应用旨在显示一系列肯定语句。配置 UI 以显示列表的第一步是创建列表项。每个肯定语句项包含一个图像和一个字符串。每个这些项的数据都随初始代码一起提供,您将创建 UI 组件来显示此类项。

该项将包含一个 Card 可组合项,其中包含一个 Image 和一个 Text 可组合项。在 Compose 中,Card 是一个显示内容和操作的单个容器的表面。肯定语句卡片在预览中将如下所示

4f657540712a069f.png

卡片显示一个图像,其下方有一些文本。可以使用包装在 Card 可组合项中的 Column 可组合项实现此垂直布局。您可以自己尝试一下,也可以按照以下步骤操作。

  1. 打开 **MainActivity.kt** 文件。
  2. AffirmationsApp() 方法下方创建一个新方法,名为 AffirmationCard(),并使用 @Composable 注解对其进行注解。

MainActivity.kt

@Composable
fun AffirmationsApp() {
}

@Composable
fun AffirmationCard() {

}
  1. 编辑方法签名以将 Affirmation 对象作为参数。 Affirmation 对象来自 model 包。

MainActivity.kt

import com.example.affirmations.model.Affirmation

@Composable
fun AffirmationCard(affirmation: Affirmation) {

}
  1. 向签名中添加一个 modifier 参数。为该参数设置 Modifier 的默认值。

MainActivity.kt

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {

}
  1. AffirmationCard 方法内部,调用 Card 可组合项。传入 modifier 参数。

MainActivity.kt

import androidx.compose.material3.Card

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {

    }
}
  1. Card 可组合项内添加一个 Column 可组合项。Column 可组合项中的项目在 UI 中垂直排列。这允许您将图像放置在关联文本的上方。相反,Row 可组合项会水平排列其包含的项目。

MainActivity.kt

import androidx.compose.foundation.layout.Column

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {

        }
    }

}
  1. Column 的 lambda 主体内部添加一个 Image 可组合项。回想一下,Image 可组合项始终需要一个要显示的资源和一个 contentDescription。资源应为传递给 painter 参数的 painterResourcepainterResource 方法将加载矢量可绘制对象或栅格化资产格式,例如 PNG。此外,还为 contentDescription 参数传递一个 stringResource

MainActivity.kt

import androidx.compose.foundation.Image
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
            )
        }
    }
}
  1. 除了 paintercontentDescription 参数外,还要传递一个 modifier 和一个 contentScalecontentScale 确定应如何缩放和显示图像。Modifier 对象应设置 fillMaxWidth 属性,高度为 194.dpcontentScale 应为 ContentScale.Crop

MainActivity.kt

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.unit.dp
import androidx.compose.ui.layout.ContentScale

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(194.dp),
                contentScale = ContentScale.Crop
            )
        }
    }
}
  1. Column 内部,在 Image 可组合项之后创建一个 Text 可组合项。将 affirmation.stringResourceIdstringResource 传递给 text 参数,将具有 padding 属性设置为 16.dpModifier 对象传递给它,并通过将 MaterialTheme.typography.headlineSmall 传递给 style 参数来设置文本主题。

MainActivity.kt

import androidx.compose.material3.Text
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.platform.LocalContext

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(194.dp),
                contentScale = ContentScale.Crop
            )
            Text(
                text = LocalContext.current.getString(affirmation.stringResourceId),
                modifier = Modifier.padding(16.dp),
                style = MaterialTheme.typography.headlineSmall
            )
        }
    }
}

预览 AffirmationCard 可组合项

卡片是 **Affirmations** 应用 UI 的核心,您为此付出了很大的努力!要检查卡片是否正确显示,您可以创建一个可组合项,无需启动整个应用即可进行预览。

  1. 创建一个名为 AffirmationCardPreview() 的私有方法。使用 @Preview@Composable 对该方法进行注解。

MainActivity.kt

import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
private fun AffirmationCardPreview() {

}
  1. 在该方法内部,调用 AffirmationCard 可组合项,并向其传递一个新的 Affirmation 对象,其中包含传递到其构造函数中的 R.string.affirmation1 字符串资源和 R.drawable.image1 可绘制资源。

MainActivity.kt

@Preview
@Composable
private fun AffirmationCardPreview() {
    AffirmationCard(Affirmation(R.string.affirmation1, R.drawable.image1))
}
  1. 打开 **拆分** 选项卡,您将看到 AffirmationCard 的预览。如有必要,请单击 **设计** 窗格中的 **构建并刷新** 以显示预览。

924a4df2c1db236c.png

创建列表

列表项组件是列表的构建块。创建列表项后,您可以利用它来创建列表组件本身。

  1. 创建一个名为 AffirmationList() 的函数,使用 @Composable 注解对其进行注解,并在方法签名中声明一个 Affirmation 对象的 List

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>) {
    
}
  1. 在方法签名中声明一个 modifier 对象,其默认值为 Modifier

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {

}
  1. 在 Jetpack Compose 中,可以使用 LazyColumn 可组合项创建可滚动列表。 LazyColumnColumn 的区别在于,当您只有少量项目需要显示时,应该使用 Column,因为 Compose 会一次性加载所有项目。 Column 只能容纳预定义或固定的数量的可组合项。 LazyColumn 可以按需添加内容,这使其非常适合长列表,尤其是在列表长度未知的情况下。 LazyColumn 还默认提供滚动功能,无需额外代码。在 AffirmationList() 函数内声明一个 LazyColumn 可组合项。将 modifier 对象作为参数传递给 LazyColumn

MainActivity.kt

import androidx.compose.foundation.lazy.LazyColumn

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {

    }
}
  1. LazyColumn 的 lambda 体中,调用 items() 方法,并将 affirmationList 传递进去。 items() 方法是向 LazyColumn 添加项目的方式。此方法对于此可组合项来说有些独特,对于大多数可组合项来说,这并非常见的做法。

MainActivity.kt

import androidx.compose.foundation.lazy.items

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) {

        }
    }
}
  1. items() 方法的调用需要一个 lambda 函数。在该函数中,指定一个 affirmation 参数,该参数表示来自 affirmationList 的一个肯定语句项目。

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) { affirmation ->

        }
    }
}
  1. 对于列表中的每个肯定语句,调用 AffirmationCard() 可组合项。将 affirmation 和一个 Modifier 对象(其 padding 属性设置为 8.dp)传递给它。

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) { affirmation ->
            AffirmationCard(
                affirmation = affirmation,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

显示列表

  1. AffirmationsApp 可组合项中,检索当前布局方向并将其保存在变量中。稍后将使用这些变量来配置填充。

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
}
  1. 现在创建一个 Surface 可组合项。此可组合项将为 AffirmationsList 可组合项设置填充。

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface() {
    }
}
  1. 将一个 Modifier 传递给 Surface 可组合项,该修饰符填充其父级的最大宽度和高度,设置状态栏填充,并将起始和结束填充设置为 layoutDirection。以下是将 LayoutDirection 对象转换为填充的示例: WindowInsets.safeDrawing.asPaddingValues().calculateStartPadding(layoutDirection)

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        Modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .padding(
            start = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateStartPadding(layoutDirection),
            end = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateEndPadding(layoutDirection),
        ),
    ) {
    }
}
  1. Surface 可组合项的 lambda 中,调用 AffirmationList 可组合项,并将 DataSource().loadAffirmations() 传递给 affirmationList 参数。

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        Modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .padding(
            start = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateStartPadding(layoutDirection),
            end = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateEndPadding(layoutDirection),
        ),
    ) {
        AffirmationsList(
            affirmationList = Datasource().loadAffirmations(),
        )
    }
}

在设备或模拟器上运行Affirmations 应用,查看最终产品!

286f5132aa155fa6.png

4. 获取解决方案代码

要下载完成的 codelab 代码,您可以使用以下 git 命令

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-affirmations.git
$ cd basic-android-kotlin-compose-training-affirmations
$ git checkout intermediate

或者,您可以将存储库下载为 zip 文件,解压缩它,然后在 Android Studio 中打开它。

如果您想查看解决方案代码,请在 GitHub 上查看

5. 结论

您现在知道了如何使用 Jetpack Compose 创建卡片、列表项和可滚动列表!请记住,这些只是创建列表的基本工具。您可以发挥您的创造力,随意自定义列表项!

摘要

  • 使用Card 可组合项创建列表项。
  • 修改 Card 可组合项中包含的 UI。
  • 使用LazyColumn 可组合项创建可滚动列表。
  • 使用自定义列表项构建列表。