Android - 在 Compose with Material 3 中创建自定义颜色

Sha*_*dow 6 android material-design android-compose

我第一次探索 Compose + Material 3,在尝试实现自定义颜色时遇到了很大的困难。

\n

我的意思是根据 Compose 之前的工作方式执行以下操作:

\n

我有我的自定义属性attrs.xml

\n
<?xml version="1.0" encoding="UTF-8"?>\n<resources>\n    <declare-styleable name="CustomStyle">\n        <attr name="myCustomColor"\n            format="reference|color"/>\n    </declare-styleable>\n</resources>\n
Run Code Online (Sandbox Code Playgroud)\n

并且该自定义属性可以在我的光明和黑暗中使用styles.xml

\n
<?xml version="1.0" encoding="utf-8"?>\n<resources xmlns:tools="http://schemas.android.com/tools">\n    <style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">\n        <item name="myCustomColor">@color/white</item> <!-- @color/black in the dark style config -->\n    </style>\n</resources>\n
Run Code Online (Sandbox Code Playgroud)\n

然后我可以在任何我想要的地方使用它,无论是在代码中还是在布局中:

\n
<com.google.android.material.imageview.ShapeableImageView\n    android:layout_width="24dp"\n    android:layout_height="24dp"\n    android:background="?myCustomColor"\n
Run Code Online (Sandbox Code Playgroud)\n

这非常简单实用,因为它会自动解析浅色和深色,而我所需要的只是使用自定义颜色参考。

\n

但在 Compose with Material 3 中我找不到任何地方解释如何完成这样的事情。

\n

在材质 2 中,可以执行以下操作:

\n
val Colors.myExtraColor: Color\n    get() = if (isLight) Color.Red else Color.Green\n
Run Code Online (Sandbox Code Playgroud)\n

但在材料 3 中这不再可能:

\n
\n

与 M2 Colors 类不同,M3 ColorScheme 类不包含 isLight 参数。一般来说,您应该尝试在主题级别上对需要此信息的任何内容进行建模。

\n
\n

https://developer.android.com/jetpack/compose/designsystems/material2-material3#islight

\n

我尝试在 SO 中寻找解决方案,但到目前为止没有发现任何适用于此的解决方案。

\n

有没有一种简单的方法可以实现这一点,就像我上面举例的非 Compose 版本一样?

\n

Tha*_*oro 17

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
)
Run Code Online (Sandbox Code Playgroud)

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
    )
}
Run Code Online (Sandbox Code Playgroud)

上面我们有一个在 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() }
Run Code Online (Sandbox Code Playgroud)
  • CustomColorsPalette data class3种颜色。
  • 添加了 6 种颜色,其中 3 种浅色,3 种深色。
  • 创建了两种 val 类型CustomColorsPalette,一种具有浅色,另一种具有深色。
  • AstaticCompositionLocalOf是根据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
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

最后我们可以使用如下颜色:

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
            )
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

浅色主题示例 深色主题示例

我们添加的颜色可以通过 来使用LocalCustomColorsPalette.current,如上面的示例所示。它与其他 Compose 对象完全相同,例如LocalTextStyle.currentLocalDensity.current等。

可以应用一种技巧来修改对这些自定义对象的调用,使其与对象内部的模式类似MaterialTheme,只需添加以下代码即可CustomColorsPalette.kt

// ...
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
// ...

val MaterialTheme.customColorsPalette: CustomColorsPalette
    @Composable
    @ReadOnlyComposable
    get() = LocalCustomColorsPalette.current
Run Code Online (Sandbox Code Playgroud)

现在可以像这样调用颜色:

Text(
    text = "Default color scheme remains available",
    color = MaterialTheme.colorScheme.onBackground
)

Text(
    text = "One of customs colors",
    color = MaterialTheme.customColorsPalette.extraColor1
)

Text(
    text = "Other custom color",
    color = MaterialTheme.customColorsPalette.extraColor2
)
Run Code Online (Sandbox Code Playgroud)