断更一时爽,一直断更一直爽~ 哈哈哈,就当给自己放了个长假吧。最近的行情太糟了,身边有同学已经被毕业,两个多月终于降薪找到下家··· 这里呼吁大家一定要存好六个月没有工作还能正常生活的银子,以备不时之需!希望疫情能早日平息,经济可以快速恢复吧~
自己也没想到这个系列可以到第六篇,断更确实很久了,居然还收到了小伙伴的催更,感谢你们的不离不弃。闲话少说,我们这次要介绍的是 Compose 主题,那么 Compose 主题 Theme 到底有什么?用 Compose 实现换肤简单吗?一起来看看吧!
Jetpack Compose 的主题 Theme 就是一套 UI 风格,其中包括字体、字号、色值等等,类比于 Android View 体系中的 Theme.MaterialComponents.DayNight.DarkActionBar
等等的主题样式。与 View 体系最大的不同在于,它完全抛弃了 xml 文件的设置,所有样式都是通过代码设置的,主题样式大体可以分为 色值、文案样式、形状样式 三大类。先来看看主题中的色值。
1. Color 色值
许多组件不仅支持设置它自己的背景色,还可以设置它包含的其他可组合项的默认色值,使用 contentColorFor
方法就可以实现。例如下面 code 1:
// code 1 Surface (color = Color.Yellow,contentColor = Color.Red) { Text(text = "July 2021",style = typography.body2) }
你会发现,Surface
的背景色为黄色,而 Text
中文案为 红色,如果将 Text
换为 Icon
,那么 Icon
的色调也会变为红色,感兴趣的同学可以试试。
类似 Surface
的还有 TopAppBar
可组合项,下面是它们的实现源码:
// code 2 Surface( color: Color = MaterialTheme.colors.surface, contentColor: Color = contentColorFor(color), ... TopAppBar( backgroundColor: Color = MaterialTheme.colors.primarySurface, contentColor: Color = contentColorFor(backgroundColor), ...
Compose 官方推荐使用 Surface
来给任何可组合项设置颜色,因为它会设置适当的内容颜色 CompositionLocal
值,看 code 2 中 Surface
的 color
属性就默认设置了 MaterialTheme.colors.surface
色值。不推荐直接调用 Modifier.background
设置颜色,因为它并没有设置任何的默认色值。在实际开发中,其实咱也没咋用到 MaterialTheme
,所以这里还是看个人吧~
// code 3 -Row(Modifier.background(MaterialTheme.colors.primary)) { // 不推荐 +Surface(color = MaterialTheme.colors.primary) { // 推荐 + Row( ...
在可组合项中,一些 UI 的参数是有默认值的,比如 Alpha 透明度、ContentColor 内容色等。我们可以使用CompositionLocalProvider
类去自定义这些属性的默认值。比如:
// code 4 CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) { Text(text = "Hello, 修之竹~") }
对比没有加 CompositionLocalProvider
的情况,会发现文案颜色更浅。这是因为,默认情况下 Text
文案的 alpha
值为 ContentAlpha.high
,这里设置为 ContentAlpha.disabled
,还有一个 ContentAlpha.medium
,alpha
值的大小排序为:high > medium > disabled
。具体的值可以查看源码,它还分了高对比度和低对比度两种情况。
Compose 在暗夜模式支持方面也做的不错。比如,是否在浅色模式中运行的判断很简单:
// code 5 val isLightTheme = MaterialTheme.colors.isLight
此外,如果在实际中就是使用的 MaterialTheme
中的色值来设置,那么需要注意的是,Compose 默认的可组合项中常见的情况是在浅色模式中将容器设为 primary
色值,在暗夜模式中将其设为 surface
色值,许多组件默认都是使用这种模式,例如TopAppBar
(应用栏) 和 BottomNavigation
(底部导航栏)。
2. 文案样式
文案样式也可以复用 MaterialTheme
中已有的字体样式,当然也可以先将已有的样式 copy 一份,然后修改其中的某些属性。比如可以修改字间距:
// code 6 Text( text = "Hello, 修之竹~", // style = MaterialTheme.typography.body1 // 复用 MaterialTheme 中的字体样式 style = MaterialTheme.typography.body1.copy( // copy 已有样式并修改字间距属性的值 letterSpacing = 5.sp ), fontSize = 20.sp // 在Text中设置 fontSize 可重写覆盖 MaterialTheme.typography.body1 TextStyle 中的字体大小 )
2.1 AnnotatedString 类来设置多种样式
AnnotatedString
用来代替 SpannableString
最好不过了,因为它真的比 SpannableString
好用多了!再也不用担心使用 SpannableString
引发的数组越界问题了。代码及效果如下,当然还可以实现许多其他的文案样式,感兴趣的同学可以自行查阅 SpanStyle
的官方文档。
// code 7 val annotatedString = buildAnnotatedString { withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) { append("Kotlin ") } append("是世上 ") withStyle(SpanStyle(fontSize = 24.sp)) { append("最好的语言") } } Text(text = annotatedString)
SpanStyle
是设置文案的样式的,作用于字符单位;而如果要针对文案的行高、对齐方式等进行设置,则需要使用ParagraphStyle
,顾名思义它是针对段落样式的。
3. 形状样式
MaterialTheme
主题中也有 Shape
形状属性,在许多的官方 Composable 组件中都有这个 Shape
属性,比如 Button
组件的 Shape
属性默认值就是 MaterialTheme.shapes.small
。
// code 8 fun Button( ··· shape: Shape = MaterialTheme.shapes.small, ··· ) { }
Shapes.kt
提供了 small
、medium
、large
3 种不同的属性值,其实都是 RoundedCornerShape
的具体实现,只不过圆角的大小不太一样罢了,具体数值可查看源码。
如果需要在自定义 Composable 组件中使用 Shape
,有两种方法:一是使用拥有 Shape
属性的官方 Composable 组件;二是使用 Modifier
中可设置 shape
的方法去接收自定义 Composable 组件传进来的 Shape
参数值。先来看看第一种方法,如 code 9 所示。
// code 9 @Composable fun RoundedCornerImage(painter: Painter, cornerSize: Int) { Surface( shape = RoundedCornerShape(cornerSize.dp) ) { Image( painter = painter, contentDescription = "圆角图片" ) } }
这是个可以设置图片圆角大小的自定义 Composable 组件,因为需要用到 Shape
设置圆角,所以使用了 Surface
这个组件的 Shape 属性来具体实现。
第二种方法就是借助 Modifier
的方法,比如 Modifier.clip(shape: Shape)
、Modifier.background(color: Color, shape: Shape = RectangleShape)
、Modifier.border(width: Dp, brush: Brush, shape: Shape)
等等。比较简单,感兴趣的同学可以试试。
4. 切换主题
上面说了这么多,其实都是针对单个主题说的,在实际应用中,我们可以做个切换主题的小功能,如下图 2 所示:
其中包含了色值、字体、形状的切换,用到的思路和原理都是一样的,所以这里就只拿主题色值的切换来说明。想要实现这一功能,首先需要明白的是,点击事件之后切换主题的回调该怎么做?
总不能给所有设置色值的地方都设置一个监听器吧?那样做想想都觉得“酸爽”。其实,在 Compose 中,我们可以将当前主题用一个 MutableState
对象来保存,然后将主题中的色值集合与这个状态相关联,当用户切换主题改变了这个 MutableState
值之后,与之关联的色值集合就会收到回调进行切换,同时通知 Compose 进行重组,这样就使用新的色值集合进行渲染了。
关于 MutableState
状态的相关知识,可以查阅我的另一篇文章:Jetpack-Compose 学习笔记(五)—— State 状态是个啥?又是新概念?
OK,整体的思路有了,咱们再详细看看具体是如何实现的。按照之前的分析,我们需要在每次渲染页面的时候读取当前主题的值,所以,首先得先获取当前的主题值。我这里是使用 MMKV
存储当前主题值,主题值是 String
类型,如下 code 10 所示:
// code 10 //获取选中的主题 id val chosenThemeId = remember { mutableStateOf( MMKV.defaultMMKV().getString(MMKVConstant.ChosenThemeCode, ThemeKinds.DEFAULT.name) ?: ThemeKinds.DEFAULT.name ) } enum class ThemeKinds { DEFAULT, //默认主题 RED, //红色主题 YELLOW, //黄色主题 BLUE //蓝色主题 }
然后自定义主题,在这里需要规定主题用到的色值、文案样式、形状样式等。在每次切换主题后,在这里还需要根据传入的当前主题值,设置相应的色值组等等。详细如下代码:
// code 11 @Composable fun CustomTheme( chosenThemeId: MutableState<String>, content: @Composable () -> Unit ) { //自定义主题色值 val colors = when (chosenThemeId.value) { ThemeKinds.DEFAULT.name -> { LightColors } ThemeKinds.RED.name -> { RedThemeColors } ThemeKinds.YELLOW.name -> { YellowThemeColors } ThemeKinds.BLUE.name -> { BlueThemeColors } else -> { DarkColors } } MaterialTheme( colors = colors, typography = typography, shapes = shapes ) { content() } } //红色主题色值 private val RedThemeColors = lightColors( primary = Color(0xFFFF4040), background = Color(0x66FF4040) ) //黄色主题色值 private val YellowThemeColors = lightColors( primary = Color(0xFFDAA520), background = Color(0x66FFD700) ) //蓝色主题色值 private val BlueThemeColors = lightColors( primary = Color(0xFF436EEE), background = Color(0x6600FFFF) ) private val DarkColors = darkColors( primary = Color.White, primaryVariant = Red700, onPrimary = Color.Black, secondary = Red300, onSecondary = Color.Black, error = Red200 ) private val LightColors = lightColors( primary = Color.Black, primaryVariant = Red900, onPrimary = Color.White, secondary = Red700, secondaryVariant = Red900, onSecondary = Color.White, error = Red800, )
可以看到,在我们自定义的主题 CustomTheme
最后,还是使用的 MaterialTheme
,只不过将官方的 MaterialTheme
中 colors
设置成了我们自己的 colors
,同理,我们还可以设置文案 typography
和 形状 shapes
等参数。