课时 1:可组合函数
Jetpack Compose 基于可组合函数构建。这些函数允许您通过描述 UI 的外观并提供数据依赖项以编程方式定义应用的 UI,而不是专注于 UI 构造过程(初始化元素、将其附加到父元素等)。要创建可组合函数,只需在函数名称前添加 @Composable
注解。
添加文本元素
首先,下载最新版本的 Android Studio,并通过选择**新建项目**创建应用,然后在**手机和平板电脑**类别下选择**空活动**。将应用命名为**ComposeTutorial**,然后点击**完成**。默认模板已包含一些 Compose 元素,但在此教程中,您将逐步构建它。
首先,通过在 onCreate
方法中添加文本元素来显示“Hello world!”文本。您可以通过定义内容块并调用 Text
可组合函数来实现。 setContent
块定义了活动的布局,可在其中调用可组合函数。可组合函数只能从其他可组合函数中调用。
Jetpack Compose 使用 Kotlin 编译器插件将这些可组合函数转换为应用的 UI 元素。例如,由 Compose UI 库定义的 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!") } } }
定义可组合函数
要使函数可组合,请添加 @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!") }
在 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") }
重新构建您的项目。应用本身不会更改,因为新的 PreviewMessageCard
函数在任何地方都没有被调用,但 Android Studio 会添加一个预览窗口,您可以通过点击拆分(设计/代码)视图来展开该窗口。此窗口显示由标记有 @Preview
注解的可组合函数创建的 UI 元素的预览。要随时更新预览,请点击预览窗口顶部的刷新按钮。
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!") } } }
// ... 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!") }
// ... import androidx.compose.ui.tooling.preview.Preview @Composable fun MessageCard(name: String) { Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard() { MessageCard("Android") }
添加多个文本
到目前为止,您已经构建了第一个可组合函数并进行了预览!要发现更多 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!") ) }
此代码在内容视图中创建了两个文本元素。但是,由于您没有提供任何有关如何排列它们的信息,因此文本元素彼此叠加,导致文本无法读取。
添加图像元素
// ... 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) } } }
配置你的布局
你的消息布局具有正确的结构,但其元素没有很好地间隔,并且图像太大!为了装饰或配置一个可组合函数,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) } } }
// ... 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!") ) }
// ... import androidx.compose.foundation.layout.Column @Composable fun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) } }
// ... 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) } } }
// ... 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) } } }
第 3 课:Material Design
Compose 构建为支持 Material Design 原则。它的许多 UI 元素都开箱即用地实现了 Material Design。在本课中,你将使用 Material Design 组件为你的应用设置样式。
使用 Material Design
你的消息设计现在有了布局,但它看起来还不够好。
Jetpack Compose 提供了 Material Design 3 及其 UI 元素的开箱即用实现。你将使用 Material Design 样式改进我们 MessageCard
可组合函数的外观。
首先,使用项目中创建的 Material 主题 ComposeTutorialTheme
以及 Surface
将 MessageCard
函数包装起来。在 @Preview
和 setContent
函数中都这样做。这样做将允许你的可组合函数继承应用主题中定义的样式,确保整个应用的一致性。
Material Design 建立在三个支柱之上:Color
、Typography
和 Shape
。你将逐一添加它们。
注意:空 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!") ) } } }
颜色
使用 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) } } }
排版
Material 排版样式在 MaterialTheme
中可用,只需将它们添加到 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 ) } } }
形状
使用 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 ) } } } }
启用深色主题
深色主题(或夜间模式)可以启用以避免在晚上出现明亮的显示屏,或者仅仅是为了节省设备电量。由于 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!") ) } } }
浅色和深色主题的颜色选择在 IDE 生成的 Theme.kt
文件中定义。
到目前为止,你已经创建了一个消息 UI 元素,它显示一个图像和两个具有不同样式的文本,并且它在浅色和深色主题中都看起来不错!
// ... 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!") ) } } }
// ... 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!") ) } } }
// ... 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) } } }
// ... @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 ) } } }
// ... 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 ) } } } }
// ... 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!") ) } } }
创建消息列表
只有一个消息的聊天感觉有点孤独,所以我们将更改对话以包含多条消息。你需要创建一个 Conversation
函数来显示多条消息。对于这种情况,使用 Compose 的 LazyColumn
和 LazyRow
。这些可组合函数仅呈现屏幕上可见的元素,因此它们被设计为对于长列表非常高效。
在此代码片段中,你可以看到 LazyColumn
有一个 items
子项。它以 List
作为参数,其 lambda 接收一个我们命名为 message
的参数(我们可以将其命名为任何我们想要的内容),它是 Message
的实例。简而言之,此 lambda 为提供的 List
的每个项目调用一次。将 示例数据集 复制到你的项目中,以帮助快速引导对话。
// ... 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) } }
在展开时为消息设置动画
对话变得越来越有趣。是时候玩转动画了!你将添加展开消息以显示更长消息的功能,同时为内容大小和背景颜色设置动画。为了存储此本地 UI 状态,你需要跟踪消息是否已展开。为了跟踪此状态更改,你必须使用 remember
和 mutableStateOf
函数。
可组合函数可以通过使用 remember
将本地状态存储在内存中,并跟踪传递给 mutableStateOf
的值的更改。使用此状态的可组合函数(及其子项)将在值更新时自动重新绘制。这称为 重新组合。
通过使用 Compose 的状态 API(如 remember
和 mutableStateOf
),对状态的任何更改都会自动更新 UI。
注意:你需要添加以下导入才能正确使用 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
修饰符来处理可组合函数上的点击事件。你将不会仅仅切换 Surface
的背景颜色,而是通过逐渐修改其值从 MaterialTheme.colorScheme.surface
到 MaterialTheme.colorScheme.primary
,反之亦然,来为背景颜色设置动画。为此,你将使用 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) } }
// ... 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 行代码** 中完成!
以下是到目前为止你学到的内容
- 定义可组合函数
- 在可组合函数中添加不同的元素
- 使用布局可组合函数构建 UI 组件
- 使用修饰符扩展可组合函数
- 创建高效的列表
- 跟踪状态并修改它
- 在可组合函数上添加用户交互
- 在展开消息时为其设置动画
如果你想更深入地了解其中的一些步骤,请探索以下资源。