【问题标题】:How to match color attributes between theme and icon in Jetpack Compose?如何在 Jetpack Compose 中匹配主题和图标之间的颜色属性?
【发布时间】:2022-04-23 21:02:14
【问题描述】:

我有一个矢量可绘制对象,它有两条路径,具有不同的属性,引用不同的主题颜色。

而且这些属性的值会被不同的主题改变,在Jetpack Compose中如何实现?

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="82dp"
    android:height="96dp"
    android:viewportWidth="82"
    android:viewportHeight="96">
  <path
      android:pathData="M0.2887,4.6197C0.2887,2.2278 2.2278,0.2887 4.6197,0.2887H77.3803C79.7722,0.2887 81.7113,2.2278 81.7113,4.6197V91.2394C81.7113,93.6314 79.7722,95.5704 77.3803,95.5704H4.6197C2.2278,95.5704 0.2887,93.6314 0.2887,91.2394V4.6197Z"
      android:fillColor="?attr/colorPrimary" />
  <path
      android:pathData="M4.043,4.0422h73.9155v73.9155h-73.9155z"
      android:fillColor="?attr/colorSecondary"/>
</vector>

以不同主题的styles.xml为例

<style name="RedTheme" parent="GlobalTheme">
    <item name="colorPrimary">@color/red</item>
    <item name="colorSecondary">@color/redDark</item>
    ...
</style>

<style name="GreenTheme" parent="GlobalTheme">
    <item name="colorPrimary">@color/green</item>
    <item name="colorSecondary">@color/greenDark</item>
    ...
</style>

根据当前使用的主题,矢量绘图或图标可以有不同的颜色

