Reputation: 1041
I work on application which supports multi themes, dark and light, with min sdk version 21. I found out that it's possible to use theme attribute (e.g. ?attr/logo_color) inside VectorDrawable.
So for example, If I set theme attribute to fill color of desired path
<vector ...>
<path
android:pathData="..."
android:fillColor="?attr/logo_color"/>
</vector>
or set theme attribute to tint whole vector
<vector android:tint="?attr/logo_color">
...
</vector>
I run the app (light theme), it sets color correctly, but when I change theme Activity.setTheme() (light to dark), color is not changed. Color is always 'cached' to previous theme's color. Interesting is that this doesn't work on lollipop and marshmallow, however on Android 10 it changes correctly.
On the other hand hand if I set android:tint="" color inside ImageView
<ImageView
...
app:srcCompat="@drawable/ic_logo"
android:tint="?attr/logo_colo"/>
It works with all versions but it of course change color of whole drawable.
Is it any bug or is it possible to use theme attributes inside VectorDrawable on lower apis with run time theme change?
Upvotes: 2
Views: 1188
Reputation: 11
Android API 30+ has caching of drawable resource. In ResourcesImpl we can find method clearAllCaches() but it's not available for call. Then we can find in Resources that this method invoke when change ResourcesLoader list. Any change like add or remove new ResourcesLoader invoke cache clean. As solution I can offer something like that:
object ResourceUpdater {
private val stateCache = mutableMapOf<Int, Boolean>()
private val mockedResourceLoader by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ResourcesLoader() else null
}
@JvmStatic
fun clearAllCaches(resources: Resources) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || mockedResourceLoader == null ) return
val resourcesHashCode = resources.hashCode()
val isAdded = stateCache.getOrPut(resourcesHashCode) { false }
if (isAdded) {
resources.removeLoaders(mockedResourceLoader)
stateCache[resourcesHashCode] = false
} else {
resources.addLoaders(mockedResourceLoader)
stateCache[resourcesHashCode] = true
}
}
}
In this solution we crate empty stub of ResourcesLoader and then add or remove dependent on current state of resources ResourcesLoader list. If call ResourceUpdater. clearAllCaches(resources) after theme change - it will clear drawable caches and colors will be correct.
Upvotes: 1
Reputation: 3576
Was facing the same issue, what I did was the following things
build.gradle
file (app level), set the following (Suggesting this point since you have not mentioned what is the minSdk you are supporting)android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
Instead of this (which is now deprecated)
context.getResources().getDrawable(/*Your resource id*/)
Use this
ContextCompat.getDrawable(context, /*Your resource id*/)
Explanation
: What the ContextCompat
class does is make sure that the drawable you get is complying to whatever theme is currently being used in your app (Android 5.0+)
If you support even below that, then you can use a ContextThemeWrapper
to wrap your current context and apply a specific theme to the drawable and then use it whenever you like
Good things to read
Jorge Castillo's Article about ContextThemeWrapper
Upvotes: 1