一、Compose 布局三剑客:Column、Row 与 Box
说实话,刚接触 Compose 的时候,我最不适应的是——怎么没有 XML 了?
但用了一周后,我彻底回不去了。Compose 的布局方式,说白了就是用代码“画”界面。而 Column、Row、Box 这三个容器,就是你的画笔。
1.1 Column:纵向排列
Column 相当于传统 Android 里的 LinearLayout 方向为 vertical。子元素从上往下排。
@Composable
fun MyColumn() {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("第一行")
Text("第二行")
Button(onClick = { }) { Text("点击") }
}
}
1.2 Row:横向排列
Row 就是水平版的 Column。子元素从左往右排。
@Composable
fun MyRow() {
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Home, contentDescription = null)
Text("首页")
Switch(checked = true, onCheckedChange = { })
}
}
嗯,这里要注意:Row 的 horizontalArrangement 和 Column 的 verticalArrangement 是对应的。别搞混了。
1.3 Box:层叠布局
Box 相当于 FrameLayout。子元素可以叠在一起。我经常用它做“徽标角标”效果。
@Composable
fun BadgeBox() {
Box(modifier = Modifier.size(48.dp)) {
Icon(Icons.Default.Person, contentDescription = "用户", modifier = Modifier.fillMaxSize())
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(16.dp)
.background(Color.Red, CircleShape)
)
}
}
二、ConstraintLayout 在 Compose 中的使用
你可能会问:有了 Column/Row/Box,为什么还要用 ConstraintLayout?
我的回答是:当布局复杂到需要“相对定位”时,ConstraintLayout 就是救星。比如一个卡片里,头像在左,标题在右上,描述在右下——用 Column/Row 嵌套会很难受。
2.1 基本用法
@Composable
fun ConstraintLayoutDemo() {
ConstraintLayout(
modifier = Modifier.fillMaxSize().padding(16.dp)
) {
val (avatar, title, desc) = createRefs()
Image(
painter = painterResource(id = R.drawable.avatar),
contentDescription = null,
modifier = Modifier
.constrainAs(avatar) {
top.linkTo(parent.top)
start.linkTo(parent.start)
width = Dimension.value(48.dp)
height = Dimension.value(48.dp)
}
)
Text(
text = "标题",
modifier = Modifier.constrainAs(title) {
top.linkTo(avatar.top)
start.linkTo(avatar.end, margin = 12.dp)
}
)
Text(
text = "这是一段描述文字",
modifier = Modifier.constrainAs(desc) {
top.linkTo(title.bottom, margin = 4.dp)
start.linkTo(title.start)
}
)
}
}
2.2 链条与辅助线
ConstraintLayout 还支持链条(Chain)和辅助线(Guideline)。链条可以让一组控件均匀分布,辅助线可以按百分比定位。
val guideline = createGuidelineFromStart(0.3f) // 从左侧30%位置
Text(
text = "对齐辅助线",
modifier = Modifier.constrainAs(createRef()) {
start.linkTo(guideline)
}
)
三、Material3 主题系统
Material3 是 Google 最新的设计语言。说白了,它比 Material2 更强调个性化和动态色彩。
3.1 主题结构
// 定义颜色
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color.White,
secondary = Color(0xFF625B71),
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE)
)
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFFD0BCFF),
onPrimary = Color(0xFF381E72),
background = Color(0xFF1C1B1F),
surface = Color(0xFF1C1B1F)
)
// 应用主题
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
3.2 使用主题中的颜色
@Composable
fun ThemedText() {
Text(
text = "Hello Material3",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.headlineMedium
)
}
四、深色模式适配
深色模式不是简单的“黑底白字”。我见过很多 App 切到深色模式后,图片边缘发白、阴影消失、对比度刺眼。
4.1 自动适配
使用 Material3 的 darkColorScheme 和 lightColorScheme,系统会自动根据 isSystemInDarkTheme() 切换。
@Composable
fun MyScreen() {
val isDark = isSystemInDarkTheme()
val bgColor = if (isDark) Color(0xFF1C1B1F) else Color(0xFFFFFBFE)
Surface(color = bgColor) {
// 内容
}
}
4.2 手动覆盖
有时候用户想手动控制。我习惯用 DataStore 保存一个布尔值,然后传给主题。
var isDark by remember { mutableStateOf(false) }
MaterialTheme(
colorScheme = if (isDark) DarkColorScheme else LightColorScheme
) {
// ...
}
五、知识体系总览
下面这张图,是我梳理的本章核心脉络。你可以把它当作“地图”,随时回来对照。
六、总结
这一章我们聊了 Compose 的三大布局容器、ConstraintLayout 的灵活用法、Material3 主题系统,以及深色模式的适配技巧。
我个人觉得,Compose 最爽的地方在于——所有 UI 都是 Kotlin 代码。没有 XML 的割裂感,没有 findViewById 的繁琐。你写布局就像写业务逻辑一样自然。
嗯,下一章我们会深入 Compose 的状态管理与副作用。但那是后话了。先把今天的代码跑起来,感受一下“声明式 UI”的魅力吧。