第16章 主题与样式:MaterialTheme、ColorScheme、Typography、Shapes、动态主题、暗黑模式适配
说实话,主题和样式这块,是很多Android开发者容易忽视的环节。我记得刚入行那会儿,总觉得能跑起来就行,颜色随便写死,字体大小直接写个16sp完事。结果呢?后来产品说要加暗黑模式,我差点没把键盘砸了——几百个地方要改颜色,那酸爽,至今难忘。
今天我们就来彻底搞定Jetpack Compose的主题系统。你想想看,一套好的主题架构,能让你的App从「能用」变成「好用」,从「杂乱无章」变成「井井有条」。
16.1 MaterialTheme 是什么?
MaterialTheme 是 Compose 里最顶层的主题容器。说白了,它就是整个App的「风格总管」。你所有的颜色、字体、形状,都得通过它来统一管理。
我个人习惯把 MaterialTheme 放在应用的最外层,像这样:
@Composable
fun MyApp() {
MaterialTheme(
colorScheme = lightColorScheme(),
typography = Typography(),
shapes = Shapes()
) {
// 所有子组件都能访问到主题
MainScreen()
}
}
嗯,这里要注意:MaterialTheme 的三个参数——colorScheme、typography、shapes——分别对应颜色、字体和形状。你传什么进去,整个App就用什么风格。
16.2 ColorScheme:颜色的「调色盘」
ColorScheme 是 Material Design 3 的颜色系统。它定义了一整套语义化的颜色变量,比如 primary、secondary、background 这些。
我在项目中遇到过最头疼的事,就是设计师给了个「五彩斑斓的黑」。后来我学乖了,直接跟设计师对齐 Material 3 的配色规范,双方都省心。
| 颜色变量 | 用途 | 示例 |
|---|---|---|
| primary | 主色调,用于主要按钮、选中状态 | #1565C0 |
| secondary | 辅助色,用于次要元素 | #7C4DFF |
| background | 页面背景色 | #FAFAFA |
| surface | 卡片、弹窗等表面颜色 | #FFFFFF |
| error | 错误提示色 | #B00020 |
| onPrimary | 主色调上的文字/图标颜色 | #FFFFFF |
自定义 ColorScheme 其实很简单:
val MyColorScheme = lightColorScheme(
primary = Color(0xFF1565C0),
secondary = Color(0xFF7C4DFF),
background = Color(0xFFF5F5F5),
surface = Color(0xFFFFFFFF),
error = Color(0xFFB00020),
onPrimary = Color.White,
onSecondary = Color.White
)
16.3 Typography:字体的「尺子」
Typography 定义了从大标题到小标签的完整字体层级。Material 3 提供了 13 种文本样式,从 displayLarge 到 labelSmall。
我记得有一次,产品经理说「这个标题再大一点,那个正文再小一点」,改来改去没完没了。后来我直接把 Typography 定义好,告诉他「所有标题都用 headlineMedium,正文用 bodyLarge」,从此天下太平。
val MyTypography = Typography(
displayLarge = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp
),
headlineMedium = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 28.sp,
lineHeight = 36.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)
16.4 Shapes:组件的「棱角」
Shapes 定义了组件圆角的大小。Material 3 把形状分为 small、medium、large 三个等级。
我曾经在一个项目里,所有按钮都用 8dp 圆角,卡片用 12dp,底部弹窗用 16dp。后来设计师说「统一改成 12dp 吧」,我只需要改一行代码——这就是 Shapes 的威力。
val MyShapes = Shapes(
small = RoundedCornerShape(4.dp), // 用于小按钮、标签
medium = RoundedCornerShape(8.dp), // 用于卡片、对话框
large = RoundedCornerShape(16.dp) // 用于底部弹窗、大卡片
)
你还可以用 CutCornerShape 做切角效果,或者用 AbsoluteRoundedCornerShape 做不对称圆角。不过我个人建议,除非设计有特殊要求,否则用 RoundedCornerShape 就够了。
16.5 动态主题:让用户自己选颜色
动态主题是 Material 3 的一大亮点。它可以根据用户的壁纸或系统设置,自动生成一套配色方案。
实现起来其实不复杂:
@Composable
fun DynamicTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = MyTypography,
shapes = MyShapes,
content = content
)
}
这里有个坑,我踩过:dynamicColor 只在 Android 12+ 上有效。低版本你得 fallback 到自定义配色。别直接拿 dynamicColorScheme 给所有版本用,会 crash 的。
16.6 暗黑模式适配:不只是「反色」
暗黑模式不是简单地把白色变成黑色。你得考虑对比度、可读性、品牌色的暗色表现。
我建议的做法是:
- 用
isSystemInDarkTheme()检测系统主题 - 分别定义
LightColorScheme和DarkColorScheme - 暗色模式下,primary 颜色要降低饱和度,避免刺眼
- surface 不要用纯黑(#000000),用深灰(#121212)更舒适
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF1565C0),
secondary = Color(0xFF7C4DFF),
background = Color(0xFFF5F5F5),
surface = Color(0xFFFFFFFF),
onPrimary = Color.White,
onSecondary = Color.White
)
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFF90CAF9), // 暗色下用浅蓝,更柔和
secondary = Color(0xFFB39DDB),
background = Color(0xFF121212),
surface = Color(0xFF1E1E1E),
onPrimary = Color(0xFF003C8F),
onSecondary = Color(0xFF4A148C)
)
16.7 在组件中使用主题
定义好主题后,怎么在组件里用呢?用 MaterialTheme.colorScheme、MaterialTheme.typography、MaterialTheme.shapes 就能拿到当前主题的值。
@Composable
fun MyCard() {
val colors = MaterialTheme.colorScheme
val typography = MaterialTheme.typography
val shapes = MaterialTheme.shapes
Card(
modifier = Modifier.padding(16.dp),
shape = shapes.medium,
colors = CardDefaults.cardColors(
containerColor = colors.surface
)
) {
Text(
text = "Hello, Theme!",
style = typography.headlineMedium,
color = colors.onSurface
)
}
}
你看,所有颜色、字体、形状都来自主题。哪天产品说要换主题色,你只需要改 ColorScheme 的定义,所有组件自动更新。这就是 Compose 主题系统的魅力。
好了,这一章的内容就到这里。主题和样式是 Compose 开发的基石,花时间把这块理清楚,后面的开发会顺畅很多。记住:好的主题架构,能让你的 App 既好看又好维护。
公众号:蓝海资料掘金营,微信deep3321