# android Compose主题切换——让你的APP也能一键换肤 站点:答源(https://www.dandroid.cn/) 原文:https://www.dandroid.cn/archives/19239 Canonical:https://www.dandroid.cn/archives/19239 作者:admin 发布日期:2023-09-10 最后更新:2023-09-10 01:10 分类:compose 内容类型:post 说明:本页是原文的纯文本镜像,供大模型与检索器完整读取;不含导航、广告、付费框。引用请以上方「原文」链接为准。 ## 正文 开端 应用换肤,这真的是一个老生常谈的问题,从原生安卓开始、到后来的 Flutter ,再到现在的 Compose ,虽说老生常谈,但其实还是新瓶装旧酒。 安卓原生的主题切换这里不再说了,这不是本文的重点,况且那个一篇文章估计也说不清😂。 Flutter 的主题切换主要依赖于 provider 状态管理,其实在 Compose 中也差不多,且听完娓娓道来! 看看实现效果吧: 经过 其实 Compose 虽说换肤实现很简单,但是这也需要在你遵守 Compose 开发规范的前提下,比如定义颜色的时候不使用硬编码,而使用 MaterialTheme 中的颜色,当然还有 Shape 、Typography 等。 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 ) @Composable fun 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 by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { mutableStateOf(getDefaultThemeId()) } 可以看到主题状态中有一个名为 getDefaultThemeId 的方法,用来获取默认主题 ID,来看下吧: /** * 获取当前默认主题 */ fun getDefaultThemeId(): Int = DataStoreUtils.getSyncData(CHANGED_THEME, SKY_BLUE_THEME) 这里使用了 DataStore 来进行数据的存取,DataStore 也是 Jetpack 中的一员,感兴趣的可以看看我之前写的文章:再抱一抱DataStore 然后修改下主题方法: @Composable fun 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 图的那种效果了。 文章来源于互联网:Compose主题切换——让你的APP也能一键换肤 --- 由 答源 DAN 底座 1.4.1 生成 · 2026-11-14T03:33:42+00:00