其实你不懂:Drawable着色(tint)的兼容方案 源码解析

前两天写一个自定义控件,使用Drawable变色来展示EditText的不同状态,涉及到了DrawableCompat这个类,今天着重分析一下它。

1:Drawable变色的通用代码

//1:通过图片资源文件生成Drawable实例
Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher).mutate();
//2:先调用DrawableCompat的wrap方法
drawable = DrawableCompat.wrap(drawable);
//3:再调用DrawableCompat的setTint方法,为Drawable实例进行着色
DrawableCompat.setTint(drawable, Color.RED);

这里涉及几个方法:

  • Drawable.mutate()
  • DrawableCompat.wrap(@NonNull Drawable drawable)
  • DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)

下面看一下这几个方法的源码,Drawable.mutate()稍后分析,先看一下DrawableCompat中的wrap和setTint这两个方法。

2:DrawableCompat.wrap(@NonNull Drawable drawable)

public static Drawable wrap(@NonNull Drawable drawable) {
  return IMPL.wrap(drawable);
}

从源码可见,wrap方法内部是return IMPL.wrap(drawable),那这个IMPL是?

    static final DrawableImpl IMPL;
    /**
     * Interface for the full API.
     */
    interface DrawableImpl {
        void jumpToCurrentState(Drawable drawable);
        void setAutoMirrored(Drawable drawable, boolean mirrored);
        boolean isAutoMirrored(Drawable drawable);
        void setHotspot(Drawable drawable, float x, float y);
        void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom);
        void setTint(Drawable drawable, int tint);
        void setTintList(Drawable drawable, ColorStateList tint);
        void setTintMode(Drawable drawable, PorterDuff.Mode tintMode);
        Drawable wrap(Drawable drawable);
        boolean setLayoutDirection(Drawable drawable, int layoutDirection);
        int getLayoutDirection(Drawable drawable);
        int getAlpha(Drawable drawable);
        void applyTheme(Drawable drawable, Resources.Theme t);
        boolean canApplyTheme(Drawable drawable);
        ColorFilter getColorFilter(Drawable drawable);
        void clearColorFilter(Drawable drawable);
        void inflate(Drawable drawable, Resources res, XmlPullParser parser, AttributeSet attrs,
                     Resources.Theme t) throws IOException, XmlPullParserException;
    }
    static {
        final int version = android.os.Build.VERSION.SDK_INT;
        if (version >= 23) {
            IMPL = new MDrawableImpl();
        } else if (version >= 21) {
            IMPL = new LollipopDrawableImpl();
        } else if (version >= 19) {
            IMPL = new KitKatDrawableImpl();
        } else if (version >= 17) {
            IMPL = new JellybeanMr1DrawableImpl();
        } else if (version >= 11) {
            IMPL = new HoneycombDrawableImpl();
        } else {
            IMPL = new BaseDrawableImpl();
        }
    }

可见,在不同的SDK版本下,IMPL对应DrawableImpl的不同子类实例。下面分别看一下这几个实现类对wrap方法的实质执行代码。

  • MDrawableImpl
    public Drawable wrap(Drawable drawable) {
    // No need to wrap on M+
    //未对Drawable实例做任何处理,直接返回
    return drawable;
    }
    可见在SDK版本>= 23(MDrawableImpl)情况下:DrawableCompat.wrap(@NonNull Drawable drawable)直接返回了原始的Drawable实例
  • LollipopDrawableImpl
    public Drawable wrap(Drawable drawable) {
    return DrawableCompatLollipop.wrapForTinting(drawable);
    }
    继续跟踪DrawableCompatLollipop.wrapForTinting:
    public static Drawable wrapForTinting(final Drawable drawable) {
    if (!(drawable instanceof TintAwareDrawable)) {
    //当前传入的Drawable实例并不属于TintAwareDrawable
    return new DrawableWrapperLollipop(drawable);
    }
    return drawable;
    }
    继续跟踪DrawableWrapperLollipop:
    class DrawableWrapperLollipop extends DrawableWrapperKitKat {
    DrawableWrapperLollipop(Drawable drawable) {
    super(drawable);
    }
    ***
    }
    继续跟踪DrawableWrapperKitKat:
    class DrawableWrapperKitKat extends DrawableWrapperHoneycomb {
    DrawableWrapperKitKat(Drawable drawable) {
    super(drawable);
    }
    ***
    }
    继续跟踪DrawableWrapperHoneycomb:
    class DrawableWrapperHoneycomb extends DrawableWrapperGingerbread {
    DrawableWrapperHoneycomb(Drawable drawable) {
    super(drawable);
    }
    ***
    }
    继续跟踪DrawableWrapperGingerbread:
    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
    private int mCurrentColor;
    private PorterDuff.Mode mCurrentMode;
    private boolean mColorFilterSet;
    DrawableWrapperState mState;  //mState默认是null
    private boolean mMutated;
    Drawable mDrawable;

    DrawableWrapperGingerbread(@Nullable Drawable dr) {
        mState = mutateConstantState();
        // Now set the drawable...
        setWrappedDrawable(dr);
    }
  • mState = mutateConstantState();