【问题讨论】:

    标签: android kotlin android-jetpack-compose android-jetpack android-theme


    【解决方案1】:

    先定义颜色:

    import androidx.compose.ui.graphics.Color
    val white = Color(0xFFFFFFFF)
    val black = Color(0xFF000000)
    ...
    

    定义属性:

    class GlobalThemeData(colorPrimary: Color ...){
        var colorPrimary by mutableStateOf(colorPrimary)
        ...
    }
    

    定义主题:

    val LightColorPalette = GlobalThemeData(colorPrimary = white)
    
    val DarkColorPalette = GlobalThemeData(colorPrimary = black)
    
    ...
    
    object GlobalTheme {
        val colors: GlobalThemeData
            @Composable
            get() = LightColorPalette
    
        enum class Theme {
            Light, Dark ...
        }
    }
    

    定义组合本地和全局主题

    private val LocalGlobalColors = compositionLocalOf {
        LightColorPalette
    }
    
    @Composable
    fun GlobalTheme(theme: GlobalTheme.Theme = GlobalTheme.Theme.Light,
                    content: @Composable () -> Unit) {
        val targetColors =
            when(theme){
                GlobalTheme.Theme.Dark -> DarkColorPalette
                GlobalTheme.Theme.Light -> LightColorPalette
                GlobalTheme.Theme.Red -> RedColorPalette
                GlobalTheme.Theme.Green -> GreenColorPalette
            }
        CompositionLocalProvider(LocalGlobalColors provides targetColors) {
            MaterialTheme(
                shapes = Shapes,
                typography = Typography,
                content = content
            )
        }
    }
    

    例子:

    val white = Color(0xFFFFFFFF)
    val black = Color(0xFF000000)
    val red = Color(0xFFC51614)
    val green = Color(0xFF67BF63)
    
    val LightColorPalette = GlobalThemeData(colorPrimary = white)
    
    val DarkColorPalette = GlobalThemeData(colorPrimary = black)
    
    val RedColorPalette = GlobalThemeData(colorPrimary = red)
    
    val GreenColorPalette = GlobalThemeData(colorPrimary = green)
    
    object GlobalTheme {
        val colors: GlobalThemeData
            @Composable
            get() = LocalGlobalColors.current
    
        enum class Theme {
            Light, Dark,Red,Green
        }
    }
    
    class GlobalThemeData(colorPrimary: Color){
        var colorPrimary by mutableStateOf(colorPrimary)
    }
    
    private val LocalGlobalColors = compositionLocalOf {
        LightColorPalette
    }
    
    @Composable
    fun GlobalTheme(theme: GlobalTheme.Theme = GlobalTheme.Theme.Light,
                    content: @Composable () -> Unit) {
        val targetColors =
            when(theme){
                GlobalTheme.Theme.Dark -> DarkColorPalette
                GlobalTheme.Theme.Light -> LightColorPalette
                GlobalTheme.Theme.Red -> RedColorPalette
                GlobalTheme.Theme.Green -> GreenColorPalette
            }
        CompositionLocalProvider(LocalGlobalColors provides targetColors) {
            MaterialTheme(
                shapes = Shapes,
                typography = Typography,
                content = content
            )
        }
    }
    
    setContent {
        var theme by remember{mutableStateOf(GlobalTheme.Theme.Dark)}
        GlobalTheme(theme) {
            Column() {
                Icon(Icons.Default.Lock,
                    modifier = Modifier.size(48.dp),
                    contentDescription = "",
                    tint = GlobalTheme.colors.colorPrimary)
                Button(onClick = {
                    theme = when(theme){
                        GlobalTheme.Theme.Dark -> GlobalTheme.Theme.Red
                        GlobalTheme.Theme.Light -> GlobalTheme.Theme.Dark
                        GlobalTheme.Theme.Red -> GlobalTheme.Theme.Green
                        GlobalTheme.Theme.Green -> GlobalTheme.Theme.Light
                    }
                }) {
                    Text(text = "Change",color = GlobalTheme.colors.colorPrimary)
                }
            }
    
        }
    }
    

    【讨论】:

    • 感谢您的详细回答,但您似乎错过了重点。现在你所有的代码都不会影响?attr/colorPrimary里面的图标。并且您与 Compose Icon 一起使用的色调应用于整个矢量可绘制对象,而不是 xml 中的特定路径。
    【解决方案2】:

    TL;DR

    在 compose 中将主题应用于可绘制对象可能有点棘手,因为 Xml 可绘制对象无法访问 Jetpack Compose 中的颜色定义。因此,您应该在 Xml 中定义颜色和主题,并让 Jetpack Compose 了解这些值。

    在 Compose 中使用 AndroidView 来加载 xml drawable:

    AndroidView(factory = { context ->
      val contextThemeWrapper = ContextThemeWrapper(context, R.style.RedTheme)
      val drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.your_drawable, contextThemeWrapper.theme)
    
      ImageView(context).apply { setImageDrawable(drawable) }
    })
    

    这样,drawable 将尊重您传递给ContextThemeWrapper 的主题样式。

    在 Xml 中定义主题

    除了您定义的主题RedThemeGreenTheme,您还需要声明这些属性: 您还需要定义样式属性:

    <declare-styleable name="ThemeStyle">
      <attr name="colorPrimary" format="color" />
      <attr name="colorSecondary" format="color" />
    </declare-styleable>
    

    将主题映射到撰写

    定义一个AppTheme 类:

    object AppTheme {
      val colors: AppColors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current
    
      val style: Int
        @Composable
        @ReadOnlyComposable
        get() = LocalStyleRes.current
    }
    
    internal val LocalStyleRes: ProvidableCompositionLocal<Int> = staticCompositionLocalOf { R.style.RedTheme }
    
    @Composable
    fun AppTheme(
      @StyleRes styleRes: Int,
      context: Context,
      content: @Composable () -> Unit,
    ) {
    
      val themeReader = ThemeReader(context, styleRes)
      val colors = AppColors(
        primary = Color(themeReader.colorPrimary),
        secondary = Color(themeReader.colorSecondary),
      )
    
      CompositionLocalProvider(
        LocalColors provides colors,
        LocalStyleRes provides styleRes
      ) {
        content()
      }
    }
    

    还有一个AppColor 类:

    data class AppColors(
      val primary: Color,
      val secondary: Color,
    )
    
    internal val LocalColors: ProvidableCompositionLocal<AppColors> = staticCompositionLocalOf {
      AppColors(
        primary = Color.White,
        secondary = Color.White
      )
    }
    

    从 Xml 读取主题

    创建一个可以读取样式主题属性的ThemeReader 类。

    class ThemeReader(
      context: Context,
      @StyleRes styleRes: Int
    ) {
      private val attributes: IntArray = intArrayOf(R.attr.primary, R.attr.success, R.attr.error)
      private val typedArray: TypedArray = context.obtainStyledAttributes(styleRes, attributes)
    
      val colorPrimary: Int = typedArray.getColor(R.styleable.ThemeStyle_colorPrimary, -1)
      val colorSecondary: Int = typedArray.getColor(R.styleable.ThemeStyle_colorSecondary, -1)
    }
    

    加载 Xml Drawable

    为确保您加载的 xml drawable 知道您的主题颜色,您应该使用 Compose 加载您的 drawable,例如:

    Image(
      painter = painterResource(iconTypeResId),
      contentDescription = null
    )
    

    而不是以经典方式将其加载为 ImageView 并将其包装到 Compose AndroidView 中。让我们为此创建一个可组合的:

    @Composable
    fun XmlDrawable(
      @DrawableRes drawableResId: Int,
    ) {
      @StyleRes val styleRes: Int = AppTheme.style
      AndroidView(factory = { context ->
        val contextThemeWrapper = ContextThemeWrapper(context, styleRes)
        val drawable = ResourcesCompat.getDrawable(context.resources, drawableResId, contextThemeWrapper.theme)
    
        ImageView(context).apply { setImageDrawable(drawable) }
      })
    }
    

    现在我们可以应用我们所做的了。

    class YourActivity : AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        setContent {
          // instantiate our custom theme
          AppTheme(
            styleRes = R.style.RedTheme, // set the theme you wanna use
            context = requireContext()
          ) {
            XmlDrawable(drawableResId = R.drawable.your_drawable) // load the xml drawable
            // you can also access any AppColor within the composition tree like: AppTheme.colors.primary
          }
        }
      }
    }
    

    参考

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-27
      • 2018-02-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多