elementstyle
elementstyle

Reputation: 1041

Theme attributes not working correctly with VectorDrawable

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

Answers (2)

dalapenko
dalapenko

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

gtxtreme
gtxtreme

Reputation: 3576

Was facing the same issue, what I did was the following things

  1. In the 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
    }
}
  1. If you are setting such drawables to any kind of views during run time or dynamically then

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

Related Questions