一篇搞懂Compose自定义theme

为了支持无需将颜色作为显式参数依赖项传递给大多数可组合项,Compose 提供了 CompositionLocal,可让您创建以树为作用域的具名对象,这可以用作让数据流经界面树的一种隐式方式

CompositionLocal 元素通常在界面树的某个节点以值的形式提供。该值可供其可组合项的后代使用,而无需在可组合函数中将 CompositionLocal 声明为参数。

CompositionLocal 是 Material 主题在后台使用的内容。 MaterialTheme 对象提供了三个 CompositionLocal 实例,即 colors、typography 和 shapes。您可以之后在组合的任何后代部分中检索这些实例。具体来说,这些是可以通过 MaterialTheme colorsshapes 和 typography 属性访问的 LocalColorsLocalShapes 和 LocalTypography 属性。比如获取颜色,可以使用MaterialTheme.colors.primary。当然对于我们很多App,它的颜色并不遵守或者使用material,这就导致了我们需要自己定义颜色。当然这里就涉及到自己定义颜色。那么如何定义呢,其实我们可以参照MaterialTheme的color的定义来自定义自己的主题色

object MaterialTheme {
   
    val colors: Colors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current
        //下面是我们上面提到的shape和typography
        ...
        
}

可以看到其实就是定义了一个object名为MaterialTheme,在其中定义了一个变量为colors,类型为Colors(tag1),而且它的get方法是通过LocalColors.current(tag2)返回的。接下来我们就重点看一下这里的tag1和tag2的实现。

tag1的实现如下

@Stable

class Colors(
    primary: Color,
    primaryVariant: Color,
    secondary: Color,
    secondaryVariant: Color,
    background: Color,
    surface: Color,
    error: Color,
    onPrimary: Color,
    onSecondary: Color,
    onBackground: Color,
    onSurface: Color,
    onError: Color,
    isLight: Boolean
) {
    var primary by mutableStateOf(primary, structuralEqualityPolicy())
        internal set
    var primaryVariant by mutableStateOf(primaryVariant, structuralEqualityPolicy())
        internal set
    var secondary by mutableStateOf(secondary, structuralEqualityPolicy())
        internal set
    var secondaryVariant by mutableStateOf(secondaryVariant, structuralEqualityPolicy())
        internal set
    var background by mutableStateOf(background, structuralEqualityPolicy())
        internal set
    var surface by mutableStateOf(surface, structuralEqualityPolicy())
        internal set
    var error by mutableStateOf(error, structuralEqualityPolicy())
        internal set
    var onPrimary by mutableStateOf(onPrimary, structuralEqualityPolicy())
        internal set
    var onSecondary by mutableStateOf(onSecondary, structuralEqualityPolicy())
        internal set
    var onBackground by mutableStateOf(onBackground, structuralEqualityPolicy())
        internal set
    var onSurface by mutableStateOf(onSurface, structuralEqualityPolicy())
        internal set
    var onError by mutableStateOf(onError, structuralEqualityPolicy())
        internal set
    var isLight by mutableStateOf(isLight, structuralEqualityPolicy())
        internal set

...
}

其实就是一个class,定义了需要的所有颜色值,并且通过mutableStateOf进行保存。

再来看tag2处

internal val LocalColors = staticCompositionLocalOf { lightColors() }

这里其实就是利用staticCompositionLocalOf创建的了一个CompositionLocal对象,这里的lightColors()是一个函数,其实就是创建类型为Colors的对象

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 = Colors(
    primary,
    primaryVariant,
    secondary,
    secondaryVariant,
    background,
    surface,
    error,
    onPrimary,
    onSecondary,
    onBackground,
    onSurface,
    onError,
    true
)

到这里可以看到我们将Colors利用CompositonLocal保存起来了,那么这个保存起来的值在什么时候挂载到树上的呢。

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable () -> Unit
) {
    val rememberedColors = remember {
        // Explicitly creating a new object here so we don't mutate the initial [colors]
        // provided, and overwrite the values set in it.
        colors.copy()
    }.apply { updateColorsFrom(colors) }
    val rippleIndication = rememberRipple()
    val selectionColors = rememberTextSelectionColors(rememberedColors)
    CompositionLocalProvider(
        LocalColors provides rememberedColors,
        LocalContentAlpha provides ContentAlpha.high,
        LocalIndication provides rippleIndication,
        LocalRippleTheme provides MaterialRippleTheme,
        LocalShapes provides shapes,
        LocalTextSelectionColors provides selectionColors,
        LocalTypography provides typography
    ) {
        ProvideTextStyle(value = typography.body1) {
            PlatformMaterialTheme(content)
        }
    }
}

其实就是在根上我们使用MaterialTheme的时候。将色值传递进去,并且利用CompositonLocalProvider来提供这个值。如需为 CompositionLocal 提供新值,请使用 CompositionLocalProvider 及其 provides infix 函数,该函数将 CompositionLocal 键与 value 相关联。在访问 CompositionLocal 的 current 属性时,CompositionLocalProvider 的 content lambda 将获取提供的值。提供新值后,Compose 会重组读取 CompositionLocal 的组合部分。至此MaterialTheme的定义我们介绍完了。那么就按照这个步骤创建我们自己的theme。

一:定义自己的颜色类

@Stable

