我第一次探索 Compose + Material 3,并且在尝试实现自定义颜色时遇到了很大的困难。
我的意思是根据 Compose 之前如何完成事情来执行以下操作:
我的自定义属性在
attrs.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="CustomStyle">
<attr name="myCustomColor"
format="reference|color"/>
</declare-styleable>
</resources>
并且该自定义属性可以在我的光明和黑暗中使用
styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="myCustomColor">@color/white</item> <!-- @color/black in the dark style config -->
</style>
</resources>
然后我可以在任何我想要的地方使用它,无论是在代码中还是在布局中:
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:background="?myCustomColor"
这非常简单实用,因为它会自动解析浅色和深色,而我所需要的只是使用自定义颜色参考。
但是在 Compose with Material 3 中我找不到任何地方解释如何完成这样的事情。
在材料 2 中,可以做这样的事情:
val Colors.myExtraColor: Color
get() = if (isLight) Color.Red else Color.Green
但是在材料 3 中这不再可能了:
与 M2 Colors 类不同,M3 ColorScheme 类不包含 isLight 参数。一般来说,您应该尝试在主题级别上对需要此信息的任何内容进行建模。
https://developer.android.com/jetpack/compose/designsystems/material2-material3#islight
我尝试在这里寻找解决方案,但到目前为止没有发现任何适用于此的解决方案。
有没有一种简单的方法可以实现这一点,就像我上面举例的非 Compose 版本一样?
CompositionLocalProvider 就是解决这个问题的方法。
Colors.kt
:
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
val LightPrimary = Color(color = 0xFF6750A4)
val LightOnPrimary = Color(color = 0xFFFFFFFF)
val LightPrimaryContainer = Color(color = 0xFFEADDFF)
val LightOnPrimaryContainer = Color(color = 0xFF21005D)
val LightInversePrimary = Color(color = 0xFFD0BCFF)
val LightSecondary = Color(color = 0xFF625B71)
val LightOnSecondary = Color(color = 0xFFFFFFFF)
val LightSecondaryContainer = Color(color = 0xFFE8DEF8)
val LightOnSecondaryContainer = Color(color = 0xFF1D192B)
val LightTertiary = Color(color = 0xFF7D5260)
val LightOnTertiary = Color(color = 0xFFFFFFFF)
val LightTertiaryContainer = Color(color = 0xFFFFD8E4)
val LightOnTertiaryContainer = Color(color = 0xFF31111D)
val LightBackground = Color(color = 0xFFFFFBFE)
val LightOnBackground = Color(color = 0xFF1C1B1F)
val LightSurface = Color(color = 0xFFFFFBFE)
val LightOnSurface = Color(color = 0xFF1C1B1F)
val LightSurfaceVariant = Color(color = 0xFFE7E0EC)
val LightOnSurfaceVariant = Color(color = 0xFF49454F)
val LightInverseSurface = Color(color = 0xFF313033)
val LightInverseOnSurface = Color(color = 0xFFF4EFF4)
val LightSurfaceTint = Color(color = 0xFF6750A4)
val LightError = Color(color = 0xFFB3261E)
val LightOnError = Color(color = 0xFFFFFFFF)
val LightErrorContainer = Color(color = 0xFFF9DEDC)
val LightOnErrorContainer = Color(color = 0xFF410E0B)
val LightOutline = Color(color = 0xFF79747E)
val LightOutlineVariant = Color(color = 0xFFCAC4D0)
val LightScrim = Color(color = 0xFF4B484E)
val DarkPrimary = Color(color = 0xFFD0BCFF)
val DarkOnPrimary = Color(color = 0xFF381E72)
val DarkPrimaryContainer = Color(color = 0xFF4F378B)
val DarkOnPrimaryContainer = Color(color = 0xFFEADDFF)
val DarkInversePrimary = Color(color = 0xFF6750A4)
val DarkSecondary = Color(color = 0xFFCCC2DC)
val DarkOnSecondary = Color(color = 0xFF332D41)
val DarkSecondaryContainer = Color(color = 0xFF4A4458)
val DarkOnSecondaryContainer = Color(color = 0xFFE8DEF8)
val DarkTertiary = Color(color = 0xFFEFB8C8)
val DarkOnTertiary = Color(color = 0xFF492532)
val DarkTertiaryContainer = Color(color = 0xFF633B48)
val DarkOnTertiaryContainer = Color(color = 0xFFFFD8E4)
val DarkBackground = Color(color = 0xFF1C1B1F)
val DarkOnBackground = Color(color = 0xFFE6E1E5)
val DarkSurface = Color(color = 0xFF1C1B1F)
val DarkOnSurface = Color(color = 0xFFE6E1E5)
val DarkSurfaceVariant = Color(color = 0xFF49454F)
val DarkOnSurfaceVariant = Color(color = 0xFFCAC4D0)
val DarkInverseSurface = Color(color = 0xFFE6E1E5)
val DarkInverseOnSurface = Color(color = 0xFF313033)
val DarkSurfaceTint = Color(color = 0xFFD0BCFF)
val DarkError = Color(color = 0xFFF2B8B5)
val DarkOnError = Color(color = 0xFF601410)
val DarkErrorContainer = Color(color = 0xFF8C1D18)
val DarkOnErrorContainer = Color(color = 0xFFF9DEDC)
val DarkOutline = Color(color = 0xFF938F99)
val DarkOutlineVariant = Color(color = 0xFF49454F)
val DarkScrim = Color(color = 0xFFB4B0BB)
val LightColorScheme = lightColorScheme(
primary = LightPrimary,
onPrimary = LightOnPrimary,
primaryContainer = LightPrimaryContainer,
onPrimaryContainer = LightOnPrimaryContainer,
inversePrimary = LightInversePrimary,
secondary = LightSecondary,
onSecondary = LightOnSecondary,
secondaryContainer = LightSecondaryContainer,
onSecondaryContainer = LightOnSecondaryContainer,
tertiary = LightTertiary,
onTertiary = LightOnTertiary,
tertiaryContainer = LightTertiaryContainer,
onTertiaryContainer = LightOnTertiaryContainer,
background = LightBackground,
onBackground = LightOnBackground,
surface = LightSurface,
onSurface = LightOnSurface,
surfaceVariant = LightSurfaceVariant,
onSurfaceVariant = LightOnSurfaceVariant,
surfaceTint = LightSurfaceTint,
inverseSurface = LightInverseSurface,
inverseOnSurface = LightInverseOnSurface,
error = LightError,
onError = LightOnError,
errorContainer = LightErrorContainer,
onErrorContainer = LightOnErrorContainer,
outline = LightOutline,
outlineVariant = LightOutlineVariant,
scrim = LightScrim
)
val DarkColorScheme = darkColorScheme(
primary = DarkPrimary,
onPrimary = DarkOnPrimary,
primaryContainer = DarkPrimaryContainer,
onPrimaryContainer = DarkOnPrimaryContainer,
inversePrimary = DarkInversePrimary,
secondary = DarkSecondary,
onSecondary = DarkOnSecondary,
secondaryContainer = DarkSecondaryContainer,
onSecondaryContainer = DarkOnSecondaryContainer,
tertiary = DarkTertiary,
onTertiary = DarkOnTertiary,
tertiaryContainer = DarkTertiaryContainer,
onTertiaryContainer = DarkOnTertiaryContainer,
background = DarkBackground,
onBackground = DarkOnBackground,
surface = DarkSurface,
onSurface = DarkOnSurface,
surfaceVariant = DarkSurfaceVariant,
onSurfaceVariant = DarkOnSurfaceVariant,
surfaceTint = DarkSurfaceTint,
inverseSurface = DarkInverseSurface,
inverseOnSurface = DarkInverseOnSurface,
error = DarkError,
onError = DarkOnError,
errorContainer = DarkErrorContainer,
onErrorContainer = DarkOnErrorContainer,
outline = DarkOutline,
outlineVariant = DarkOutlineVariant,
scrim = DarkScrim
)
Theme.kt
:
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
useDynamicColors: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (useDarkTheme) dynamicDarkColorScheme(context = LocalContext.current)
else dynamicLightColorScheme(context = LocalContext.current)
}
useDarkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
上面我们有一个在 Material 3 中定义主题颜色的基本代码。
要使用
CompositionLocalProvider
添加自定义颜色或其他任何内容,我们首先需要创建一个包含值/类型的 data class
。假设我们想要 3 种新的颜色类型,它们可能会根据浅色或深色主题而有所不同。
为此,我们向项目添加一个新文件:
CustomColorsPalette.kt
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
@Immutable
data class CustomColorsPalette(
val extraColor1: Color = Color.Unspecified,
val extraColor2: Color = Color.Unspecified,
val extraColor3: Color = Color.Unspecified
)
val LightExtraColor1 = Color(color = 0xFF29B6F6)
val LightExtraColor2 = Color(color = 0xFF26A69A)
val LightExtraColor3 = Color(color = 0xFFEF5350)
val DarkExtraColor1 = Color(color = 0xFF0277BD)
val DarkExtraColor2 = Color(color = 0xFF00695C)
val DarkExtraColor3 = Color(color = 0xFFC62828)
val LightCustomColorsPalette = CustomColorsPalette(
extraColor1 = LightExtraColor1,
extraColor2 = LightExtraColor2,
extraColor3 = LightExtraColor3
)
val DarkCustomColorsPalette = CustomColorsPalette(
extraColor1 = DarkExtraColor1,
extraColor2 = DarkExtraColor2,
extraColor3 = DarkExtraColor3
)
val LocalCustomColorsPalette = staticCompositionLocalOf { CustomColorsPalette() }
CustomColorsPalette data class
有3种颜色。CustomColorsPalette
,一种是浅色,另一种是深色。staticCompositionLocalOf
是根据 CompositionLocalProvider 的文档创建的。之后,我们可以返回
Theme.kt
文件添加逻辑并完成CompositionLocalProvider
的配置。
Theme.kt
:
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
useDynamicColors: Boolean = true,
content: @Composable () -> Unit
) {
// "normal" palette, nothing change here
val colorScheme = when {
useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (useDarkTheme) dynamicDarkColorScheme(context = LocalContext.current)
else dynamicLightColorScheme(context = LocalContext.current)
}
useDarkTheme -> DarkColorScheme
else -> LightColorScheme
}
// logic for which custom palette to use
val customColorsPalette =
if (useDarkTheme) DarkCustomColorsPalette
else LightCustomColorsPalette
// here is the important point, where you will expose custom objects
CompositionLocalProvider(
LocalCustomColorsPalette provides customColorsPalette // our custom palette
) {
MaterialTheme(
colorScheme = colorScheme, // the MaterialTheme still uses the "normal" palette
content = content
)
}
}
最后我们可以按如下方式使用核心:
MainActivity.kt
AppTheme {
Scaffold { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues = innerPadding),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Default Material 3 color on background")
Card {
Text(text = "Default Material 3 color for elevation")
}
Text(
text = "One of customs colors",
color = LocalCustomColorsPalette.current.extraColor1
)
Text(
text = "Other custom color",
color = LocalCustomColorsPalette.current.extraColor2
)
}
}
}
我们添加的颜色可以通过
LocalCustomColorsPalette.current
使用,如上例所示。它与其他 Compose 对象完全相同,例如 LocalTextStyle.current
、LocalDensity.current
等。