主题主要学习内容 - Material Design 入门指南以及如何针对您的品牌对其进行自定义
- Compose 如何实现 Material Design 系统
- 如何在应用中定义和使用颜色、排版和形状
- 如何设置组件的样式
- 如何支持浅色主题和深色主题
在本次学习中我们将设置新闻阅读应用的样式,从未设置样式的应用入手,应用所学的内容来设置应用的主题,并为设置深色主题提供支持
准备工作官网示例下载 因为之后的代码都是基于其中的项目进行的,所以还是推荐下载。同时也可以看一下Google人员对于的Compose的代码编写风格 因为代码过多且需要添加drawable资源文件,此处就不将代码写出来了
在解压文件中的ThemingCodelab 目录中存放本次学习的案例代码 此项目包含 3 个主要软件包: com.codelab.theming.data - 该软件包包含模型类和示例数据,无需修改该软件包com.codelab.theming.ui.start - 该 示例 的起点,您应该在该软件包中完成此 示例 中要求的所有更改com.codelab.theming.ui.finish - 该软件包是此 示例 的最终状态,供参考
我们可以选择Import Project 方式进行学习,也可以通过拷贝代码到自己项目中的方式 我使用的是拷贝代码的方式,可能之后跟Import Project 方式有些区别请谅解 如果是选择拷贝代码的方式请注意: Empty Activity 默认Activity 继承androidx.appcompat.app.AppCompatActivity ,而要使用 Compose 则需要Activity 继承androidx.activity.ComponentActivity ,否则会产生如下异常
Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
Material主题设置Jetpack Compose 提供了 Material Design 的实现,Material Design 是一个用于创建数字化界面的综合设计体系。Material Design 组件(按钮、卡片、开关等)在Material Theme 设置的基础上构建而成,一个 Material Theme由颜色、排版和形状属性组成 颜色Material Theme 定义了一些从语义上命名的颜色,我们可以在应用中使用: 其中 primary 是应用的主要颜色,secondary 用于提供强调色,我们可以通过这两种颜色的设置凸显出需要对比区域 background 和surface 两种颜色用于在概念上驻留在"应用表面"的组件的容器,也就是背景颜色
Material Design 中还定义了on 颜色,是与具名颜色产生明显对比的颜色 例如:采用surface 作为背景颜色的容器中文本应该采用onSurface 颜色
Material 组件已配置并使用这些主题颜色。例如,FloatingActionButton 的默认颜色为 secondary ,Card 的默认颜色为 surface ,诸如此类。 排版同样,Material Theme还定义了一些从语义上命名的字体样式: 虽然我们可能不会按主题来更改字体样式,但使用 Material Design 中的字体样式可提升应用内部的一致性 Material 组件已配置并使字体样式。例如,TopAppBar 默认使用 h6 样式,Button 默认使用 button 样式,诸如此类。 形状Material Theme 中定义了 3 个类别:小型、中型和大型组件;每种组件都可以定义要使用的形状,从而自定义角的样式(切角和圆角)和大小 默认情况下,Button 和TextField 使用小型形状主题,Card 和Dialog 使用中型形状主题,Sheet 使用大型形状主题 如需查看组件和形状主题的完整对应关系,请点击此处
基准Material 默认采用“基准”主题,即紫色的配色方案、Roboto 字体比例,以及以上图片所示的略呈圆形的形状。如果您未指定或自定义主题,组件就会使用基准主题 定义主题MaterialTheme在 Jetpack Compose 中实现主题设置的核心元素是 MaterialTheme 可组合项。如果将此可组合项放在 Compose 层次结构中,您就可以为其中的所有组件指定颜色、字体和形状的自定义设置 Unit) { ... }
我们可以使用 MaterialTheme object 检索传递到此可组合项的参数,以公开 colors 、typography 和 shapes 属性。在之后,我们将逐一进行深入介绍
找到 Home 可组合函数 - 这是应用的主入口点。请注意,虽然我们声明了 MaterialTheme ,但并未指定任何参数,因此会获得默认的“基准”样式:
创建主题如果需集中设置样式,官方建议创建自己的可组合项,用于封装和配置 MaterialTheme 这样做,我们就可以在指定自己的主题自定义设置,并在多个位置(例如跨多个屏幕或 @Preview )轻松地重复使用这些自定义设置 可以根据需要创建多个主题可组合项。例如,如果您想针对应用的不同部分支持不同的样式
我们可以在Theme.kt 中添加一个名为StudyTheme 新可组合函数 Unit) { MaterialTheme(content = content)}
如果使用Import Project ,在start 中没有Theme.kt 文件,需要新建Theme.kt 文件
我们回到 Home 可组合函数,并将 MaterialTheme 替换为 StudyTheme :
同样在PostItemPreview 和 FeaturedPostPreview 以使用新的 StudyTheme 可组合项来封装其内容,以便预览使用新的主题
颜色我们要在应用中实现的调色板如下所示: Compose 中的颜色是使用 Color 类定义的。借助多个构造函数,您可以将颜色指定为 ULong ,也可以按单独的颜色通道来指定颜色 若要从用于指定颜色的常用“#dd0d3c”格式进行转换,请将“#”替换为“0xff”,即 Color(0xffdd0d3c) ,其中“ff”表示完整的 Alpha 值
在Color.kt 中添加以下颜色:
在定义颜色时,我们要根据颜色值“字面意义”命名颜色,而不要“从语义上”命名颜色 例如,命名为 Red500 而不是 primary 。这样一来,我们就可以定义多个主题。例如,在深色主题中或样式设置不同的屏幕上,系统可能会将另一种颜色视为 primary
注意导入 Compose 的 Color 类型是 androidx.compose.ui.graphics.Color ,而不是 android.graphics.Color
如果使用Import Project ,在start 中没有Color.kt 文件,需要新建Color.kt 文件
现在,我们已经定义了应用的颜色。接下来,我们将其合并到 MaterialTheme 所需的 Colors 对象中,从而将特定颜色分配到 Material 的具名颜色。切换回 Theme.kt ,然后添加以下代码:
下面,我们要使用 lightColors 函数来构建 Colors ,这样即可提供合理的默认值,让我们不必将构成 Material 调色板的所有颜色全都指定出来 例如,我们尚未指定 background 颜色或许多“on”颜色,我们将会使用lightColors 中的默认值
我们在StudyTheme 中使用这些颜色: Unit) { MaterialTheme( content = content,/*+*/ colors = LightColors )}
此时刷新预览,就会发现新的配色方案会反映在 TopAppBar 等组件中
排版我们要在应用中实现的字体样式如下所示: 在 Compose 中,我们可以定义 TextStyle 对象,以定义设置一些文本的样式所需的信息。下面是其属性的示例:
我们所需的字体比例要针对标题使用 Montserrat ,并针对正文文本使用 Domine 相关字体文件在 示例 的 res/font 文件夹中
在Type.kt 文件中定义 FontFamily (结合了每个 Font 的不同粗细):
然后创建一个 MaterialTheme 接受的 Typography 对象,为比例中的每个语义样式指定 TextStyle :
我们在StudyTheme 中使用新的 Typography : Unit) { MaterialTheme( content = content, colors = LightColors,/*+*/ typography = StudyTypography )}
形状Compose 提供了 RoundedCornerShape 类和 CutCornerShape 类,可用于定义形状主题 在 Shape.kt ,并添加以下代码:
我们在StudyTheme 中使用新的 Shapes : Unit) { MaterialTheme( content = content, colors = LightColors, typography = StudyTypography,/*+*/ shapes = StudyShapes )}
刷新预览,可以看见精选博文的 Card 变为左上切角形状 深色主题在应用中支持深色主题不仅有助于您的应用在用户设备上更好地集成(从 Android 10 开始,设备上已提供全局深色主题切换开关),还有助于降低能耗以及为满足无障碍功能需求提供支持。Material 提供了关于如何创建深色主题的接口 以下是我们想为深色主题实现的调色板: 打开 Color.kt 并添加以下颜色:
打开 Theme.kt 并添加以下代码:
darkColors 和lightColors 一样,当我们没有提供调色板颜色时提供默认值
然后,更新 StudyTheme : Unit) { MaterialTheme( content = content,/*+*/ colors = if (darkTheme) DarkColors else LightColors, typography = StudyTypography, shapes = StudyShapes )}
此时,我们添加了用于判断是否使用深色主题的新参数,并将其默认设为查询设备的全局设置 为 FeaturedPost 可组合项创建新的预览,此预览能够以深色主题显示该可组合项:
预览效果对比 处理颜色我们现在可以创建自己的Theme,设置应用的颜色、字体样式、形状。而所有的Material 组件默认支持这些自定义属性 例如,FloatingActionButton 可组合项默认使用主题中的 secondary 颜色,当然我们可以通过为此参数指定不同的值来设置颜色:
原色Compose 提供了一个 Color 类。您可以在本地创建这些类,并将其保留在 object 等元素中:
注意:在静态声明颜色定义时,请务必小心,因为这些定义会导致更难/无法支持不同的主题(例如,浅色/深色主题)
Color 中有许多有用的方法,例如 copy ,您可以通过此方法使用不同的 alpha/red/green/blue 值来创建新的颜色
主题颜色我们可以从主题中检索颜色
通过使用 MaterialTheme object ,其colors 属性会返回MaterialTheme 可组合项中设置的Colors 。也就是说,我们只需为主题提供不同的颜色集,即可支持不同的外观和风格,而无需处理应用代码 由于主题中的每种颜色都是Color 实例,因此我们可以通过copy 派生出不同的颜色
这种方法可以确保颜色可以在不同主题下正常显示,无需编写静态颜色代码 背景色和内容颜色许多组件都接受一对颜色和“内容颜色”:
这样我们不仅可以设置可组合项的背景颜色,还可以为"内容"(即包含在内的可组合项)提供默认颜色。默认情况下,许多可组合项都会使用这种内容颜色,如:Text、Icon contentColorFor 方法可以为任何主题颜色检索适当的“on”颜色,例如,如果您设置 primary 背景,它就会返回 onPrimary 作为内容颜色。如果您设置非主题背景颜色,则应自行提供合理的内容颜色
跟踪contentColorFor 方法最终会进入该方法,所以如果backgroundColor 使用非主题颜色,需要提供contentColor onPrimary primaryVariant -> onPrimary secondary -> onSecondary secondaryVariant -> onSecondary background -> onBackground surface -> onSurface error -> onError else -> Color.Unspecified }}
我们还可以使用 LocalContentColor CompositionLocal 来检索与当前背景形成对比的颜色: 如果对于CompositionLocal 很陌生,可以看看我的另一篇简书 简书-CompositionLocal
当设置任何元素的颜色时,最好使用 Surface 来实现此目的,因为它会设置适当的内容颜色 CompositionLocal 值 请慎用直接 Modifier.background 调用,这种调用不会设置适当的内容颜色
目前,我们的 Header 组件background 始终使用 Color.LightGray 背景。这在浅色主题中看起来没有问题,但在深色主题中,就会与背景形成高度对比。而且也不会指定与背景颜色形成对比的文本颜色
接下来,我们通过Surface 去解决这个问题 在 Header 可组合项中,移除用于指定硬编码颜色的 background 修饰符。改为将 Text 封装在包含主题派生颜色的 Surface 中,并指定相应内容应采用 primary 颜色:
内容 Alpha 值通常情况下,我们通过强调或弱化内容来突出重点并体现出视觉上的层次感。Material Design 建议采用不同的不透明度来传达这些不同的重要程度 Jetpack Compose 通过 LocalContentAlpha 实现此功能。您可以通过为此 CompositionLocal 提供一个值来为层次结构指定内容 Alpha 值 子可组合项可以使用此值,例如 Text 和 Icon 默认使用LocalContentColor 的颜色值 ,其中的Alpha 会调整为 LocalContentAlpha 的值
Material 指定了一些标准 Alpha 值(high、medium、disabled),这些值由 ContentAlpha 对象提供
请注意,MaterialTheme 默认将 LocalContentAlpha 设置为 ContentAlpha.high
我们将使用内容 Alpha 值来阐明精选博文的信息层次结构。在 PostMetadata 可组合项中,重点突出元数据 medium :
深色主题若要在 Compose 中实现深色主题,我们只需提供不同的颜色集并通过主题查询颜色即可 我们可以通过下面代码检测是否在浅色主题中运行:
此值由 lightColors/[darkColors 构建器函数设置
Material Design 建议避免在深色主题中使用大面积的明亮颜色 一种常见模式是在浅色主题中将容器背景颜色设为 primary ,并在深色主题中将其设为 surface ;许多组件都默认使用此策略,例如TopAppBar 和BottomNavigation 为了便于实现,Colors 提供了 primarySurface 颜色,以准确完成上述行为
遵循此指南,我们需要将AppBar 中的TopAppBar 的backgroundColor 切换为primarySurface 或移除此参数(因为此参数为默认设置)即可
在 Material 中,如果采用的是深色主题,则高度较高 (elevation) 的 Surface 会获得高度叠加层(其背景颜色会变浅)。在使用深色主题时,系统会自动实现此效果: 处理文本在处理文本时,我们使用 Text 可组合项来显示文本,使用 TextField 和 OutlinedTextField 进行文本输入,并使用 TextStyle 对文本应用单一样式。我们可以使用 AnnotatedString 对文本应用多种样式 和颜色一样,用于显示文本的 Material 组件可以获取到主题排版自定义设置:
不过实现此目的要比使用默认参数(如在设置颜色时所看到的那样)略微复杂一些 因为组件本身往往不会显示文本,而是提供槽 API,让您能够传入 Text 可组合项。那么,组件是如何设置主题排版样式的呢? 在后台,它们使用 ProvideTextStyle 可组合项(本身就使用 CompositionLocal )来设置"current"TextStyle 。如果您未提供具体的 textStyle 参数,Text 可组合项会默认查询此"current"样式
主题文本样式就像颜色一样,最好从当前主题中检索 TextStyle ,使用一组数量少且一致的样式,并使其更易于维护 MaterialTheme.typography 会检索在 MaterialTheme 可组合项中设置的 Typography 实例,让我们能够使用自己定义的样式:
如果您需要自定义 TextStyle ,可以对其执行 copy 操作并替换相关属性,或者让 Text 可组合项接受大量样式参数,这些参数会叠加到任何 TextStyle 的上层:
在我们的应用中,许多地方都会自动应用主题 TextStyle ,例如,TopAppBar 将其 title 的样式设为 h6 ,而 ListItem 将其主要文本和辅助文本的样式分别设为 subtitle1 和 body2 接下来,我们要将主题排版样式应用于应用的其余部分。将 Header 设为使用 subtitle2 ;对于 FeaturedPost 中的文本,将标题设为 h6 ,并将作者信息和PostMetadata 设为 body2 :
多种样式如果您需要对某些文本应用多种样式,可以使用 AnnotatedString 类来应用标记,从而为一系列文本添加 SpanStyle 。您可以动态添加这些元素,也可以使用 DSL 语法来创建内容:
接下来,我们要为描述应用中的各个博文的标签设置样式。目前,它们使用与元数据其余部分相同的文本样式;我们将使用 overline 文本样式和背景颜色来区分它们。在 PostMetadata 可组合项中: if (index != 0) { append(tagDivider) }/*+*/ withStyle(tagStyle){ append(" ${tag.uppercase(Locale.getDefault())} ")/*+*/ } } } ...}
处理形状与颜色和排版一样,如果设置形状主题,相应设置会反映在 Material 组件中。例如,Button 会获取为小型组件设置的形状:
与颜色一样,Material 组件使用默认参数,我们可以直接查看组件将要使用的形状类别,或提供替代方案。如需查看组件和形状类别的完整对应关系,请参阅此文档。 请注意,有些组件会使用经过修改的主题形状,以适应其上下文的要求。例如,默认情况下,TextField 使用小型形状主题,但它会对底角应用零边角大小:
主题形状在创建自己的组件时,我们可以自行使用各种形状;为此,需要使用接受形状的可组合项或 Modifier (例如,Surface 、Modifier.clip 、Modifier.background 、Modifier.border 等)
接下来,我们要将形状主题添加到 PostItem 中显示的图片;我们要对其应用主题的 small 形状,并使用 Modifier.clip 应用该形状:
组件样式Compose 没有提供用于提取组件样式(例如,Android View 样式或 CSS 样式)的明确方法。由于所有 Compose 组件都是用 Kotlin 编写的,因此还可通过其他方法来实现相同的目的 我们可以改为创建自己的自定义组件库,并在整个应用中使用这些组件 比如 示例 中:
Header 可组合项本质上是样式化的 Text ,可供我们在整个应用中使用
所有组件都是由较低级别的构建块构造而成的,我们可以使用同样的构建块来自定义 Material 组件 例如, Button 使用 ProvideTextStyle 可组合项为传递给它的内容设置默认文本样式。您可以使用完全相同的机制来设置自己的文本样式 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |