Now in Android学习Compose是怎么切换主题的

上篇文章我们介绍了,Now in Android的架构,和具体功能介绍,在介绍功能的时候,里面有个切换主题功能,我觉得这个功能很nice,所以我们一起来看看是怎么实现的。

1、切换主题的具体实现,从单选框选择UI到配置数据的改变。

首先我们找到Ui部分,是一个SettingDialog,具体的位置是在feature 模块里面的settings模块,就两个文件,SettingsDialog,和SettingsViewModel

image.png

打开SettingsDialog映入眼帘的就是预览界面

image.png

我们再去看具体实现的代码。看看切换单选框的时候,发生了什么事情。
image.png

SettingsPanel点击事件,执行了onChangeThemBrand回调,

image.png

onChangeThemeBrand = viewModel::updateThemeBrand, Kotlin里面也有::的语法了么,不知道什么意思,猜测应该是调用viewModel里的updateThemeBrand方法,
在 Kotlin 中,viewModel::updateNumber是一种函数引用的表达方式。

具体来说:

函数引用的概念

函数引用允许你直接引用一个已有函数(或方法),将其作为一个值进行传递。在某些情况下,它可以替代使用 lambda 表达式来传递一个可调用的代码块。

这里的具体含义

  1. viewModel通常是一个对象,它可能是一个视图模型(ViewModel)实例。

  2. updateNumber是这个视图模型对象中的一个方法。

所以,viewModel::updateNumber整体表示对viewModel对象的updateNumber方法的引用。

这种用法常见于一些函数式编程的场景中,例如在使用高阶函数时,将特定的方法作为参数传递给另一个函数,以便在合适的时候调用这个方法。比如,可能会有一个函数接收一个函数类型的参数,然后调用这个参数所代表的函数,就可以将viewModel::updateNumber作为实参传递给这个函数,以实现对updateNumber方法的间接调用。

fun updateThemeBrand(themeBrand: ThemeBrand) {
    viewModelScope.launch {
        userDataRepository.setThemeBrand(themeBrand)
    }
}

我们根据跟踪代码,发现,UI改变后,调用了ViewModel里面的方法,而这个方法里,更新了DataStore里的数据,然后就没了,然后UI就发生了改变。

image.png 好家伙1727332688014.png

我们分析了从UI,到数据折条线,点击UI更改了User数据仓库里面的东西,更改了DataStore里的东西。
下面我们分析另外一条线。

2、从配置数据,到更改主题

首先我们来到MainActivity的setContent方法

setContent {
    val darkTheme = shouldUseDarkTheme(uiState)


    val appState = rememberNiaAppState(
        networkMonitor = networkMonitor,
        userNewsResourceRepository = userNewsResourceRepository,
        timeZoneMonitor = timeZoneMonitor,
    )

    val currentTimeZone by appState.currentTimeZone.collectAsStateWithLifecycle()

    CompositionLocalProvider(
        LocalAnalyticsHelper provides analyticsHelper,
        LocalTimeZone provides currentTimeZone,
    ) {
        NiaTheme(
            darkTheme = darkTheme,
            androidTheme = shouldUseAndroidTheme(uiState),
            disableDynamicTheming = shouldDisableDynamicTheming(uiState),
        ) {
            @OptIn(ExperimentalMaterial3AdaptiveApi::class)
            NiaApp(appState)
        }
    }
}

这里的切换就是Android主题和Default主题,所以我们就看shuoldUserAndroidTheme方法就可以了
这个方法是个Copmoseable方法

@Composable
private fun shouldUseAndroidTheme(
    uiState: MainActivityUiState,
): Boolean = when (uiState) {
    Loading -> false
    is Success -> when (uiState.userData.themeBrand) {
        ThemeBrand.DEFAULT -> false
        ThemeBrand.ANDROID -> true
    }
}

拿到User配置数据,判断,返回一个Boolean值,这样就能知道用哪个主题了,是用Android的还是Default的呀,是用暗黑主题啊,还是明亮主题呀。

3、主题的具体实现。

定义一个ColorScheme,里面定义了主体内的颜色类型,比如error颜色,button颜色,之类的,Now in Android,直接使用了MaterialDesign3里的ColorScheme,我们在开发真实项目中,可以和UI小姐姐一起商量,定义我门自己的ColorScheme