mutateConstantState()一路追踪到底:
    DrawableWrapperState mutateConstantState() {
        //返回一个DrawableWrapperStateBase实例
        //mState默认是null
        return new DrawableWrapperStateBase(mState, null);
    }
    private static class DrawableWrapperStateBase extends DrawableWrapperState {
        //调用父类 DrawableWrapperState 的构造函数
        //orig就是DrawableWrapperGingerbread中的mState,默认是null
        DrawableWrapperStateBase(
                @Nullable DrawableWrapperState orig, @Nullable Resources res) {
            super(orig, res);
        }
        @Override
        public Drawable newDrawable(@Nullable Resources res) {
            return new DrawableWrapperGingerbread(this, res);
        }
    }
    protected static abstract class DrawableWrapperState extends Drawable.ConstantState {
        int mChangingConfigurations;
        Drawable.ConstantState mDrawableState;
        ColorStateList mTint = null;
        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
        //orig就是DrawableWrapperGingerbread中的mState,默认是null
        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
            //因为orig是null,所以mChangingConfigurations,mDrawableState,
            //mTint,mTintMode都是DrawableWrapperState中的默认值
            if (orig != null) {
                mChangingConfigurations = orig.mChangingConfigurations;
                mDrawableState = orig.mDrawableState;
                mTint = orig.mTint;
                mTintMode = orig.mTintMode;
            }
        }
}

代码一路跟下来可见:mState = mutateConstantState(),mState被赋值为一个新的DrawableWrapperState实例
其中:mState(DrawableWrapperState)中,下面成员变量的值都是默认值:
int mChangingConfigurations;
Drawable.ConstantState mDrawableState;
ColorStateList mTint = null;
PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;

  • setWrappedDrawable(dr);
setWrappedDrawable(Drawable dr)一路追踪到底:
    public final void setWrappedDrawable(Drawable dr) {
        if (mDrawable != null) {
            mDrawable.setCallback(null);
        }
        mDrawable = dr;
        if (dr != null) {
            dr.setCallback(this);
            // Only call setters for data that's stored in the base Drawable.
            setVisible(dr.isVisible(), true);
            setState(dr.getState());
            setLevel(dr.getLevel());
            setBounds(dr.getBounds());
            //mState不为null:为一个新的DrawableWrapperState实例
            if (mState != null) {
                //为mState的mDrawableState赋值为Drawable原始实例
                //关联的ConstantState
                mState.mDrawableState = dr.getConstantState();
            }
        }
        invalidateSelf();
    }

这里涉及到DrawableWrapperGingerbread中的几个方法:
setVisible(boolean visible, boolean restart)
@Override
public boolean setVisible(boolean visible, boolean restart) {
//Drawable中的setVisible,用于控制Drawable实例是否执行动画,对于AnimationDrawable实例会产生效果,控制是否执行动画
return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart);
}
setState(final int[] stateSet)

    @Override
    public boolean setState(final int[] stateSet) {
        boolean handled = mDrawable.setState(stateSet);
        handled = updateTint(stateSet) || handled;
        return handled;
    }
    private boolean updateTint(int[] state) {
        //isCompatTintEnabled()这里直接返回了true
        if (!isCompatTintEnabled()) {
            // If compat tinting is not enabled, fail fast
            return false;
        }
        //mState.mTint是默认值:null
        final ColorStateList tintList = mState.mTint;
        //mState.mTintMode是默认值:DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN
        final PorterDuff.Mode tintMode = mState.mTintMode;
        if (tintList != null && tintMode != null) {
            //tintList为null,所以不会执行下面代码
            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
                setColorFilter(color, tintMode);
                mCurrentColor = color;
                mCurrentMode = tintMode;
                mColorFilterSet = true;
                return true;
            }
        } else {
            //tintList为null
            mColorFilterSet = false;
            //执行的是其父类Drawable的clearColorFilter()
            clearColorFilter();
        }
        return false;
    }
    protected boolean isCompatTintEnabled() {
        // It's enabled by default on Gingerbread
        //这里直接返回了true
        return true;
    }
