14.2 Compose MaterialTheme 原理及拓展

733 阅读2分钟

MaterialTheme

Compose 中除了基础布局和 BasicText(这个基本上不用)之外常用的组件都在 M2/M3 依赖中,这些组件样式都基于 MaterialTheme 开发设计。

M2 包含 color、typography、shape 三个部分。

MaterialTheme(
    colors = …,
    typography = …,
    shapes = …
) {
    // app content

M3 暂时还不包含 shape,但 M3 中有基于主屏墙纸的动态颜色功能。

MaterialTheme(
    colorScheme = …,
    typography = …
    // Updates to shapes coming soon
) {
    // M3 app conten
}
// Dynamic color is available on Android 12+
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
    dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
    dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
    darkTheme -> DarkColorScheme
    else -> LightColorScheme
}

项目中用的是 M3 组件,这里我们就着重说一下 M3 主题,详情见官网

color

定义一套颜色方案 ,由设计人员提供或者使用M3 官方提供了  Material Theme Builder ,选一下 Core colors 自动生成 Light / Dark Themem ,右上角可以直接导出 kt 文件。

46700E65-95DA-490B-A97B-A1AAE31FC1BA.png

typography

M3 / M2 默认字体样式

0D7F3E31-88CB-4E91-96B9-4B69E348C26B.png

Typography 函数用来修改默认的字体样式:

val KarlaFontFamily = FontFamily(
    Font(R.font.karla_regular),
    Font(R.font.karla_bold, FontWeight.Bold)
)

val AppTypography = Typography(
    bodyLarge = TextStyle(
        fontFamily = KarlaFontFamily,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    // titleMedium, labelSmall, etc.

MaterialTheme 工作原理

我们以 CenterAlignedTopAppBar 标题颜色为例

@Composable
fun CenterAlignedTopAppBar(
    colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors()
)
@Composable
fun centerAlignedTopAppBarColors(
    titleContentColor: Color =
    TopAppBarSmallCenteredTokens.HeadlineColor.toColor()
)
internal object TopAppBarSmallCenteredTokens {
    val HeadlineColor = ColorSchemeKeyTokens.OnSurfac
}
{
    ProvideTextStyle(value = titleTextStyle) {
        CompositionLocalProvider(
            LocalContentColor provides titleContentColor.copy(alpha = titleAlpha)
            content = title
        )
    }
}

可以看出标题颜色使用主题中的 OnSurface ,将这个颜色设置给 LocalContentColor 并包裹标题组件,这样标题组件中获取的 LocalContentColor 就是主题中的 OnSurface 。再以标题是 Text 为例

val textColor = color.takeOrElse {
    style.color.takeOrElse {
        LocalContentColor.current
    }
}

字体颜色默认是 LocalContentColor 。

CompositionLocal + MaterailTheme 就是以这种方法完成了 UI 样式的统一设置。

MaterialTheme 拓展

间距也是我们常用的属性,M2/M3 中都没有这一属性,下面我们基于 CompositionLocal 来对 MaterialTheme 进行拓展。

添加数据类 Spacing 同时声明对应的 CompositionLocal

data class Spacing(
    val normalPadding:Dp = 16.dp,
    val largePadding:Dp = 28.dp,
    val smallPadding:Dp = 8.dp,
    //todo add
)

val LocalSpacing = staticCompositionLocalOf<Spacing> { error("Spacing not provide") 

使用 CompositionLocalProvider 包裹 MaterialTheme 提供 LocalSpacing 的值

    CompositionLocalProvider( LocalSpacing provides Spacing()) {
        MaterialTheme(
            colorScheme = colorScheme,
            typography = Typography
            content = content
        )
    }

仿照 MaterialTheme 定义 object WanAndroidTheme 单例添加 spacing 属性

object WanAndroidTheme{
    val colorScheme: ColorScheme
        @Composable
        @ReadOnlyComposable
        get() = MaterialTheme.colorScheme

    val typography: Typography
        @Composabl
        @ReadOnlyComposable
        get() = MaterialTheme.typography

    val shapes: Shapes
        @Composable
        @ReadOnlyComposable
        get() = MaterialTheme.shapes

    val spacing: Spacing
        @Composable
        @ReadOnlyComposable
        get() = LocalSpacing.current
}

在 Composable 函数中使用 spacing

@Composable
fun UiProfile() {
    Text(
        modifier = Modifier.padding(horizontal = WanAndroidTheme.spacing.largePadding), text = "我的"
    
}