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
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
.
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>
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
)
}
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)
}
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
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With