第 1 课:可组合函数
Jetpack Compose 是围绕可组合函数构建的。这些函数可让您以程序化方式定义应用的界面,只需描述应用界面的外观并提供数据依赖项,而不必关注界面的构建过程(初始化元素、将其附加到父项等)。如需创建可组合函数,只需将 @Composable
注解添加到函数名称中即可。
添加文本元素
首先,下载最新版本的 Android Studio,然后通过以下方法创建应用:选择 New Project,然后在 Phone and Tablet 类别下,选择 Empty Activity。 将应用命名为 ComposeTutorial,然后点击 Finish。默认模板已包含一些 Compose 元素,但在此教程中,您要逐步进行构建。
首先,通过在 onCreate
方法内添加文本元素,让系统显示“Hello world!”文本。可以通过定义内容块并调用 Text
可组合函数来实现此目的。setContent
块定义了 activity 的布局,我们会在其中调用可组合函数。可组合函数只能从其他可组合函数调用。
Jetpack Compose 使用 Kotlin 编译器插件将这些可组合函数转换为应用的界面元素。例如,由 Compose 界面库定义的 Text
可组合函数会在屏幕上显示一个文本标签。
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.Text class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hello world!") } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-02.png?authuser=5&hl=cs)
定义可组合函数
如需使函数成为可组合函数,请添加 @Composable
注解。如需尝试此操作,请定义一个 MessageCard
函数并向其传递一个名称,然后该函数就会使用该名称配置文本元素。
// ... import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android") } } } @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=5&hl=cs)
在 Android Studio 中预览函数
借助 @Preview
注解,您可以在 Android Studio 中预览可组合函数,而无需构建应用并将其安装到 Android 设备或模拟器中。该注解必须用于不接受参数的可组合函数。因此,您无法直接预览 MessageCard
函数,而是需要创建另一个名为 PreviewMessageCard
的函数,由该函数使用适当的参数调用 MessageCard
。请在 @Composable
上方添加 @Preview
注解。
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=5&hl=cs)
重新构建您的项目。由于新的 PreviewMessageCard
函数未在任何位置受到调用,因此应用本身不会更改,但 Android Studio 会添加一个预览窗口,您可以点击拆分(设计/代码)视图以展开此窗口。此窗口会显示由可组合函数创建的标有 @Preview
注解的界面元素的预览。任何时候,如需更新预览,请点击预览窗口顶部的刷新按钮。
![Android Studio 中可组合函数的预览](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-04.png?authuser=5&hl=cs)
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.Text class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hello world!") } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-02.png?authuser=5&hl=cs)
// ... import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android") } } } @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=5&hl=cs)
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-03.png?authuser=5&hl=cs)
![Android Studio 中可组合函数的预览](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson1-04.png?authuser=5&hl=cs)
添加多个文本
到目前为止,您已经构建了第一个可组合函数和预览!为探索更多 Jetpack Compose 功能,您将构建一个简单的消息界面,界面上显示可以展开且具有动画效果的消息列表。
首先,通过显示消息发送者和消息内容,使消息可组合项更丰富。您需要先将可组合参数更改为接受 Message
对象(而非 String
),然后在 MessageCard
可组合项中添加另一个 Text
可组合项。请务必同时更新预览。
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-02.png?authuser=5&hl=cs)
这段代码会在内容视图中创建两个文本元素。不过,由于您未提供有关如何排列这两个文本元素的信息,因此它们会相互重叠,使文本无法阅读。
添加图片元素
下面我们来添加消息发送者的个人资料照片,以丰富消息卡片。使用 Resource Manager 从照片库中导入图片,或使用这张图片。添加一个 Row
可组合项,以实现良好的设计结构,并向该可组合项中添加一个 Image
可组合项。
// ... import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.ui.res.painterResource @Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-05.png?authuser=5&hl=cs)
配置布局
您的消息布局拥有良好的结构,但其元素的间距不合理,并且图片过大!为了装饰或配置可组合项,Compose 使用了修饰符。通过修饰符,您可以更改可组合项的大小、布局、外观,还可以添加高级互动,例如使元素可点击。您可以将这些修饰符链接起来,以创建更丰富的可组合项。您将使用其中一些修饰符来改进布局。
// ... import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable fun MessageCard(msg: Message) { // Add padding around our message Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier // Set image size to 40 dp .size(40.dp) // Clip image to be shaped as a circle .clip(CircleShape) ) // Add a horizontal space between the image and the column Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // Add a vertical space between the author and message texts Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-06.png?authuser=5&hl=cs)
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-02.png?authuser=5&hl=cs)
![两个重叠的 Text 可组合项的预览](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-03.png?authuser=5&hl=cs)
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-04.png?authuser=5&hl=cs)
// ... import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.ui.res.painterResource @Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-05.png?authuser=5&hl=cs)
// ... import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp @Composable fun MessageCard(msg: Message) { // Add padding around our message Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier // Set image size to 40 dp .size(40.dp) // Clip image to be shaped as a circle .clip(CircleShape) ) // Add a horizontal space between the image and the column Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // Add a vertical space between the author and message texts Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson2-06.png?authuser=5&hl=cs)
第 3 课:Material Design
Compose 旨在支持 Material Design 原则。它的许多界面元素都原生支持 Material Design。在本课中,您将使用 Material Design widget 来设置应用的样式。
使用 Material Design
您的消息设计现在已有布局,但看上去还不是特别理想。
Jetpack Compose 原生提供 Material Design 3 及其界面元素的实现。您将使用 Material Design 样式改进 MessageCard
可组合项的外观。
首先,使用在您的项目中创建的 Material 主题 ComposeTutorialTheme
和 Surface
来封装 MessageCard
函数。
在 @Preview
和 setContent
函数中都需要执行此操作。这样一来,可组合项即可沿用应用主题中定义的样式,从而在整个应用中确保一致性。
Material Design 是围绕 Color
、Typography
、Shape
这三大要素构建的。您将逐一添加这些要素。
注意:Empty Compose Activity 模板会为您的项目生成默认主题,使您能够自定义 MaterialTheme
。
如果您为项目指定的名称不是 ComposeTutorial,可以在 ui.theme
子软件包的 Theme.kt
文件中找到您的自定义主题。
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Surface(modifier = Modifier.fillMaxSize()) { MessageCard(Message("Android", "Jetpack Compose")) } } } } } @Preview @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!") ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-02.png?authuser=5&hl=cs)
颜色
通过 MaterialTheme.colorScheme
,使用已封装主题中的颜色来设置样式。您可以在需要颜色的任何位置使用主题中的这些值。此示例使用动态主题颜色(由设备偏好设置定义)。
如需更改此设置,您可以在 MaterialTheme.kt
文件中将 dynamicColor
设为 false
。
设置标题样式,并为图片添加边框:
// ... import androidx.compose.foundation.border import androidx.compose.material3.MaterialTheme @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-03.png?authuser=5&hl=cs)
Typography(排版)
MaterialTheme
中提供了 Material Typography 样式,只需将其添加到 Text
可组合项中即可。
// ... @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.bodyMedium ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-04.png?authuser=5&hl=cs)
Shape(形状)
通过 Shape
,我们可以添加最后的“点睛之笔”。首先,将消息正文封装在 Surface
可组合项中。这样即可自定义消息正文的形状和高度。此外,还要为消息添加内边距,以改进布局。
// ... import androidx.compose.material3.Surface @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.bodyMedium ) } } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-05.png?authuser=5&hl=cs)
启用深色主题
您可以启用深色主题(或夜间模式),以避免显示屏过亮(尤其是在夜间),或者只是节省设备电量。由于支持 Material Design,Jetpack Compose 默认能够处理深色主题。使用 Material Design 颜色、文本和背景时,系统会自动适应深色背景。
您可以在文件中以单独函数的形式创建多个预览,也可以向同一个函数中添加多个注解。
添加新的预览注解并启用夜间模式。
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-06.png?authuser=5&hl=cs)
浅色和深色主题的颜色选项是在由 IDE 生成的 Theme.kt
文件中定义的。
到目前为止,您已创建了一个消息界面元素,它会以不同样式显示一张图片和两项文本,并且在浅色和深色主题下都有良好的视觉效果!
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-06.png?authuser=5&hl=cs)
// ... class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Surface(modifier = Modifier.fillMaxSize()) { MessageCard(Message("Android", "Jetpack Compose")) } } } } } @Preview @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!") ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-02.png?authuser=5&hl=cs)
// ... import androidx.compose.foundation.border import androidx.compose.material3.MaterialTheme @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-03.png?authuser=5&hl=cs)
// ... @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.bodyMedium ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-04.png?authuser=5&hl=cs)
// ... import androidx.compose.material3.Surface @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.bodyMedium ) } } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-05.png?authuser=5&hl=cs)
// ... import android.content.res.Configuration @Preview(name = "Light Mode") @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode" ) @Composable fun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-06.png?authuser=5&hl=cs)
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson3-07.png?authuser=5&hl=cs)
创建消息列表
只包含一条消息的聊天略显孤单,因此我们将更改对话,使其包含多条消息。您需要创建一个可显示多条消息的 Conversation
函数。对于此用例,请使用 Compose 的 LazyColumn
和 LazyRow
。这些可组合项只会呈现屏幕上显示的元素,因此,对于较长的列表,使用它们会非常高效。
在此代码段中,您可以看到 LazyColumn
包含一个 items
子项。它接受 List
作为参数,并且其 lambda 会收到我们命名为 message
的参数(可以随意为其命名),该参数是 Message
的实例。简而言之,系统会针对提供的 List
的每个项调用此 lambda。将示例数据集复制到您的项目中,以便快速引导对话。
// ... import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @Composable fun Conversation(messages: List<Message>) { LazyColumn { items(messages) { message -> MessageCard(message) } } } @Preview @Composable fun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson4-02.png?authuser=5&hl=cs)
在展开消息时显示动画效果
对话变得更加有趣了。是时候添加动画效果了!您将添加展开消息以显示更多内容的功能,同时为内容大小和背景颜色添加动画效果。为了存储此本地界面状态,您需要跟踪消息是否已展开。为了跟踪这种状态变化,您必须使用 remember
和 mutableStateOf
函数。
可组合函数可以使用 remember
将本地状态存储在内存中,并跟踪传递给 mutableStateOf
的值的变化。该值更新时,系统会自动重新绘制使用此状态的可组合项(及其子项)。这称为重组。
通过使用 Compose 的状态 API(如 remember
和 mutableStateOf
),系统会在状态发生任何变化时自动更新界面。
注意:您需要添加以下导入内容才能正确使用 Kotlin 的委托属性语法(by
关键字)。按 Alt+Enter 或 Option+Enter 即可添加这些内容。
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
// ... import androidx.compose.foundation.clickable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } } } @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
现在,您可以根据点击消息时消息的 isExpanded
状态,更改消息内容的背景颜色。您将使用 clickable
修饰符来处理可组合项上的点击事件。您会为背景颜色添加动画效果,使其值逐步从 MaterialTheme.colorScheme.surface
更改为 MaterialTheme.colorScheme.primary
(反之亦然),而不只是切换 Surface
的背景颜色。为此,您将使用 animateColorAsState
函数。最后,您将使用 animateContentSize
修饰符顺畅地为消息容器大小添加动画效果:
// ... import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // surfaceColor will be updated gradually from one color to the other val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface, ) // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, // surfaceColor color will be changing gradually from primary to surface color = surfaceColor, // animateContentSize will change the Surface size gradually modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
// ... import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @Composable fun Conversation(messages: List<Message>) { LazyColumn { items(messages) { message -> MessageCard(message) } } } @Preview @Composable fun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } }
![](https://developer.android.google.cn/static/develop/ui/compose/images/compose-tutorial/lesson4-02.png?authuser=5&hl=cs)
// ... import androidx.compose.foundation.clickable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } } } @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
// ... import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize @Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // surfaceColor will be updated gradually from one color to the other val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface, ) // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, // surfaceColor color will be changing gradually from primary to surface color = surfaceColor, // animateContentSize will change the Surface size gradually modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
后续步骤
恭喜,您已完成 Compose 教程!您已高效地构建了一个简单的聊天界面,该界面显示包含图片和文字的可展开动画消息列表,使用 Material Design 原则设计,添加了深色主题且具有预览功能,所有内容只需不到 100 行代码!
以下是您目前为止所学的内容:
- 定义可组合函数
- 在可组合项中添加不同的元素
- 使用布局可组合项构建界面组件
- 使用修饰符扩展可组合项
- 创建高效列表
- 跟踪状态以及修改状态
- 在可组合项上添加用户互动
- 在展开消息时显示动画效果
如果您想深入了解其中的一些步骤,请浏览以下资源。