开始

利用换肤,这真的是一个陈词滥调的问题,从原生安卓开始、到起初的 Flutter ,再到当初的 Compose ,虽说陈词滥调,但其实还是新瓶装旧酒。

安卓原生的主题切换这里不再说了,这不是本文的重点,况且那个一篇文章预计也说不清。

Flutter 的主题切换次要依赖于 provider 状态治理,其实在 Compose 中也差不多,且听完娓娓道来!

GitHub 地址在文章开端。先来看看实现成果吧:

通过

其实 Compose 虽说换肤实现很简略,然而这也须要在你恪守 Compose 开发标准的前提下,比方定义色彩的时候不应用硬编码,而应用 MaterialTheme 中的色彩,当然还有 ShapeTypography 等。

Compose 中的主题大家应该都很相熟了,但应该还有不是很相熟的,这里先来简略说下吧。

Compose 中的主题

当你创立一个新的 Compose 我的项目之后,零碎会主动帮你创立上面的构造:

能够看到零碎一共创立了四个文件,顾名思义,别离为:色彩、形态、主题、类型,本文咱们次要看色彩及主题。

先来看看 Color 文件默认是什么样子的吧:

val Purple200 = Color(0xFFBB86FC)val Purple500 = Color(0xFF6200EE)val Purple700 = Color(0xFF3700B3)val Teal200 = Color(0xFF03DAC5)

下面就是 Color 文件中的代码,只是简略定义了四种色彩。

上面再来看看 Theme 文件吧:

private val DarkColorPalette = darkColors(    primary = Purple200,    primaryVariant = Purple700,    secondary = Teal200)private val LightColorPalette = lightColors(    primary = Purple500,    primaryVariant = Purple700,    secondary = Teal200)@Composablefun PlayAndroidTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {    val colors = if (darkTheme) {        DarkColorPalette    } else {        LightColorPalette    }    MaterialTheme(        colors = colors,        typography = Typography,        shapes = Shapes,        content = content    )}

能够看到 Theme 中先是定义了深色和浅色两个色彩,而后通过判断是否为夜间模式来动静适配。

上面来看看 lightColors 办法吧:

fun lightColors(    primary: Color = Color(0xFF6200EE),    primaryVariant: Color = Color(0xFF3700B3),    secondary: Color = Color(0xFF03DAC6),    secondaryVariant: Color = Color(0xFF018786),    background: Color = Color.White,    surface: Color = Color.White,    error: Color = Color(0xFFB00020),    onPrimary: Color = Color.White,    onSecondary: Color = Color.Black,    onBackground: Color = Color.Black,    onSurface: Color = Color.Black,    onError: Color = Color.White): Colors

通过下面代码能够得悉,能够在这里设置每种色彩值,深浅模式都相似,都能够进行设置。

应用主题

下面简略说了下 Compose 中的主题,那么主题写好之后应该如何进行应用呢?来看代码:

override fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)    setContent {        // 应用主题        PlayAndroidTheme {            NavGraph()        }    }}

没错,就这么简略,只须要在页面外层嵌套下方才设置的主题即可。当初主题是设置上了,那应该如何应用方才设置到主题中的那些色彩呢?亦或是别的资源?上代码:

// 色彩应用MaterialTheme.colors.primary// 形态应用MaterialTheme.shapes.large// 字体类型应用MaterialTheme.typography.body1

还记得下面所说的须要恪守 Compose 的开发标准吧,所说的其实就是应用这些资源的时候尽量应用咱们定义好的。

解决

如何切换主题

首先须要思考如何来进行主题的切换,整个主题必定应用在我的项目的开始——启动 Activity 中,但切换主题的页面必定不在一块,那这个时候应该如何在切换主题页面切换了之后让 Activity 晓得呢?

最开始的时候我的想法还是不够 Compose ,我想的是应用播送,在切换主题页面点击之后发送一个播送,而后在 Activity 中进行接管,而后接管到之后刷新。我的确这样做了,性能也的确实现了,然而总感觉哪里不对,感觉 Compose 中不应该这样才对。

中午在食堂吃饭的时候忽然想到:Compose 中全部都是以状态驱动 UI 扭转的,我间接将主题切换设置成一个状态不得了!

开搞

说干就干,首先先来设置下咱们默认的几套主题吧:

// 天蓝色const val SKY_BLUE_THEME = 0// 灰色const val GRAY_THEME = 1// 深蓝色const val DEEP_BLUE_THEME = 2// 绿色const val GREEN_THEME = 3// 紫色const val PURPLE_THEME = 4// 橘黄色const val ORANGE_THEME = 5// 棕色const val BROWN_THEME = 6// 红色const val RED_THEME = 7// 青色const val CYAN_THEME = 8// 品红色const val MAGENTA_THEME = 9

这里为了之后代码简略点没有应用枚举,其实都差不多的。

而后增加一个主题的状态:

/** * 主题状态 */val themeTypeState: MutableState<Int> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {    mutableStateOf(getDefaultThemeId())}

能够看到主题状态中有一个名为 getDefaultThemeId 的办法,用来获取默认主题 ID,来看下吧:

/** * 获取以后默认主题 */fun getDefaultThemeId(): Int = DataStoreUtils.getSyncData(CHANGED_THEME, SKY_BLUE_THEME)

这里应用了 DataStore 来进行数据的存取,DataStore 也是 Jetpack 中的一员,感兴趣的能够看看我之前写的文章:再抱一抱DataStore

而后批改下主题办法:

@Composablefun PlayAndroidTheme(    themeId: Int = 0,    darkTheme: Boolean = isSystemInDarkTheme(),    content: @Composable () -> Unit) {    val colors = if (darkTheme) {        playDarkColors()    } else {        getThemeForThemeId(themeId)    }    MaterialTheme(        colors = colors,        typography = typography,        shapes = Shapes,        content = content    )}

能够看到下面增加了一个参数 themeId 用来示意以后须要应用的主题 id,getThemeForThemeId 办法用来通过主题 ID 来获取须要的主题色彩集,来看下实现吧:

/** * 通过主题 ID 来获取须要的主题 */private fun getThemeForThemeId(themeId: Int) = when (themeId) {    SKY_BLUE_THEME -> {        playLightColors(            primary = primaryLight        )    }    GRAY_THEME -> {        playLightColors(            primary = gray_theme        )    }    // 别的主题相似,篇幅起因省略}

接下来批改下 Activity 中的调用吧:

PlayAndroidTheme(themeTypeState.value) {    NavGraph()}

因为 themeTypeState 是一个 State,所以当它的值扭转的时候,就会主动刷新 UI。

收尾

创立主题列表

先做下筹备工作,先来创立一个用来寄存主题信息的实体类吧:

data class ThemeModel(val color: Color, val colorId: Int, val colorName: String)

接下来将咱们增加的主题放到一个 List 中:

// 主题model列表private val themeList = arrayListOf(    ThemeModel(primaryLight, SKY_BLUE_THEME, "天蓝色"),    ThemeModel(gray_theme, GRAY_THEME, "灰色"),    ThemeModel(deep_blue_theme, DEEP_BLUE_THEME, "深蓝色"),    ThemeModel(green_theme, GREEN_THEME, "绿色"),    ThemeModel(purple_theme, PURPLE_THEME, "紫色"),    ThemeModel(orange_theme, ORANGE_THEME, "橘黄色"),    ThemeModel(brown_theme, BROWN_THEME, "棕色"),    ThemeModel(red_theme, RED_THEME, "红色"),    ThemeModel(cyan_theme, CYAN_THEME, "青色"),    ThemeModel(magenta_theme, MAGENTA_THEME, "品红色"),)

创立主题切换页面

万事具备,只欠东风。当初主题这块曾经全副筹备好了,只须要再创立一个主题切换的页面,点击的时候保留下来主题 ID 并刷新下 themeTypeState 的值即可。

由上图能够看出这个布局最好应用 LazyVerticalGrid,而后设置下一行放 5 个 item 即可:

LazyVerticalGrid(    cells = GridCells.Fixed(5),    modifier = Modifier.padding(horizontal = 10.dp)) {    items(themeList) { item: ThemeModel ->        ThemeItem(playTheme, item) {            val playTheme = item.colorId            themeTypeState.value = playTheme            DataStoreUtils.putSyncData(CHANGED_THEME, playTheme)        }    }}

OK了,完结了,这样就能够了,曾经能够实现文章结尾 Gif 图的那种成果了。