前两天写一个自定义控件,使用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
可见在SDK版本>= 23(MDrawableImpl)情况下:DrawableCompat.wrap(@NonNull Drawable drawable)直接返回了原始的Drawable实例public Drawable wrap(Drawable drawable) { // No need to wrap on M+ //未对Drawable实例做任何处理,直接返回 return 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();
代码一路跟下来可见:mState = mutateConstantState(),mState被赋值为一个新的DrawableWrapperState实例,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(DrawableWrapperState)中,下面成员变量的值都是默认值:
int mChangingConfigurations;
Drawable.ConstantState mDrawableState;
ColorStateList mTint = null;
PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; - setWrappedDrawable(dr);
这里涉及到DrawableWrapperGingerbread中的几个方法: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(); }
setVisible(boolean visible, boolean restart)setState(final int[] stateSet)@Override public boolean setVisible(boolean visible, boolean restart) { //Drawable中的setVisible,用于控制Drawable实例是否执行动画,对于AnimationDrawable实例会产生效果,控制是否执行动画 return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart); }可见:setState(dr.getState())这一步直接移除了Drawable实例关联的ColorFilter.@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); }
setLevel直接使用的是其父类Drawable中的方法setLevel(@IntRange(from=0,to=10000) int level)可见:setLevel(dr.getLevel())这一步并未产生实质影响,未执行处理逻辑。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; }
setBounds直接使用的是其父类Drawable中的方法setBounds(@NonNull Rect bounds)可见:setBounds(dr.getBounds())这一步为新产生的DrawableWrapperGingerbread实例设置其绘制范围与原始Drawable实例一致。/** * 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); }
- mState = mutateConstantState();
可见在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做了重写,有图有真相:

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:

2:使用mutate:

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