Android-Drawable setColorFilter方法踩坑

kboypkb發表於2021-09-09

原創-轉載請註明出處

Drawable mutations

有沒有遇到過這樣一種情況,我們要載入同一資源到兩個ImageView,但需要給其中一個資源改變顏色或者透明度。如下面的程式碼

    ImageView imageView1 = (ImageView) view.findViewById(R.id.imageview);
    ImageView imageView2 = (ImageView) view.findViewById(R.id.imageview2);

    Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
    drawable.setColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP);
    imageView.setImageDrawable(drawable);

    Drawable drawable1 = getResources().getDrawable(R.mipmap.ic_launcher);
    imageView2.setImageDrawable(drawable1);

我們給imageView1設定ColorFilter改變一下圖示的顏色,imageview2保持不變。這樣會發生什麼呢,看下面的圖:

圖片描述


這時候奇怪的事情發生了,兩個ImageView都被改變了顏色。這是因為Drawable使用在Android系統中使用範圍比較廣,系統對此作了最佳化,同一資源的drawable共享一個狀態,叫做ConstantState.例如,上面的R.mipmap.ic_launcher,每次新建一個drawable都是一個不同的drawable例項,但他們共享一個狀態,這個狀態中包含bitmap image,所以所有的R.mipmap.ic_launcher都共享一個bitmap,這就是兩個ImageView都改變顏色原因。

Because drawables are used so extensively throughout the system, Android optimizes them when they are loaded from resources. For instance, every time you create a Button, a new drawable is loaded from the framework resources (android.R.drawable.btn_default). This means all buttons across all the apps use a different drawable instance as their background. However, all these drawables share a common state, called the "constant state." The content of this state varies according to the type of drawable you are using, but it usually contains all the properties that can be defined by a resource. In the case of a button, the constant state contains a bitmap image. This way, all buttons across all applications share the same bitmap, which saves a lot of memory.(摘抄自)。如下圖所示

圖片描述

但有沒有辦法解決呢,Drawable提供了一個mutate方法,我們來看下mutate方法的註釋

Make this drawable mutable. This operation cannot be reversed. A mutable drawable is guaranteed to not share its state with any other drawable. This is especially useful when you need to modify properties of drawables loaded from resources. By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification. Calling this method on a mutable Drawable will have no effect.

mutate不知道怎麼翻譯合適,姑且叫做可變的吧。這個mutate方法使得這個drawable變成可變的,這個操作不可逆轉。呼叫了mutate方法,使得該drawable不和其他drawable共享狀態。
我們來看下這個mutate的實現,發現在Drawable原始碼中,mutate方法只是返回了他自身。那我們來看下drawable子類有沒有對該方法重寫。我們找到BitmapDrawable

     @Override
    public Drawable mutate() {        if (!mMutated && super.mutate() == this) {
            mBitmapState = new BitmapState(mBitmapState);
            mMutated = true;
        }        return this;
    }

如果沒有呼叫過mutate方法,會新建一個BitmapState,再將mMutated置為true。這樣相當於做了一次狀態的複製,就不會與其他drawable共享狀態了。
接下來我們修改下上面的程式碼

    ImageView imageView1 = (ImageView) view.findViewById(R.id.imageview);
    ImageView imageView2 = (ImageView) view.findViewById(R.id.imageview2);

    Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher).mutate();
    drawable.setColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP);
    imageView.setImageDrawable(drawable);

    Drawable drawable1 = getResources().getDrawable(R.mipmap.ic_launcher);
    imageView2.setImageDrawable(drawable1);

執行效果:

圖片描述


這次兩個ImageView不相同了.



作者:程式猿Jeffrey
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1916/viewspace-2821969/,如需轉載,請註明出處,否則將追究法律責任。