Compose主题切换,让你的APP也能一键换肤

56df248cc21eeb3650dbbdff26874749.jpeg

/   今日科技快讯   /

近日,据外媒援引知情人士消息,推特已经聘请并购法领域的重量级律师事务所Wachtell, Lipton, Rosen & Katz LLP组建专门的律师团队,以起诉特斯拉首席执行官埃隆·马斯克放弃以440亿美元收购推特的违约行为。

/   作者简介   /

本篇文章转载自Zhujiang的博客,文章主要分享了如何使用Compose主题切换,让应用换肤,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

Zhujiang的博客地址:

https://juejin.cn/user/3913917127985240

/   开端   /

应用换肤,这真的是一个老生常谈的问题,从原生安卓开始、到后来的 Flutter ,再到现在的 Compose ,虽说老生常谈,但其实还是新瓶装旧酒。

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

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

GitHub 地址在文章末尾。先来看看实现效果吧:

b61de48af084bea053a2586aa9b7459c.gif

/   经过   /

其实 Compose 虽说换肤实现很简单,但是这也需要在你遵守 Compose 开发规范的前提下,比如定义颜色的时候不使用硬编码,而使用 MaterialTheme 中的颜色,当然还有 Shape 、Typography 等。

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

Compose 中的主题

当你创建一个新的 Compose 项目之后,系统会自动帮你创建下面的结构:

outside_default.png

可以看到系统一共创建了四个文件,顾名思义,分别为:颜色、形状、主题、类型,本文咱们主要看颜色及主题。

先来看看 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<Int> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
    mutableStateOf(getDefaultThemeId())
}

可以看到主题状态中有一个名为 getDefaultThemeId 的方法,用来获取默认主题 ID,来看下吧。

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

这里使用了 DataStore 来进行数据的存取,DataStore 也是 Jetpack 中的一员。然后修改下主题方法:

@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 的值即可。

7122f810020899f4b7d935947c1a197a.png

由上图可以看出这个布局最好使用 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 图的那种效果了。


/   结局   /


这么快就到结局了,还有点不舍,哈哈哈。光顾着写实现了,还没放 Github 地址呢,在这里:
https://github.com/zhujiang521/PlayAndroid

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

我为Android版Microsoft Edge所带来的变化

一个Android沉浸式状态栏上的黑科技

欢迎关注我的公众号

学习技术或投稿

7f2c815f8314386c19ad2cdecb92bed4.png

a48df61dc82c45f2d2e38dd11072d0cb.jpeg

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值