Drawable的clearColorFilter方法:移除了当前Drawable实例关联的ColorFilter
    public void clearColorFilter() {
        setColorFilter(null);
    }

可见:setState(dr.getState())这一步直接移除了Drawable实例关联的ColorFilter.
**setLevel直接使用的是其父类Drawable中的方法setLevel(@IntRange(from=0,to=10000) int level) **

Drawable的setLevel方法:
    public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
        //在这里,因为mLevel就是之前DrawableWrapperGingerbread构造函数中的Drawable dr的level值,
        //而level=dr.getLevel()返回的也是Drawable dr的level值,mLevel == level,
        //所以下面的代码并不会执行
        if (mLevel != level) {
            mLevel = level;
            return onLevelChange(level);
        }
        return false;
    }

可见:setLevel(dr.getLevel())这一步并未产生实质影响,未执行处理逻辑。
setBounds直接使用的是其父类Drawable中的方法setBounds(@NonNull Rect bounds)

    /**
     * Specify a bounding rectangle for the Drawable. This is where the drawable
     * will draw when its draw() method is called.
     */
    public void setBounds(@NonNull Rect bounds) {
        setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
    }

可见:setBounds(dr.getBounds())这一步为新产生的DrawableWrapperGingerbread实例设置其绘制范围与原始Drawable实例一致。

可见在SDK版本>= 21(LollipopDrawableImpl)情况下:DrawableCompat.wrap(@NonNull Drawable drawable)返回了Drawable的子类DrawableWrapperGingerbread的一个新实例。
且在updateTint方法中移除了该新实例关联过的ColorFilter,设置了该新实例的绘制范围和原始Drawable实例相同

  • KitKatDrawableImpl
    跟踪终点同LollipopDrawableImpl
在KitKatDrawableImpl情况下,wrap(Drawable drawable)一路跟踪到底:
@Override
public Drawable wrap(Drawable drawable) {
    return DrawableCompatKitKat.wrapForTinting(drawable);
}
**
继承关系跟踪到最后还是DrawableWrapperGingerbread,和LollipopDrawableImpl相同:
DrawableWrapperGingerbread(@Nullable Drawable dr) {
    mState = mutateConstantState();
    // Now set the drawable...
    setWrappedDrawable(dr);
}
  • JellybeanMr1DrawableImpl
    跟踪终点同LollipopDrawableImpl
  • HoneycombDrawableImpl
    跟踪终点同LollipopDrawableImpl
  • BaseDrawableImpl
    跟踪终点同LollipopDrawableImpl

综上可见:
1:在SDK版本>= 23(MDrawableImpl)情况下:DrawableCompat.wrap(@NonNull Drawable drawable)直接返回了原始的Drawable实例;
2:其余情况下,DrawableCompat.wrap(@NonNull Drawable drawable)返回了Drawable的子类DrawableWrapperGingerbread的一个新实例,且在updateTint方法中移除了该新实例关联过的ColorFilter,设置了该新实例的绘制范围和原始Drawable实例相同;

3:DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)

    public static void setTint(@NonNull Drawable drawable, @ColorInt int tint) {
        IMPL.setTint(drawable, tint);
    }

之前分析wrap方法时候已经看到IMPL在不同SDK版本下有不同的实现,还是逐一查看:

  • MDrawableImpl
setTint一路跟踪:
@Override
public void setTint(Drawable drawable, int tint) {
    DrawableCompatLollipop.setTint(drawable, tint);
}
public static void setTint(Drawable drawable, int tint) {
    //执行的是Drawable原生的setTint方法
    drawable.setTint(tint);
}
  • LollipopDrawableImpl
setTint一路跟踪:
@Override
public void setTint(Drawable drawable, int tint) {
    //同MDrawableImpl
    DrawableCompatLollipop.setTint(drawable, tint);
}
  • KitKatDrawableImpl
setTint一路跟踪:
@Override
public void setTint(Drawable drawable, int tint) {
    DrawableCompatBase.setTint(drawable, tint);
}
public static void setTint(Drawable drawable, int tint) {
    if (drawable instanceof TintAwareDrawable) {
        ((TintAwareDrawable) drawable).setTint(tint);
    }
}
public interface TintAwareDrawable {
    void setTint(@ColorInt int tint);
    void setTintList(ColorStateList tint);
    void setTintMode(PorterDuff.Mode tintMode);
}

在上面分析DrawableCompat.wrap方法时候,已知其返回结果为DrawableWrapperGingerbread新实例,看一下DrawableWrapperGingerbread类的声明:class DrawableWrapperGingerbread extends Drawable implements Drawable.Callback, DrawableWrapper, TintAwareDrawable
由此可见,setTint实质执行的还是DrawableWrapperGingerbread的setTint方法,继续跟踪:

    setTint一路追踪:
    @Override
    public void setTint(int tint) {
        setTintList(ColorStateList.valueOf(tint));
    }
    @Override
    public void setTintList(ColorStateList tint) {
        //1:在上面分析DrawableCompat.wrap时候,mState的值如下:
        //mState(DrawableWrapperState)中,下面成员变量的值都是默认值:
        //int mChangingConfigurations;
        //Drawable.ConstantState mDrawableState;
        //ColorStateList mTint = null;
        //PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;

        //2:在DrawableCompat.setTint时候,mState.mTint不再为空值
        mState.mTint = tint;
        updateTint(getState());
    }

    //之前DrawableCompat.wrap已经执行过一次updateTint,
    //现在DrawableCompat.setTint第二次执行!!
    private boolean updateTint(int[] state) {
        //isCompatTintEnabled()返回true
        if (!isCompatTintEnabled()) {
            // If compat tinting is not enabled, fail fast
            return false;
        }
        //此时mState.mTint已经在setTintList中赋值不为null
        final ColorStateList tintList = mState.mTint;
        //mState.mTintMode依然为默认值不为null
        final PorterDuff.Mode tintMode = mState.mTintMode;
        if (tintList != null && tintMode != null) {
            //两者都不为空,因而执行if条件下代码

            //获取当前状态下对应的颜色
            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
            //mColorFilterSet默认是false
            //color即为setTint时候传入的颜色
            //mCurrentColor默认值是0
            //tintMode是mState中的mTintMode=DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN
            //mCurrentMode默认值是null
            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
                //对Drawable实例产生着色的,本质上还是执行了Drawable中的setColorFilter方法。
                setColorFilter(color, tintMode);
                mCurrentColor = color;
                mCurrentMode = tintMode;
                mColorFilterSet = true;
                return true;
            }
        } else {
            mColorFilterSet = false;
            clearColorFilter();
        }
        return false;
    }
  • JellybeanMr1DrawableImpl
    跟踪终点同KitKatDrawableImpl
  • HoneycombDrawableImpl
    跟踪终点同KitKatDrawableImpl
  • BaseDrawableImpl
    跟踪终点同KitKatDrawableImpl

综上可见:
1:在SDK版本>= 21(MDrawableImpl和LollipopDrawableImpl)情况下:DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)执行的是Drawable原生的setTint方法;
2:其余情况下,DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)本质上还是执行了Drawable中的setColorFilter方法;

4:原生Drawable.setTint(@ColorInt int tintColor)

public void setTint(@ColorInt int tintColor) {
    setTintList(ColorStateList.valueOf(tintColor));
}
public void setTintList(@Nullable ColorStateList tint) {
    //你没有看错,竟然是个空方法!!!!
}

刚看到这儿时候也有些纳闷,后来一想肯定是我们在获取Drawable原始实例的时,获取的其实是Drawable的子类实例,在Drawable子类里对setTintList做了重写,有图有真相:


setTintList重写.png

5:Drawable.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.
     *
     * @return This drawable.
     * @see ConstantState
     * @see #getConstantState()
     */
    public @NonNull Drawable mutate() {
        return this;
    }

单纯看源码解释可能比较抽象,说的通俗一点,我们通过Resource获取mipmap文件夹下的一张资源图片,在获取Drawable初始实例时候如果不使用mutate(),那么我们对这个Drawable进行着色,不仅改变了当前Drawable实例的颜色,以后任何通过这个图片获取到的Drawable实例,都会具有之前设置的颜色。所以如果我们对一张资源图片的着色不是APP全局生效的,就需要使用mutate()。

具体原因:
Android为了优化系统性能,同一张资源图片生成的Drawable实例在内存中只存在一份,在不使用mutate的情况下,修改任意Drawable都会全局发生变化。
使用mutate,Android系统也没有把Drawable实例又单独拷贝一份,仅仅是单独存放了状态值,很小的一部分数据,Drawable实例在内存中仍然保持1份,因而并不会影响系统的性能。
具体变化可以通过2张图片说明:
1:不使用mutate:

共享状态.png

2:使用mutate:
不共享状态.png

以上就是个人分析的一点结果,若有错误,请各位同学留言告知!

That's all !

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容