Message Card
项目描述:simple UI component with declarative functions
地址:https://developer.android.com/develop/ui/compose/tutorial
Section1:Template
SubSection1:Single text
Step1: 添加文本元素
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello world!")
}
}
}
Step2: 将文本元素抽象成 MessageCard 函数,并使用 @Preview 注解预览效果
// ...
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MessageCard("Android")
}
}
}
@Composable
fun MessageCard(name: String) {
Text(text = "Hello $name!")
}
@Preview
@Composable
fun PreviewMessageCard() {
MessageCard("Android")
}
这一小节我们注意到三点:
首先,是 Text() 函数的使用,它通过接收一个String参数,可以创建一个 Text 元素.接着,是将元素抽取成Composable函数 MessageCard(name: String).最后,通过Preview来进行预览上面我们抽取出来的函数
SubSection2:Multiple texts
Step1: 自定义 Message 数据类,包含两个String变量
Step2: 修改MessageCard 函数,接受 Message 变量并展示
data class Message(val author: String, val body: String)
@Composable
fun MessageCard(msg: Message) {
Text(text = msg.author)
Text(text = msg.body)
}
Summary
这一小节中,我们定义了一个数据类(data class) ,命名为Message,参数为两个 String 变量,author和body,修改MessageCard使其接收并创建两个 Text 元素。
此时,这两个文本会重叠在一起,这个问题我们会在下一节中解决。
这两个小节中,我们把项目的骨架搭好了.后面会在这个骨架上继续进行开发,进行修改样式、添加图片元素等操作.做项目就像搭积木,要先把底层的搭好,才能搭上层的.
Section2:Layouts
本节中,我们将修改文本布局,添加图片元素,并引入MD3改善组件外观。
SubSection1:Modifier
Step1: 使用 Column 包裹多列文本[^Column, Row, Box]
Step2: 使用 Image 添加图片元素
Step3: 使用 modifier 自定义元素的样式,包括:
- 图片:使用到的 Modifier 包括尺寸(size),裁剪形状(clip)
- Spacer:修改元素间空间宽高,使用到的 Modifier 包括水平宽度(width)和垂直高度(height)
- Row:使用到 Modifier 为 padding,作用为增加填充空间,项目P01的Iteration2中也有用到
@Composable
fun MessageCard(msg: Message) {
// Add padding around our message
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.momoer),
contentDescription = "Contact profile picture",
modifier = Modifier
// Set image size
.size(40.dp)
// Clip image to be shaped as a circle
.clip(CircleShape)
)
// Add a horizonal 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)
}
}
}
Issue1: 图片导入/加载出错
这里出现了两个问题,我们来一一解析。
问题1:IDE中 Resource Manager 无法导入图片
原因:图片路径或名称中含有中文、数字或特殊字符。Android Studio无法导入路径中含中文或特殊字符的文件。
问题2:Preview中图片无法显示
原因:图片被放在了drawable-hdr 文件夹中,其属于资源限定符目录。这与Android资源加载机制相关,参见讨论一。1
Troubleshoot1:图片导入出错排查流程
| 优先级 | 排查步骤 | 说明 |
|---|---|---|
| 1 | 检查文件名 | 必须全小写 + 下划线,无空格/特殊字符 |
| 2 | 检查文件路径 | 放在 drawable 基础目录,不要放限定符文件夹 |
| 3 | Clean + Rebuild | 解决IDE的缓存问题 |
Summary
在本节中,我们首次接触了图片元素(Image),包括如何导入图片,如何修改图片尺寸。温习了P01中曾接触过的Modifier,未来还会多次接触这个Compose中的核心概念,需要做好心理准备。
SubSection2:Material3
使用MD3(Material Design3)风格改进
MessageCard的外观(appearance)。MD3三大核心:
Color、Typography、Shape。这三个部分,这一节都会涉及到。
Step1:使用ComposeTutorialTheme{}包裹Surface{}再包裹MessageCard()
Insight
思考:为什么不能直接ComposeTutorialTheme{} 包裹MessageCard()呢?
原因:ComposeTutorialTheme{} 和 Surface{},它们的职责完全不同。前者只负责主题配置,后置负责提供实际的视觉容器。如果没有Surface{},组件将会缺失背景颜色。
反面案例:
// ❌ 这样写会导致背景是透明的,看不到内容
ComposeTutorialTheme {
Column { // 这个Column没有背景色
Text("Hello") // 文字会显示,但没有容器背景
}
}
MaterialTheme单例对象
import androidx.compose.material3.MaterialTheme
核心机制: Compose 的 CompositionLocal,实现隐式传参
功能:
-
MaterialTheme.colorScheme可获取当前主题的完整颜色系统,返回的对象类型为ColorScheme,内部包括主题的多个颜色角色,如primary(主色)、secondary(次要色)、tertiary(第三色)等 -
MaterialTheme.typography调整文本框样式 -
MaterialTheme.shapes可决定当前容器的形状。- 它定义了MD3中不同尺寸组件的形状系统,返回的是
Shapes对象。 - 默认为圆角,还可以自定义为其他形状,如切角(CutCornerShape)、圆形(CircleShape)等。
- 它定义了MD3中不同尺寸组件的形状系统,返回的是
示例代码:
Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
Text(
text = msg.body,
modifier = Modifier.padding(all = 4.dp),
style = MaterialTheme.typography.bodyMedium
)
}
- 利用
Configuration.UI_MODE_NIGHT_YES在@Preview中设置黑夜模式:- 其中主题的亮暗色都由文件
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!")
)
}
}
}
Section3:Lists and animations
本节中,我们将会接触如下知识:
- List的创建方法
- 简单动画的引入方法
- 如何让点击事件触发组件背景颜色改变的方法。
Step1:使用 LazyColumn 或 LazyRow + Lambda表达式,实现多条消息逐条展示。
注意:Compose DSL2中,Lambda表达式是必须且最简洁的写法。
示例:
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageCard(message)
}
}
}
Step2:添加折叠和展开动画效果
使用函数 remember 和 mutableStateOf 记录状态(state)
重点:recomposition机制
示例代码:
// ...
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
)
}
}
}
}
Step3:为点击消息添加背景颜色
利用 isExpanded 和 clickable Modifier 处理点击事件
// ...
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
)
}
}
}
}
Summary
老实说这一节的内容是最难懂,但课程里讲的最少的。后面需要结合其他项目进一步熟悉。
General Summary
这个项目的代码量不大,但难懂的概念有一箩筐。由于它的Tutorial性质,导致注定不会讲太深。不过嘛,知识积累讲求一个循序渐进,计算机代码也是,先会模仿,后面再去加深理解。
Annotation
[^Column, Row, Box]: Jetpack Compose 里的三大核心布局组件。本身不渲染具体的视觉内容,而是专门决定他们内部的子组件如何排版和堆叠。Android Studio中被称为Widget。
The
Columnfunction lets you arrange elements vertically. You can useRowto arrange items horizontally andBoxto stack elements.
评论