fun lightColorScheme(
    primary: Color = ColorLightTokens.Primary,
    onPrimary: Color = ColorLightTokens.OnPrimary,
    primaryContainer: Color = ColorLightTokens.PrimaryContainer,
    onPrimaryContainer: Color = ColorLightTokens.OnPrimaryContainer,
    inversePrimary: Color = ColorLightTokens.InversePrimary,
    secondary: Color = ColorLightTokens.Secondary,
    onSecondary: Color = ColorLightTokens.OnSecondary,
    secondaryContainer: Color = ColorLightTokens.SecondaryContainer,
    onSecondaryContainer: Color = ColorLightTokens.OnSecondaryContainer,
    tertiary: Color = ColorLightTokens.Tertiary,
    onTertiary: Color = ColorLightTokens.OnTertiary,
    tertiaryContainer: Color = ColorLightTokens.TertiaryContainer,
    onTertiaryContainer: Color = ColorLightTokens.OnTertiaryContainer,
    background: Color = ColorLightTokens.Background,
    onBackground: Color = ColorLightTokens.OnBackground,
    surface: Color = ColorLightTokens.Surface,
    onSurface: Color = ColorLightTokens.OnSurface,
    surfaceVariant: Color = ColorLightTokens.SurfaceVariant,
    onSurfaceVariant: Color = ColorLightTokens.OnSurfaceVariant,
    surfaceTint: Color = primary,
    inverseSurface: Color = ColorLightTokens.InverseSurface,
    inverseOnSurface: Color = ColorLightTokens.InverseOnSurface,
    error: Color = ColorLightTokens.Error,
    onError: Color = ColorLightTokens.OnError,
    errorContainer: Color = ColorLightTokens.ErrorContainer,
    onErrorContainer: Color = ColorLightTokens.OnErrorContainer,
    outline: Color = ColorLightTokens.Outline,
    outlineVariant: Color = ColorLightTokens.OutlineVariant,
    scrim: Color = ColorLightTokens.Scrim,
    surfaceBright: Color = ColorLightTokens.SurfaceBright,
    surfaceContainer: Color = ColorLightTokens.SurfaceContainer,
    surfaceContainerHigh: Color = ColorLightTokens.SurfaceContainerHigh,
    surfaceContainerHighest: Color = ColorLightTokens.SurfaceContainerHighest,
    surfaceContainerLow: Color = ColorLightTokens.SurfaceContainerLow,
    surfaceContainerLowest: Color = ColorLightTokens.SurfaceContainerLowest,
    surfaceDim: Color = ColorLightTokens.SurfaceDim,
): ColorScheme =
    ColorScheme(
        primary = primary,
        onPrimary = onPrimary,
        primaryContainer = primaryContainer,
        onPrimaryContainer = onPrimaryContainer,
        inversePrimary = inversePrimary,
        secondary = secondary,
        onSecondary = onSecondary,
        secondaryContainer = secondaryContainer,
        onSecondaryContainer = onSecondaryContainer,
        tertiary = tertiary,
        onTertiary = onTertiary,
        tertiaryContainer = tertiaryContainer,
        onTertiaryContainer = onTertiaryContainer,
        background = background,
        onBackground = onBackground,
        surface = surface,
        onSurface = onSurface,
        surfaceVariant = surfaceVariant,
        onSurfaceVariant = onSurfaceVariant,
        surfaceTint = surfaceTint,
        inverseSurface = inverseSurface,
        inverseOnSurface = inverseOnSurface,
        error = error,
        onError = onError,
        errorContainer = errorContainer,
        onErrorContainer = onErrorContainer,
        outline = outline,
        outlineVariant = outlineVariant,
        scrim = scrim,
        surfaceBright = surfaceBright,
        surfaceContainer = surfaceContainer,
        surfaceContainerHigh = surfaceContainerHigh,
        surfaceContainerHighest = surfaceContainerHighest,
        surfaceContainerLow = surfaceContainerLow,
        surfaceContainerLowest = surfaceContainerLowest,
        surfaceDim = surfaceDim,
    )

大概就是这个样子,然后根据这个类

image.png
将所有主题的ColorScheme定义好,供我们使用,
使用的时候直接
MaterialTheme.colorScheme.surfaceVariant,颜色就设置好了,

4、重要的概念CompositionLocal

它会为当前的Composeable域创建一个变量,它是一个副本
这里的值可以改变,不响应我们的定义好的值,
MaterialTheme 对象提供了三个 CompositionLocal 实例,即 colors、typography 和 shapes。可以在任何地方拿到这些实例进行使用。具体来说,这些 MaterialTheme的 colorsshapes 和 typography 属性就是访问 LocalColors、LocalShapes 和 LocalTypography。
CompositionLocal 实例的作用域限定为Composable的一部分,因此可以在结构树的不同级别提供不同的值。CompositionLocal 的 current 值对应于Composable的某个父级提供的就近值。

如需为 CompositionLocal 提供新值,请使用 CompositionLocalProvider 及其 provides infix 函数,该函数将 CompositionLocal 键与 value 相关联。在访问 CompositionLocal 的 current 属性时,CompositionLocalProvider 的 content lambda 将获取提供的值。提供新值后,Compose 会重组读取 CompositionLocal 的组合部分。

这样,最外层的Theme的colorScheme,放到CompositionLocal里,里面所有的东西都能用了。而且提供新值后,Compose会重组,所有的颜色也就跟着改变了。

5、StateFlow

StateFlow状态的订阅,是一种特殊的SharedFlow。Now in Android,用来代替了LiveData给状态提供了订阅通知的的功能。
一处改变,到处通知。MainActtivityUIState和 SettingsUiState 都订阅了User数据仓库里的,userData对象,当userData发生改变,就会通知到MainActivity 和SettingDialog,这样,主题,就会改变,Settings的单选框状态也会改变。StateFlow的具体原理,有时间再单独写一篇文章给大家介绍。

6、总的逻辑

总的逻辑,我画了图,根据图再去看代码,一看就能看明白。

image.png

7、总结

Compose切换主题的主要逻辑,Composeable域,有一个全局变量,存储所有颜色,不过这个变量用CompositionLocal进行了包装,这样只影响自己的composeable域。这个地方不仅可以存储主题颜色之类的,其他的业务数据也可以存。

某些场景下,CompositionLocal可能不合适,甚至过度使用。

显式参数

在极简单逻辑情况,应尽量使用显示参数传递,且只传递有效参数,避免造成参数过多。

控制反转

另一种避免参数过多或无效参数的方法就是控制反转。一些逻辑可以不在子级页面进行,而应该转移到父级页面来进行。

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22337,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?