行业知识
Android的Compose
Jetpack Compose 是用于构建原生 Android 界面的新工具包,无需修改任何 XML 布局,也不需要使用布局编辑器。相反,只需调用可组合函数来定义所需的元素,Compose 编译器即会完成后面的所有工作。
简而言之,使用Compose,不再需要xml编写页面。
可组合函数(Composable function)
Compose是围绕可组合函数构建的,只需要描述应用界面的外观并提供数据依赖,而不必关注界面的构建过程(如初始化元素、将其附加到父项等)。而创建Composable function,只需要添加注解@Composable到函数名称前。
首先,我们构建创建一个应用:ComposeTutorial。在AS中选择Empty Activity创建。
添加文本元素
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent {//在此处定义布局 //此处的Text方法是由Compose界面库定义的文本Composable function Text("Hello world!") } }} setContent块定义了activity的布局,在此处我们添加了Text即“Hello World!”。
自定义可组合函数
如果需要将一个函数转换为Composable function,我们需要添加注解“@Composable”。
修改MainActivity:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard("Android")//使用我们自定义的Composable function } }}@Composable//添加注解,使该函数成为Composable functionfun MessageCard(name: String) { Text(text = "Hello $name!")} 在AS中预览Composable function
借助@Preview注解,可以在AS中预览Composable function,无需安装到设备或虚拟器中。
唯一要求是该注解不能用于接收参数的函数中,因此在MainActivity新增如下函数:
@Preview@Composablefun PreviewMessageCard() { MessageCard("Android")} 在重新构建后,该函数没有被调用,应用本身不会改变,但是AS对于所有添加了@Preview注解的界面元素可以进行预览,点击如下两个按钮之一即可:
布局(Layout)
在此处我们实现一个简单的聊天界面,显示发送者和消息内容,点击消息时可缩放。
添加多个文本
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { //向Composable function中传入发送者名称和消息内容 MessageCard(Message("Android", "Jetpack Compose")) } }}//新建一个Message类,包含消息发送者和消息内容data class Message(val author: String, val body: String)@Composablefun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body)}@Preview@Composablefun PreviewMessageCard() { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") )} 这段代码会在内容视图中创建两个文本元素。不过,由于未提供有关如何排列这两个文本元素的信息,因此它们会相互重叠,使文本无法阅读。
使用Column、Row、Box
Column,直译为“圆柱体、长列”。
使用该函数修改MessageCard,可以垂直排列元素,使其不再重叠文本。
@Composablefun MessageCard(msg: Message) { Column { Text(text = msg.author) Text(text = msg.body) }} 同样的,可以使用Row函数水平排列元素,而使用Box函数可以堆叠元素。
添加图片元素
使用Resource Manager从照片库中导入图片,修改MessageCard:
@Composablefun MessageCard(msg: Message) { //使用Row方法,水平排列图片和消息 Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } } 配置布局(Modifier)
使用修饰符(Modifier)实现。
Compose中的每一个组件都具有Modifier属性,通过他我们可以设置组件的大小、间距、外观,甚至添加互动事件,如点击、触摸事件。
@Composablefun MessageCard(msg: Message) { //在消息周围添加8dp的距离 Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", modifier = Modifier //设置图片大小 .size(40.dp) //将图片修剪为圆形 .clip(CircleShape) ) //在图片和消息之间添加一个水平的空间(Spacer),间距为8dp Spacer(modifier = Modifier.width(8.dp)) Column { Text(text = msg.author) // 在发送者和消息内容之间添加一个垂直的空间 Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } }} Material Design
Compose 旨在支持 Material Design 原则。它的许多界面元素都原生支持 Material Design。
使用
Jetpack Compose 原生提供 Material Design 3 及其界面元素的实现。我们使用 Material Design 样式改进MessageCard可组合项的外观。
在创建ComposeTutorial项目时,会同时创建一个名为“ComposeTutorialTheme”的Material主题,和一个来自Material Design 3的“Surface”。我们将用到这两位来封装MessageCard函数。
Material Design是围绕Color、Typography、Shape来构建的,我们将逐一添加这些元素。
修改MainActivity:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { //使用ComposeTutorialTheme和Surface封装MessageCard函数 ComposeTutorialTheme { //将给定的组件或布局填满父容器的界面 Surface(modifier = Modifier.fillMaxSize()) { MessageCard(Message("Android", "Jetpack Compose")) } } } }}@Preview@Composablefun PreviewMessageCard() { //在@Preview中也要封装,沿用应用主题中定义的样式,保持统一 ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!") ) } }} 颜色
@Composablefun 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, //通过MaterialTheme.colorScheme,使用已封装主题中的颜色设置样式 color = MaterialTheme.colorScheme.secondary ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) } }} Typography(排版)
MaterialTheme 中提供了 Material Typography 样式,只需将其添加到 Text 可组合项中即可。
@Composablefun 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(形状)
@Composablefun 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中,此时可自定义消息详情的大小、形状等布局 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颜色、文本和背景时,系统会自动适应深色背景。
//可以在文件中以单独函数的形式创建多个预览,也可以向同一个函数中添加多个注解@Preview(name = "Light Mode")@Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, name = "Dark Mode")@Composablefun PreviewMessageCard() { ComposeTutorialTheme { Surface { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) } }} 列表和动画
创建消息列表(LazyColumn、LazyRow)
LazyColumn、LazyRow是Compose常见的两个组件,他们的优点是可以延迟加载。他们只会加载当前可见的列表项,并在滚动时动态回收其他项,这使得他们适用于展示大量数据或无限滚动的列表。
在使用中,会包含一个items子项。他接受LIst作为参数,并且其lambda会接收到参数。系统会针对提供的List的每个项调用此lambda。
@Composablefun Conversation(messages: List) { LazyColumn { //items子项 items(messages) { message -> MessageCard(message) } }}@Preview@Composablefun PreviewConversation() { ComposeTutorialTheme { Conversation(SampleData.conversationSample) }} 在展开消息时显示动画效果
为了存储某条消息是否已展开,我们使用remember和mutableStateOf函数。
可组合函数可以使用remember将本地状态存储到内存中,并跟踪传递给mutableStateOf的值的变化。而mutableStateOf函数可以在可组合函数内部创建一个可变的状态,并将其与UI组件进行绑定。当状态值发生变化时,Compose会自动重绘相关的组件。
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposeTutorialTheme { Conversation(SampleData.conversationSample) } } }}@Composablefun 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)) // 在此变量中,跟踪当前消息是否展开 var isExpanded by remember { mutableStateOf(false) } // 当点击该消息时,改变展开状态 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), // 若展开,全部显示;不展开,最大显示一行 maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } }} 注:需要添加以下导入内容才能正确使用 Kotlin 的委托属性语法(by 关键字):
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
使用by关键字是避免每次都访问value属性的好方法。
同时需要注意的是,我们应该只在可组合函数的作用域之外改变状态。这是因为可组合项可以频繁运行、且以任何顺序执行。此处我们可以在clickable中修改isExpanded的值,是因为clickable不是可组合函数。
除了remember关键字,我们还可以使用rememberSaveable,它与前者类似,但存储的值可以在重新创建activity和进程后保存下来。需要注意的是,rememberSaveable适用于UI状态,如购物车的商品数量或选定的标签页,但不适用于过渡动画状态等。
同时我们可以加入颜色,在消息缩放时改变消息的颜色。但不能只是简单的改变消息的背景颜色,我们应当加入动画,使得变化时逐步更改。
相同的,在点击缩放时,我们也可以加上动画,使得缩放更加顺滑。
@Composablefun 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)) var isExpanded by remember { mutableStateOf(false) } // 该变量会逐步更新颜色,animateColorAsState函数实现颜色之间的过渡动画效果 val surfaceColor by animateColorAsState( if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface, ) 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, color = surfaceColor, //加入animateContentSize,给缩放加入动画,更加顺滑 modifier = Modifier.animateContentSize().padding(1.dp) ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } }}