前兩天寫一個自定義控件,使用Drawable變色來展示EditText的不同狀態(tài)鳍征,涉及到了DrawableCompat這個類,今天著重分析一下它。
1:Drawable變色的通用代碼
//1:通過圖片資源文件生成Drawable實例
Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher).mutate();
//2:先調(diào)用DrawableCompat的wrap方法
drawable = DrawableCompat.wrap(drawable);
//3:再調(diào)用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方法內(nèi)部是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的不同子類實例蛾方。下面分別看一下這幾個實現(xiàn)類對wrap方法的實質(zhì)執(zhí)行代碼。
-
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);
}
繼續(xù)跟蹤DrawableCompatLollipop.wrapForTinting:
public static Drawable wrapForTinting(final Drawable drawable) {
if (!(drawable instanceof TintAwareDrawable)) {
//當前傳入的Drawable實例并不屬于TintAwareDrawable
return new DrawableWrapperLollipop(drawable);
}
return drawable;
}
繼續(xù)跟蹤DrawableWrapperLollipop:
class DrawableWrapperLollipop extends DrawableWrapperKitKat {
DrawableWrapperLollipop(Drawable drawable) {
super(drawable);
}
***
}
繼續(xù)跟蹤DrawableWrapperKitKat:
class DrawableWrapperKitKat extends DrawableWrapperHoneycomb {
DrawableWrapperKitKat(Drawable drawable) {
super(drawable);
}
***
}
繼續(xù)跟蹤DrawableWrapperHoneycomb:
class DrawableWrapperHoneycomb extends DrawableWrapperGingerbread {
DrawableWrapperHoneycomb(Drawable drawable) {
super(drawable);
}
***
}
繼續(xù)跟蹤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 {
//調(diào)用父類 DrawableWrapperState 的構造函數(shù)
//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原始實例
//關聯(lián)的ConstantState
mState.mDrawableState = dr.getConstantState();
}
}
invalidateSelf();
}
這里涉及到DrawableWrapperGingerbread中的幾個方法:
setVisible(boolean visible, boolean restart)
@Override
public boolean setVisible(boolean visible, boolean restart) {
//Drawable中的setVisible沉删,用于控制Drawable實例是否執(zhí)行動畫,對于AnimationDrawable實例會產(chǎn)生效果,控制是否執(zhí)行動畫
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,所以不會執(zhí)行下面代碼
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;
//執(zhí)行的是其父類Drawable的clearColorFilter()
clearColorFilter();
}
return false;
}
protected boolean isCompatTintEnabled() {
// It's enabled by default on Gingerbread
//這里直接返回了true
return true;
}
Drawable的clearColorFilter方法:移除了當前Drawable實例關聯(lián)的ColorFilter
public void clearColorFilter() {
setColorFilter(null);
}
可見:setState(dr.getState())這一步直接移除了Drawable實例關聯(lián)的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構造函數(shù)中的Drawable dr的level值矾瑰,
//而level=dr.getLevel()返回的也是Drawable dr的level值,mLevel == level隘擎,
//所以下面的代碼并不會執(zhí)行
if (mLevel != level) {
mLevel = level;
return onLevelChange(level);
}
return false;
}
可見:setLevel(dr.getLevel())這一步并未產(chǎn)生實質(zhì)影響殴穴,未執(zhí)行處理邏輯。
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())這一步為新產(chǎn)生的DrawableWrapperGingerbread實例設置其繪制范圍與原始Drawable實例一致货葬。
可見在SDK版本>= 21(LollipopDrawableImpl)情況下:DrawableCompat.wrap(@NonNull Drawable drawable)返回了Drawable的子類DrawableWrapperGingerbread的一個新實例采幌。
且在updateTint方法中移除了該新實例關聯(lián)過的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方法中移除了該新實例關聯(lián)過的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方法時候已經(jīng)看到IMPL在不同SDK版本下有不同的實現(xiàn),還是逐一查看:
- MDrawableImpl
setTint一路跟蹤:
@Override
public void setTint(Drawable drawable, int tint) {
DrawableCompatLollipop.setTint(drawable, tint);
}
public static void setTint(Drawable drawable, int tint) {
//執(zhí)行的是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實質(zhì)執(zhí)行的還是DrawableWrapperGingerbread的setTint方法江咳,繼續(xù)跟蹤:
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已經(jīng)執(zhí)行過一次updateTint爹土,
//現(xiàn)在DrawableCompat.setTint第二次執(zhí)行!踩身!
private boolean updateTint(int[] state) {
//isCompatTintEnabled()返回true
if (!isCompatTintEnabled()) {
// If compat tinting is not enabled, fail fast
return false;
}
//此時mState.mTint已經(jīng)在setTintList中賦值不為null
final ColorStateList tintList = mState.mTint;
//mState.mTintMode依然為默認值不為null
final PorterDuff.Mode tintMode = mState.mTintMode;
if (tintList != null && tintMode != null) {
//兩者都不為空胀茵,因而執(zhí)行if條件下代碼
//獲取當前狀態(tài)下對應的顏色
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實例產(chǎn)生著色的,本質(zhì)上還是執(zhí)行了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)執(zhí)行的是Drawable原生的setTint方法宰掉;
2:其余情況下,DrawableCompat.setTint(@NonNull Drawable drawable, @ColorInt int tint)本質(zhì)上還是執(zhí)行了Drawable中的setColorFilter方法赁濒;
4:原生Drawable.setTint(@ColorInt int tintColor)
public void setTint(@ColorInt int tintColor) {
setTintList(ColorStateList.valueOf(tintColor));
}
public void setTintList(@Nullable ColorStateList tint) {
//你沒有看錯,竟然是個空方法C虾Α>苎住!挨务!
}
剛看到這兒時候也有些納悶击你,后來一想肯定是我們在獲取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為了優(yōu)化系統(tǒng)性能,同一張資源圖片生成的Drawable實例在內(nèi)存中只存在一份佛舱,在不使用mutate的情況下椎例,修改任意Drawable都會全局發(fā)生變化。
使用mutate请祖,Android系統(tǒng)也沒有把Drawable實例又單獨拷貝一份粟矿,僅僅是單獨存放了狀態(tài)值,很小的一部分數(shù)據(jù)损拢,Drawable實例在內(nèi)存中仍然保持1份陌粹,因而并不會影響系統(tǒng)的性能。
具體變化可以通過2張圖片說明:
1:不使用mutate:
2:使用mutate:
以上就是個人分析的一點結果福压,若有錯誤掏秩,請各位同學留言告知!
That's all !