Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to match color attributes between theme and icon in Jetpack Compose?

I have a vector drawable which has two paths with different attributes referencing to different theme colors.

And these attributes' values are being changed by different theme, how to achieve the same in 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 with different themes, as an example

<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>

Depending which theme is currently used, vector drawable or icon can have different colors

like image 782
Akbolat SSS Avatar asked Oct 20 '25 05:10

Akbolat SSS


1 Answers

TL;DR

Applying a theme to a drawable within compose can be a bit tricky as Xml drawables can not access color definitions in Jetpack Compose. Therefore you should define your colors and themes in Xml and make Jetpack Compose aware of those values.

Use AndroidView within Compose for loading an 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) }
})

This way the drawable will respect the theme style you are passing to ContextThemeWrapper.

Define a Theme in Xml

Along with your defined themes RedTheme and GreenTheme you also need to declare those attributes: You also need to define the style attributes:

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

Map Theme to Compose

Define an AppTheme class:

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()
  }
}

And a AppColor class:

data class AppColors(
  val primary: Color,
  val secondary: Color,
)

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

Read Theme from Xml

Create a ThemeReader class that can read attributes for a style theme.

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)
}

Loading the Xml Drawable

To ensure that your loaded xml drawable is aware of your theme colors you should not load your drawable with Compose like:

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

rather than loading it the classic way as an ImageView and wrapping it into Compose AndroidView. Lets create a composable for this:

@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) }
  })
}

Now we can apply what we have done.

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
      }
    }
  }
}

Reference

  • How to create a custom theme in Jetpack Compose
like image 126
ChristianB Avatar answered Oct 21 '25 18:10

ChristianB



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!