ColorDrawable源碼分析
ColorDrawable是Drawable子類中最簡單的,代表一種顏色圖。
在代碼中使用是非常簡單的。一般對於純色背景都可以使用ColorDrawable。
<?xml version="1.0" encoding="utf-8"?>
<color
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#0000ff">
</color>
這樣就定義了一個純藍色的背景
然後就可以在Java代碼中或者xml中使用
Drawable d = getResources().getDrawable(R.drawable.color_drawable);
Log.i(TAG, d.getClass().getSimpleName());//輸出ColorDrawable
在xml中,就是比如某個組件的background之類的屬性就可以把資源引用加上去,系統就會加載該資源
(一)前一篇對Drawable的分析中,有一個setColorFilter方法,可以改變顏色,那麼我們看一下到底是不是這麼回事?
float[] array = {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0};//顏色矩陣計算,從藍色轉變爲紅色
d.setColorFilter(new ColorMatrixColorFilter(array));
parent.setBackground(d);//重新設置背景色
執行上述代碼之後,發現顏色沒變,還是藍色!WTF?Drawable源碼裏面明明寫的是通過setColorFilter就可以改變顏色啊!那我們要使用ColorDrawable改變顏色怎麼辦?
parent.setBackground(new ColorDrawable(Color.RED));//成功變成紅色
顯然,新創建一個ColorDrawable當然沒問題,但是爲什麼setColorFilter沒有用呢?
//ColorDrawable開頭的註釋內容
A specialized Drawable that fills the Canvas with a specified color.
Note that a ColorDrawable ignores the ColorFilter.//會忽略ColorFilter
原來是這樣子,它會忽略ColorFilter的值,那麼到底是在哪裏處理的?因爲ColorFilter是設置在Paint上的,所以我們看一下子類的draw方法,可能會有什麼發現。
public void draw(Canvas canvas) {
// 獲取ColorFilter
final ColorFilter colorFilter = mPaint.getColorFilter();
// 判斷使用的顏色透明度是否爲0,如果爲0,則沒必要繪製背景了
// 這裏需要注意,如果動態設置顏色的時候沒有明確透明度,那麼這裏就是按照24位RGB來計算的,最後就是0!!!
if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
if (colorFilter == null) {
mPaint.setColorFilter(mTintFilter);
}
// 關鍵點在這裏啊,重新設置了顏色值,這樣就和ColorFilter無關了
mPaint.setColor(mColorState.mUseColor);
// 可以看到,ColorDrawable是按照矩形繪製的
canvas.drawRect(getBounds(), mPaint);
// Restore original color filter.
// 再把ColorFilter保存回來
mPaint.setColorFilter(colorFilter);
}
}
到這裏,我們就知道對於ColorDrawable爲什麼設置ColorFilter無效了。
(二)接下來看,ConstantState在這裏的子類實現,ColorState
int mBaseColor; // 基礎顏色,和透明度獨立
int mUseColor; // 會被透明度影響的基礎顏色
剛纔我們在draw方法裏面用到的也是mUseColor,因此,我們可以這樣理解:
mBaseColor是保存了set後的顏色
mUseColor是保存每次變化後的顏色
爲什麼這麼說呢?因爲從源碼中搜索可以看出,mBaseColor只有在setColor和updateFromTypedArray中才有更新
當顏色不一致時才設置並重繪自身,因此可以通過setColor的方式改變顏色
public void setColor(@ColorInt int color) {
if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {
mColorState.mBaseColor = mColorState.mUseColor = color;
invalidateSelf();
}
}
從xml中獲取屬性值
state.mBaseColor = a.getColor(R.styleable.ColorDrawable_color, state.mBaseColor);
那麼改變透明度就表示在mUseColor上面做動作麼?
public void setAlpha(int alpha) {
alpha += alpha >> 7; // make it 0..256
final int baseAlpha = mColorState.mBaseColor >>> 24;//無符號右移,所以前24位都是0,最後8位是透明度
final int useAlpha = baseAlpha * alpha >> 8;
final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
// 先左移8位去掉8位透明度,再無符號右移8位。
// 前8位0,後24爲RGB顏色,再或透明度左移24位,最後得到新的32位ARGB顏色
if (mColorState.mUseColor != useColor) {
mColorState.mUseColor = useColor;
invalidateSelf();
}
}
這麼一大段左右移運算到底在幹啥?爲啥不能簡單點?
useColor & 0xFFFFFF | alpha << 24//這樣不行麼?
說實話。。我沒看懂透明度那部分爲什麼要這麼計算。。Google的工程師還是天資聰穎
但是我們也可以看到,所有的改變都是在mUserColor上進行,mBaseColor是一個基準顏色。
(三)最關鍵的mutate方法,它到底做了什麼?
private boolean mMutated;//保存是否改變過的布爾值
public Drawable mutate() {
// 如果沒有改變過,並且是同一個Drawable(super.mutate方法直接返回this)
if (!mMutated && super.mutate() == this) {
// 可以看到直接新建了一個ColorState,這樣就不和其他ColorDrawable共享狀態,因此不會相互影響,相當於深拷貝
mColorState = new ColorState(mColorState);
// 標記已改變
mMutated = true;
}
// mColorState是成員變量,因此this是一個已經改變後的ColorDrawable
return this;
}
(四)那改變了之後還能不能複用呢?有沒有改變mMutated變量的方法呢?
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
// 可以看到該方法是可以清除標記位的,但是實際由於Hide,是無法調用的。所以一旦mutate調用了之後,就無法回頭了哦。
(五)那麼如果我想再創建一個一模一樣的ColorDrawable應該怎麼辦呢?
@Override
public Drawable newDrawable() {
return new ColorDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new ColorDrawable(this, res);
}
// 這個this指代的就是ColorState,因爲該方法是在ColorState類中定義的。
那麼在Java代碼中,就可以使用
d.getConstantState().newDrawable();
// 就可以創建一個和當前狀態一模一樣的ColorDrawable對象,但是他們還是共享一個ColorState哦。
對於最簡單的ColorDrawable需要了解的就這麼多了。下一節將討論比ColorDrawable稍微複雜一點的ShapeDrawable。敬請期待。