Google 致力于为黑人社区推动种族平等。查看具体举措
教程

Jetpack Compose 基础知识

Jetpack Compose 是用于构建原生 Android 界面的新工具包。Jetpack Compose 使用更少的代码、强大的工具和直观的 Kotlin API 简化并加快了 Android 上的界面开发。

在本教程中,您将使用声明性的函数构建一个简单的界面组件。您无需修改任何 XML 布局,也不需要直接创建界面微件,而只需要调用 Jetpack Compose 函数来声明您想要的元素,Compose 编译器即会完成后面的所有工作。

完整预览
注意:Jetpack Compose 目前为 Alpha 版。API Surface 尚未最终确定,预计会有变动。
完整预览

第 1 课:可组合函数

Jetpack Compose 是围绕可组合函数构建的。这些函数可让您以编程方式定义应用界面,只需描述应用界面的形状和数据依赖关系,而不必关注界面的构建过程。如需创建可组合函数,只需将 @Composable 注释添加到函数名称中即可。

添加文本元素

首先,按照 Jetpack Compose 设置说明操作,使用 Empty Compose Activity 模板创建一个应用。默认模板已包含一些 Compose 元素,但我们下面要逐步进行构建。首先,删除“Greeting”和“Default Preview”函数,然后从 MainActivity 中删除 setContent 块,将该 Activity 留空。编译并运行您的空白应用。

现在,向空白的 Activity 中添加文本元素。可以通过定义内容块并调用 Text() 函数来实现此目的。

setContent 块定义了 Activity 的布局。我们不使用 XML 文件来定义布局内容,而是调用可组合函数。Jetpack Compose 使用自定义 Kotlin 编译器插件将这些可组合函数转换为应用的界面元素。例如,Compose 界面库定义了 Text() 函数 ;您可以调用该函数在应用中声明文本元素。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
显示预览
隐藏预览

定义可组合函数

可组合函数只能在其他可组合函数的范围内调用。要使函数成为可组合函数,请添加 @Composable 注释。如需尝试此操作,请定义一个 Greeting() 函数并向其传递一个名称,然后该函数就会使用该名称配置文本元素。

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
显示预览
隐藏预览

在 Android Studio 中预览函数

当前的 Canary 版 Android Studio 允许您在 IDE 中预览可组合函数,而无需将应用下载到 Android 设备或模拟器中。主要限制在于,可组合函数不能接受任何参数。因此,您无法直接预览 Greeting() 函数,而是需要创建另一个名为 PreviewGreeting() 的函数,由该函数使用适当的参数调用 Greeting()。请在 @Composable 上方添加 @Preview 注释。

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
显示预览
隐藏预览

重新构建您的项目。由于新的 previewGreeting() 函数未在任何位置受到调用,因此应用本身不会更改,但 Android Studio 会添加一个预览窗口。此窗口会显示由标有 @Preview 注释的可组合函数创建的界面元素的预览。任何时候,如需更新预览,请点击预览窗口顶部的刷新按钮。

图 1. 使用 Android Studio 预览可组合函数。
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
显示预览
隐藏预览
class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
显示预览
隐藏预览
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
显示预览
隐藏预览
图 1. 使用 Android Studio 预览可组合函数。

第 2 课:布局

界面元素采用多层次结构,元素中又包含其他元素。在 Compose 中,您可以通过从可组合函数中调用其他可组合函数来构建界面层次结构。

从一些文本开始

返回到您的 Activity,用新的 NewsStory() 函数替换 Greeting() 函数。在本教程的其余部分,您将修改该 NewsStory() 函数,并且不会再更改 Activity 代码。

最佳做法是单独创建不会被应用调用的预览函数;专门的预览函数可以提高性能,并且有利于以后更轻松地设置多个预览。因此,请创建一个默认预览函数,该函数的唯一用途就是调用 NewsStory() 函数。随着您按照本教程对 NewsStory() 进行更改,预览内容会反映您所做的更改。

这段代码会在内容视图中创建三个文本元素。但是,由于我们未提供有关如何排列这三个文本元素的信息,因此它们会相互重叠,使文本无法阅读。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
显示预览
隐藏预览

使用 Column

Column 函数可让您垂直堆叠元素。向 NewsStory() 函数中添加一个 Column

默认设置会直接将所有子项逐个堆叠起来,中间不留间距。Column 本身位于内容视图的左上角。

@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览

向 Column 中添加样式设置

通过将参数传递给 Column 调用,可以配置 Column 的尺寸和位置,以及 Column 的子项的排列方式。

该设置具有以下含义:

  • modifier:可供您配置布局。本例中使用了一个 Modifier.padding 修饰符,将 Column 内嵌在周围的视图中。
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览

添加图片

我们想在文本上面添加一张图片。使用资源管理器将这张名为 header 的照片添加到应用的可绘制资源。

现在,修改您的 NewsStory() 函数。您将添加对 Image() 的调用,以将图片放入 Column。“foundation”软件包中提供了这些可组合项,您可能需要添加该软件包。请参阅 Jetpack Compose 设置说明。图片的比例会有问题,但没关系,您可以在下一步中纠正此问题。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览

图片已添加到布局中,但其尺寸尚未调整为适当大小。如需设置图片样式,请将尺寸 Modifier 传递给对 Image() 的调用。

  • preferredHeight(180.dp):指定图片的高度。
  • fillMaxWidth():指定图片的宽度应足以填充所属布局。

您还需要向 Image() 传递一个 contentScale 参数:

  • contentScale = ContentScale.Crop:指定图片应填充 Column 的整个宽度,并根据需要剪裁为适当的高度。
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览

添加 Spacer,将图片与标题分开。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览

第 3 课:Material Design

Compose 旨在支持 Material Design 原则。它的许多界面元素都原生支持 Material Design。在本课中,您将使用 Material 微件来设置应用的样式。

采用形状

Material Design 系统的关键要素之一就是 Shape。使用 clip() 函数对图片的四角进行圆角化处理。

Shape 不可见,但图片已被剪裁以匹配 Shape,因此现在呈现轻微的圆角。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览

设置文本样式

借助 Compose,您可以轻松遵循 Material Design 原则。将 MaterialTheme 应用到您创建的组件。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
显示预览
隐藏预览

差别可能不太明显,但文本现在采用了 MaterialTheme 的默认文本样式。接下来,对每个文本元素应用特定的段落样式。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
显示预览
隐藏预览

在本例中,文章的标题很短。但有时,一篇文章的标题很长,我们不希望过长的标题影响应用的外观。尝试更改第一个文本元素。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
显示预览
隐藏预览

配置文本元素,将长度上限设置为 2 行。如果文本很短,不超过此限制,则此设置没有影响;但如果文本过长,显示的文本就会被自动截短。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
显示预览
隐藏预览
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
显示预览
隐藏预览

大功告成

太棒了!您了解了 Compose 的基础知识。

您学习了以下内容:

  • 定义可组合函数
  • 使用 Column 并设置 Column 的样式,以改善布局
  • 根据 Material Design 原则设置应用的样式

如果您想深入了解其中的一些步骤,请浏览以下资源。

继续学习

衔接课程

查看我们精选的 Codelab 和视频衔接课程,能够帮助您学习和掌握 Jetpack Compose。

指南

仔细阅读文档,详细了解本教程中提及的 Jetpack Compose API。

示例

这些示例应用演示了如何使用强大的 Compose 功能,可以激发您的灵感。