云计算百科
云计算领域专业知识百科平台

Compose Codelab - 使用 Material 3 在 Compose 中设置主题

使用 Material 3 在 Compose 中设置主题

  • 官方文档:https://developer.android.google.cn/codelabs/jetpack-compose-theming?hl=zh_cn#0
1、配色方案
(1)基本介绍
  • 配色方案的基础是五种关键颜色,其中每种颜色都对应 Material 3 组件使用的 13 色调调色板之一

  • 每种强调色(主色、辅色、第三色)都会提供四种不同色调的兼容色,以便进行配对、定义强调效果和视觉表现

  • 中性色也分兼容的色调,用于 Surface 和背景。当这些颜色被应用于任何 Surface 时,它们对于突出文本图标也非常重要

  • 除了主色主色、辅色、第三色,其他颜色都是中性色(Neutral)

    它主要用来做背景(Background)、卡片(Surface)、文字(Text)

    例如,为了让悬浮操作按钮(FAB)成为引人注目的按钮,可以使用第三色

    (2)生成配色方案
  • 可以手动创建自定义 ColorScheme

  • 也可以使用 Material 主题构建器,并且选择导出 Compose 主题代码:https://material-foundation.github.io/material-theme-builder/

  • 生成的 Theme.kt 文件包含浅色和深色配色方案以及应用主题,同时还包含主要主题可组合函数 AppTheme

  • @Composable
    fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    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 -> darkScheme
    else -> lightScheme
    }

    MaterialTheme(
    colorScheme = colorScheme,
    typography = AppTypography,
    content = content
    )
    }

  • 将 MaterialTheme 可组合项封装在 AppTheme 函数中,接受以下参数
  • 1. useDarkTheme:此参数会与函数 isSystemInDarkTheme 相关联,用于观察系统主题设置并应用浅色或深色主题
    如果想手动让应用保持浅色或深色主题,可以将布尔值传递给 useDarkTheme

    2. content:主题将被应用到的内容

  • 在项目中正确集成了通过 Material 主题构建器生成的内容后(Color.kt、Theme.kt、Type.kt),就可以在代码中直接使用 MaterialTheme.colorScheme 来访问这些定义好的颜色
  • // Theme.kt 中的关键配置代码

    @Composable
    fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    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 -> darkScheme
    else -> lightScheme
    }

    MaterialTheme(
    colorScheme = colorScheme,
    typography = AppTypography,
    content = content
    )
    }

    (3)颜色角色
  • 主色是基础颜色,用于主要组件,例如,显眼的按钮、活动状态

  • 辅色用于界面中不太显眼的组件,例如,过滤组件

  • 第三色用于提供对比鲜明的强调色

  • 中性色用于应用中的背景和 Surface

  • (4)色调和阴影高度
  • 在界面上,高度(elevation)就是一个组件浮在背景上有多高

  • 传统做法是用阴影(shadow)来表示高度,越高,阴影越大越深

  • 色调高度是用颜色来暗示高度,而不是只靠阴影

  • 当一个组件的高度提高时,它除了阴影变重,它的背景颜色也会发生微妙变化。这个颜色变化是从主色里提取的色调叠加层

  • 简单来说,组件浮得越高,它就越染上一点主色的色调

  • @Composable
    fun TonalElevation_ShadowElevation_Demo() {
    Column(
    modifier = Modifier
    .fillMaxSize()
    .padding(16.dp),
    verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
    Surface(
    shadowElevation = 8.dp,
    tonalElevation = 0.dp,
    modifier = Modifier.fillMaxWidth()
    ) {
    Text("阴影 8.dp,无色调", modifier = Modifier.padding(16.dp))
    }

    Surface(
    shadowElevation = 0.dp,
    tonalElevation = 8.dp,
    modifier = Modifier.fillMaxWidth()
    ) {
    Text("色调 8.dp,无阴影", modifier = Modifier.padding(16.dp))
    }

    Surface(
    shadowElevation = 8.dp,
    tonalElevation = 8.dp,
    modifier = Modifier.fillMaxWidth()
    ) {
    Text("阴影 + 色调 都是 8.dp", modifier = Modifier.padding(16.dp))
    }
    }
    }

    @Preview(showBackground = true)
    @Composable
    fun TonalElevation_ShadowElevation_Demo_Preview() {
    TonalElevation_ShadowElevation_Demo()
    }

    2、动态配色
  • 使用动态颜色,算法会从用户的壁纸中派生自定义颜色,以将其应用到其应用和系统界面

  • 动态颜色适用于 Android 12 及更高版本

  • 如果动态颜色可用,可以使用 dynamicLightColorScheme 函数或 dynamicDarkColorScheme 函数设置动态配色方案

  • 如果不可用,应回退采用默认的浅色或深色的 ColorScheme

  • 状态栏的样式能根据用于为应用设置主题的配色方案进行动态变化

  • // Theme.kt 中的关键配置代码

    @Composable
    fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    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 -> darkScheme
    else -> lightScheme
    }

    MaterialTheme(
    colorScheme = colorScheme,
    typography = AppTypography,
    content = content
    )
    }

    3、排版
  • Material Design 3 定义了一个字体比例。命名和分组已简化为:显示、大标题、标题、正文、标签。每个都有大号、中号和小号

  • Compose 提供了 M3 Typography 类以及现有的 TextStyle 和 font-related 类,用以对 Material 3 字体比例进行建模

  • class Typography(
    val displayLarge: TextStyle = TypographyTokens.DisplayLarge,
    val displayMedium: TextStyle = TypographyTokens.DisplayMedium,
    val displaySmall: TextStyle = TypographyTokens.DisplaySmall,
    val headlineLarge: TextStyle = TypographyTokens.HeadlineLarge,
    val headlineMedium: TextStyle = TypographyTokens.HeadlineMedium,
    val headlineSmall: TextStyle = TypographyTokens.HeadlineSmall,
    val titleLarge: TextStyle = TypographyTokens.TitleLarge,
    val titleMedium: TextStyle = TypographyTokens.TitleMedium,
    val titleSmall: TextStyle = TypographyTokens.TitleSmall,
    val bodyLarge: TextStyle = TypographyTokens.BodyLarge,
    val bodyMedium: TextStyle = TypographyTokens.BodyMedium,
    val bodySmall: TextStyle = TypographyTokens.BodySmall,
    val labelLarge: TextStyle = TypographyTokens.LabelLarge,
    val labelMedium: TextStyle = TypographyTokens.LabelMedium,
    val labelSmall: TextStyle = TypographyTokens.LabelSmall,
    )

    Typography 把整个 App 里所有要用到的文字样式都打包好了

    例如,headlineLarge(大号大标题)、titleMedium(中号标题)、bodyLarge(大号正文)、labelSmall(小号标签)

    不需要一个一个地去设置字体大小、粗细、行高,只要定义一个全局的 Typography,整个 App 的主题就会自动应用这些样式,保证设计统一

    TextStyle 是具体的样式类,它包含了所有关于文字的细节属性,例如

    color(颜色)、fontSize(字号)、fontWeight(粗细)

    fontFamily(字体家族)、letterSpacing(字间距)、lineHeight(行高)

    Typography 里面其实就是一个一个的 TextStyle。例如,Typography.bodyLarge 本质上就是一个被预设好参数的 TextStyle 对象

    font-related 类指的是和字体相关的独立工具类

    FontFamily,定义字体,例如,衬线体、无衬线体或自定义下载字体

    FontWeight,定义粗细,例如,Bold、Light

    FontStyle,定义风格,例如,正常、斜体

  • 定义并传递给 MaterialTheme 后,使用 MaterialTheme.typography 访问当前主题的排版样式
  • val typography = Typography(
    headlineSmall = TextStyle(
    fontWeight = FontWeight.SemiBold,
    fontSize = 24.sp,
    lineHeight = 32.sp,
    letterSpacing = 0.sp
    ),
    titleLarge = TextStyle(
    fontWeight = FontWeight.Normal,
    fontSize = 18.sp,
    lineHeight = 28.sp,
    letterSpacing = 0.sp
    ),
    bodyLarge = TextStyle(
    fontWeight = FontWeight.Normal,
    fontSize = 16.sp,
    lineHeight = 24.sp,
    letterSpacing = 0.15.sp
    ),
    bodyMedium = TextStyle(
    fontWeight = FontWeight.Medium,
    fontSize = 14.sp,
    lineHeight = 20.sp,
    letterSpacing = 0.25.sp
    ),
    labelMedium = TextStyle(
    fontWeight = FontWeight.SemiBold,
    fontSize = 12.sp,
    lineHeight = 16.sp,
    letterSpacing = 0.5.sp
    )
    )

    @Composable
    fun AppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable() () -> Unit
    ) {

    ...

    MaterialTheme(
    colorScheme = colors,
    typography = typography,
    content = content
    )
    }

  • 未将排版应用于 Text 可组合项,默认情况下,所有文本都会回退到 Typography.bodyLarge
  • 4、形状
  • Compose 提供带有扩展参数的 Shapes 类来实现新的 M3 形状

  • 形状比例中包含不同大小的形状:特小、小、中、大、特大

  • class Shapes(
    val extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
    val small: CornerBasedShape = ShapeDefaults.Small,
    val medium: CornerBasedShape = ShapeDefaults.Medium,
    val large: CornerBasedShape = ShapeDefaults.Large,
    val extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
    )

  • 定义形状后,并传递给 MaterialTheme 后,使用 MaterialTheme.shape 将形状应用于组件
  • @Composable
    fun AppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable() () -> Unit
    ) {

    ...

    MaterialTheme(
    colorScheme = colors,
    typography = typography,
    shapes = shapes,
    content = content
    )
    }

    val shapes = Shapes(
    extraSmall = RoundedCornerShape(4.dp),
    small = RoundedCornerShape(8.dp),
    medium = RoundedCornerShape(16.dp),
    large = RoundedCornerShape(24.dp),
    extraLarge = RoundedCornerShape(32.dp)
    )

  • 还可以使用另外两种形状,RectangleShape 和 CircleShape
  • 5、强调方式
  • 强调,即突出显示,例如,标题要比正文显眼

  • M3 提供了两种方式做强调

  • 用颜色组合,例如,深色背景配浅色文字,浅色背景配深色文字

    用字体粗细,例如,标题用粗体,正文用细体


    补充学习

    1、切换状态栏图标颜色

    val view = LocalView.current
    if (!view.isInEditMode) {
    SideEffect {
    val window = (view.context as Activity).window
    WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
    }
    }

    (1)获取当前视图

    val view = LocalView.current

    • LocalView.current 获取当前 Compose 界面依附的 View 对象,即 Android 传统的 View 系统实例
    (2)避免预览崩溃

    if (!view.isInEditMode) { ... }

  • isInEditMode 用于判断当前是否处于 Android Studio 的预览模式

  • 在预览模式下没有真实的 Activity 和 Window,如果执行下面的代码会抛出异常

  • 这个判断确保逻辑只在真机或模拟器运行时生效

  • (3)副作用执行

    SideEffect { ... )

  • SideEffect 是 Compose 中的一个副作用 API

  • SideEffect 保证里面的代码每次重组完成后都会执行

  • 修改状态栏属于外部命令式操作,要确保在 UI 绘制到屏幕上之后立即同步执行,避免界面闪烁

  • (4)切换状态栏图标颜色

    val window = (view.context as Activity).window
    WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme

  • 通过 view.context 强制转为 Activity,拿到当前窗口对象

  • 通过 WindowCompat.getInsetsController 方法获取 AndroidX 兼容库提供的控制器,用于管理状态栏和导航栏的显示

  • isAppearanceLightStatusBars 属性,true 为浅色状态栏(图标为黑色),false 为深色状态栏(图标为白色)

  • !darkTheme 表示,当主题为浅色时,使用黑色图标;当主题为深色时,使用白色图标

  • 2、切换状态栏颜色

    val view = LocalView.current
    if (!view.isInEditMode) {
    SideEffect {
    val window = (view.context as Activity).window
    window.statusBarColor = colorScheme.primary.toArgb()
    WindowCompat
    .getInsetsController(window, view)
    .isAppearanceLightStatusBars = useDarkTheme
    }
    }

    (1)获取当前视图

    val view = LocalView.current

    • LocalView.current 获取当前 Compose 界面依附的 View 对象,即 Android 传统的 View 系统实例
    (2)避免预览崩溃

    if (!view.isInEditMode) { ... }

  • isInEditMode 用于判断当前是否处于 Android Studio 的预览模式

  • 在预览模式下没有真实的 Activity 和 Window,如果执行下面的代码会抛出异常

  • 这个判断确保逻辑只在真机或模拟器运行时生效

  • (3)副作用执行

    SideEffect { ... )

  • SideEffect 是 Compose 中的一个副作用 API

  • SideEffect 保证里面的代码每次重组完成后都会执行

  • 修改状态栏属于外部命令式操作,要确保在 UI 绘制到屏幕上之后立即同步执行,避免界面闪烁

  • (4)切换状态栏颜色

    val window = (view.context as Activity).window
    window.statusBarColor = colorScheme.primary.toArgb()
    WindowCompat
    .getInsetsController(window, view)
    .isAppearanceLightStatusBars = useDarkTheme

  • 通过 view.context 强制转为 Activity,拿到当前窗口对象

  • 将状态栏的背景色设置为 colors.primary。如果是深色主色,背景色较深;如果是浅色主色,背景色较亮

  • 通过 WindowCompat.getInsetsController 方法获取 AndroidX 兼容库提供的控制器,用于管理状态栏和导航栏的显示

  • isAppearanceLightStatusBars 属性,true 为浅色状态栏(图标为黑色),false 为深色状态栏(图标为白色)

  • useDarkTheme 表示,当主题为深色时,使用黑色图标;当主题为浅色时,使用白色图标

  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » Compose Codelab - 使用 Material 3 在 Compose 中设置主题
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!