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

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

开端

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

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

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

GitHub 地址在文章末尾。先来看看实现效果吧:
compose换肤1.gif

经过

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

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

Compose 中的主题

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

image.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 中的一员,感兴趣的可以看看我之前写的文章:再抱一抱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 的值即可。

image.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

别忘了切换分支:compose

上面对主题的讲解只是一带而过,如果大家想系统地学习 Compose 的话,可以购买我的新书《Jetpack Compose:Android全新UI编程》进行阅读,里面有完整的 Compose 框架供大家学习。

京东购买地址

当当购买地址

如果对你有帮助的话,别忘记点个 Star,感激不尽。

其实还有一些细节的东西我没有说到,大家如果有疑问的话可以在评论区提出来。

好了,先写到这里吧,再会!

目录
相关文章
|
3月前
|
Kubernetes Linux Docker
【Azure 应用服务】使用Docker Compose创建App Service遇见"Linux Version is too long. It cannot be more than 4000 characters"错误
【Azure 应用服务】使用Docker Compose创建App Service遇见"Linux Version is too long. It cannot be more than 4000 characters"错误
|
4月前
|
存储 移动开发 Android开发
使用kotlin Jetpack Compose框架开发安卓app, webview中h5如何访问手机存储上传文件
在Kotlin和Jetpack Compose中,集成WebView以支持HTML5页面访问手机存储及上传音频文件涉及关键步骤:1) 添加`READ_EXTERNAL_STORAGE`和`WRITE_EXTERNAL_STORAGE`权限,考虑Android 11的分区存储;2) 配置WebView允许JavaScript和文件访问,启用`javaScriptEnabled`、`allowFileAccess`等设置;3) HTML5页面使用`<input type="file">`让用户选择文件,利用File API;
|
5月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
5月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
5月前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
5月前
|
存储 Android开发
安卓app,MediaPlayer播放本地音频 | 按钮控制播放和停止
在Jetpack Compose中,不直接操作原生Android组件如`Button`和`MediaPlayer`,而是使用Compose UI构建器定义界面并结合ViewModel管理音频播放逻辑。以下示例展示如何播放本地音频并用按钮控制播放/停止:创建一个`AudioPlayerViewModel`管理`MediaPlayer`实例和播放状态,然后在Compose UI中使用`Button`根据`isPlaying`状态控制播放。记得在`MainActivity`设置Compose UI,并处理相关依赖和权限。
|
存储 前端开发 Shell
Android Jetpack Compose——一个简单的笔记APP
此项目功能较为简单,基本就是使用Room数据库实现CRUD,但是此项目实现了一个干净的架构,项目使用MVVM架构进行设计,每一个模块的职责划分清晰,功能明确,没有冗余的代码。其中涉及了Hilt依赖注入,对于数据库的的操作,使用接口实现类进行获取,然后将实现类的CRUD操作封装在一个数据类中,最后通过Hilt自动注入依赖,供外部调用。
664 1
Android Jetpack Compose——一个简单的笔记APP
|
Android开发
Android Compose——一个简单的新闻APP
此Demo是参考Google Github其中一个Demo而完成,涉及的内容并不复杂,主要是为了熟悉Compose编码习惯,其次参考官方的代码,可以有利于培养编程思维,仅此而已
783 1
Android Compose——一个简单的新闻APP
|
存储 缓存 前端开发
Android Compose——一个简单的Bilibili APP
此Demo采用Android Compose声明式UI编写而成,主体采用MVVM设计框架,Demo涉及到的主要技术包括:Flow、Coroutines、Retrofit、Okhttp、Hilt以及适配了深色模式等;主要数据来源于Bilibili API。
1328 0
Android Compose——一个简单的Bilibili APP
|
XML 前端开发 算法
Jetpack Compose助我快速打造电影App
Jetpack Compose助我快速打造电影App
Jetpack Compose助我快速打造电影App

热门文章

最新文章