class YctColors(
    color999999: Color,
    color666666: Color,
    color4cb033: Color,
    color181816: Color,
    colorcccccc: Color,
    colorBlack40: Color,
    colorf5f5f5: Color,
    colorf7f7f7: Color,
    colorffffff: Color,
    color000000: Color,
    color333333: Color
) {
    var color999999: Color by mutableStateOf(color999999)
        private set
    var color666666: Color by mutableStateOf(color666666)
        private set
    var color4cb033: Color by mutableStateOf(color4cb033)
        private set
    var color181816: Color by mutableStateOf(color181816)
        private set
    var colorcccccc: Color by mutableStateOf(colorcccccc)
        private set
    var colorBlack40: Color by mutableStateOf(colorBlack40)
        private set
    var colorf5f5f5: Color by mutableStateOf(colorf5f5f5)
        private set
    var colorf7f7f7: Color by mutableStateOf(colorf7f7f7)
        private set
    var colorffffff: Color by mutableStateOf(colorffffff)
        private set
    var color000000: Color by mutableStateOf(color000000)
        private set
    var color333333: Color by mutableStateOf(color333333)
        private set
}

二:根据需要创建不同的类的实例

比如只是适配亮色和暗色,那么我们就提供两个方法创建对应的色值实例,这里就创建了light和dark两种

private val LightColorPalette = YctColors(
    color000000 = Color000000,
    colorffffff = Colorffffff,
    color4cb033 = Color4cb033,
    color999999 = Color999999,
    color666666 = Color666666,
    color181816 = Color181816,
    colorcccccc = Colorcccccc,
    colorBlack40 = ColorBlack40,
    colorf5f5f5 = Colorf5f5f5,
    colorf7f7f7 = Colorf7f7f7,
    color333333 = Color333333
)


private val DarkColorPalette = YctColors(
    color000000 = Color000000Dark,
    colorffffff = ColorffffffDark,
    color4cb033 = Color4cb033Dark,
    color999999 = Color999999Dark,
    color666666 = Color666666Dark,
    color181816 = Color181816Dark,
    colorcccccc = ColorccccccDark,
    colorBlack40 = ColorBlack40Dark,
    colorf5f5f5 = Colorf5f5f5Dark,
    colorf7f7f7 = Colorf7f7f7Dark,
    color333333 = Color333333Dark
)

三:创建CompositonLocal对象

利用compositionLocalOf创建一个CompositionLocal 对象,然后通过CompositionLocalProvider来访问。这里默认传入的是亮色值的对象

private val LocalYctColors = compositionLocalOf {
    LightColorPalette
}

四:定义单例以访问定义的颜色

利用创建好的CompositonLocal来访问定义的颜色

object YctTheme {
    val colors: YctColors @Composable get() = LocalYctColors.current
}

五:创建Theme,使用CompositonLocal中保存的颜色实例

创建Theme(composable)函数,利用isSystemInDarkTheme()确定当前色系,并且再函数中通过CompositionLocalProvider提供所使用的调色板(深色或浅色)

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun YctTheme(themeDark: Boolean= isSystemInDarkTheme(), content: @Composable() () -> Unit) {
    val targetColors = if (!themeDark) LightColorPalette else DarkColorPalette


    val colorffffff = animateColorAsState(targetValue = targetColors.colorffffff, TweenSpec(600))
    val color000000 = animateColorAsState(targetValue = targetColors.color000000, TweenSpec(600))
    val colorf7f7f7 = animateColorAsState(targetValue = targetColors.colorf7f7f7, TweenSpec(600))
    val colorf5f5f5 = animateColorAsState(targetValue = targetColors.colorf5f5f5, TweenSpec(600))
    val colorBlack40 = animateColorAsState(targetValue = targetColors.colorBlack40, TweenSpec(600))
    val colorcccccc = animateColorAsState(targetValue = targetColors.colorcccccc, TweenSpec(600))
    val color181816 = animateColorAsState(targetValue = targetColors.color181816, TweenSpec(600))
    val color4cb033 = animateColorAsState(targetValue = targetColors.color4cb033, TweenSpec(600))
    val color666666 = animateColorAsState(targetValue = targetColors.color666666, TweenSpec(600))
    val color999999 = animateColorAsState(targetValue = targetColors.color999999, TweenSpec(600))
    val color333333 = animateColorAsState(targetValue = targetColors.color333333, TweenSpec(600))

    val colors = YctColors(
        color000000 = color000000.value,
        colorffffff = colorffffff.value,
        color4cb033 = color4cb033.value,
        color999999 = color999999.value,
        color666666 = color666666.value,
        color181816 = color181816.value,
        colorcccccc = colorcccccc.value,
        colorBlack40 = colorBlack40.value,
        colorf5f5f5 = colorf5f5f5.value,
        colorf7f7f7 = colorf7f7f7.value,
        color333333 = color333333.value
    )
    val fontScale = LocalDensity.current.fontScale
    val displayMetrics = LocalContext.current.resources.displayMetrics
    val widthPixels = displayMetrics.widthPixels

    CompositionLocalProvider(
        LocalYctColors provides colors,
        //屏幕适配
        LocalDensity provides Density(
            density = widthPixels / 375f, fontScale = fontScale
        ),
        //去掉列表滚动到边界时阴影
        LocalOverscrollConfiguration provides null,
        //去掉点击水波纹
        LocalIndication provides NoIndication

    ) {
        content()
    }
}

当然整个app的亮暗切换,可以通过如下代码设置夜间模式还是白天模式。

AppCompatDelegate.setDefaultNightMode(if (changedTheme)AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)

如果是原有的view系统修改完立即生效,可以使用recreate函数重新生成,对于view系统还是compose,如果需要适配drawable中的图片资源,只需再drawable-night-xxhdpi中放入同名的夜间模式图片即可。以上就是Compose中自定义主题的全部内容。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYhQTiWg' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片