該篇是繼上兩篇文章分析的,主要分析了background到view顯示的流程,以及後面也分析了foregrounds在view上顯示的過程,後面也介紹了foreground顯示水波效果,以及如何自定義foreground的水波效果顏色,如果還沒有看前面兩節的內容,大家先看看前兩節的內容:
StateListDrawable的state初始化
還記得在第一篇介紹drawable顯示到view的過程說過,通過xml各種標籤名生成drawable的時候,後面繼續呼叫了drawable的inflate方法吧,我們們就順著這個方向看下inflate方法做了啥,直接看StateListDrawable下面的inflate方法:
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
updateStateFromTypedArray(a);
updateDensity(r);
a.recycle();
//方法很重要,用來獲取xml中的屬性
inflateChildElements(r, parser, attrs, theme);
//獲取完屬性之後,觸發state的改變
onStateChange(getState());
}
複製程式碼
看註釋一,呼叫了inflateChildElements方法:
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final StateListState state = mStateListState;
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
//遍歷裡面的每一個item標籤
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//如果裡面的標籤不是item直接跳出該處迴圈
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.StateListDrawableItem);
//如果當前屬性值直接是一個drawable的話,而不是一個xml檔案,直接返回drawable
Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
a.recycle();
//拿到item下面的各種狀態值
final int[] states = extractStateSet(attrs);
if (dr == null) {
//如果drawable屬性是單獨的xml檔案,還得繼續去解析drawable下面的xml檔案
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
//最後將不同的狀態加到StateListState裡面
state.addStateSet(states, dr);
}
}
複製程式碼
上面程式碼是解析每一個item標籤,如果標籤裡面drawable的值直接是一個值,而不是一個drawable的xml檔案的時候直接返回dr,通過extraStateSet方法,將各個狀態下對應的state是true或者false的狀態值獲取到:
int[] extractStateSet(AttributeSet attrs) {
int j = 0;
final int numAttrs = attrs.getAttributeCount();
int[] states = new int[numAttrs];
for (int i = 0; i < numAttrs; i++) {
final int stateResId = attrs.getAttributeNameResource(i);
switch (stateResId) {
case 0:
break;
//如果屬性是drawable或者id直接不要
case R.attr.drawable:
case R.attr.id:
continue;
default:
//通過屬性的布林值,返回對應state_***的整型值
states[j++] = attrs.getAttributeBooleanValue(i, false)
? stateResId : -stateResId;
}
}
states = StateSet.trimStateSet(states, j);
return states;
}
複製程式碼
上面方法如果item標籤裡面有drawable或者是id的屬性,直接不要;如果獲取的state_*** 是true,那麼返回它對應的state_*** 對應的id,如果為false,則返回它對應的-id。
獲取到對應的state_***的對應的id之後放到陣列states裡面。如果上面定義的drawable值不能通過上面獲取到,那麼通過Drawable.createFromXmlInner方法獲取。最後將各個state下對應的drawable,新增到StateListState
裡面,StateListState
是StateListDrawable
的子類,它將每一個state下的drawable儲存下來,放到父類的mDrawables
變數裡面,將state放到mStateSets裡面,下面我們通過一個例子來說說上面的api:
寫了一個selector的drawable,名字是test_back.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorAccent" android:state_pressed="true" />
<item android:drawable="@color/colorAccent" android:state_selected="true" />
<item android:drawable="@color/colorPrimary" />
</selector>
複製程式碼
三種狀態,對應的都是colorDrawable,然後在佈局中給view設定背景:
然後通過debug,可以看下程式碼跟蹤的情況: 第一次獲取的dr是colorDrawable,然後我們看下對應的狀態id: 對應的id是16842919,這個怎麼看對應的state_*** 呢,我們可以android.R.attr下面找到對應的state_*** 可以看到: 正好對應的id是state_press的id。看到這裡,第二次迴圈應該是state_selected
對應的id吧:
哈哈哈,還真的是state_pressed
對應的id,第三次對應的就是普通時候的state了。第三次對應的states
是空的,因為在正常情況下在extractStateSet
方法裡面獲取的stateResId=0
,因此直接跳出迴圈extractStateSet
方法的迴圈了,所以下面通過日誌列印到不同狀態下state的drawable如下:
final View view2 = findViewById(R.id.view2);
Drawable background = view2.getBackground();
StateListDrawable.DrawableContainerState constantState =
(StateListDrawable.DrawableContainerState) background.getConstantState();
Drawable[] children = constantState.getChildren();
for (int i = 0; i < children.length; i++) {
Drawable child = children[i];
if (child instanceof ColorDrawable) {
ColorDrawable colorDrawable = (ColorDrawable) child;
int color = colorDrawable.getColor();
Log.d(TAG, "color:" + toHexEncoding(color));
} else {
Log.d(TAG, "drawable:" + children[i]);
}
}
複製程式碼
在color.xml中定義的幾個顏色值如下:
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
複製程式碼
所以更加說明了,在StateListDrawable.StateListState
類通過addStateSet
方法,將不同狀態下對應的drawable放到mDrawable[]陣列裡面,但是有人好奇了,為什麼在列印日誌裡面,除了前面三個狀態下的drawable都是colorDrawable,而後面7個是為空呢,其實這個當時我也很好奇,為什麼輸出的長度是10個,翻開StateSet類發現,所有關於state_***的屬性總共10個:
StateListDrawable.StateListState
類呼叫addStateSet
方法的時候,呼叫了父類DrawableContainer
的addChild
方法的時候有這麼一句:
第一次addState的時候,mNumChildren=0,這個時候mDrawables.length=0,此時呼叫了growArray方法:
public void growArray(int oldSize, int newSize) {
//newSize=10,oldSize=0
Drawable[] newDrawables = new Drawable[newSize];
//arraycopy是將mDrawables拷貝到newDrawables裡面,所以此時mDrawables的長度=10
System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
mDrawables = newDrawables;
}
複製程式碼
從這裡不難看出,最終是將長度=10的newDrawables作為拷貝的陣列,放到了mDrawables裡面。所以印證了上面日誌上輸出的長度=10的列印結果。
StateListDrawable的繪製過程
還記得我們在第一節android中drawable顯示到view上的過程的時候,說過view在action_down和action_up的時候會觸發drawable的setState
方法:
public boolean setState(@NonNull final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
複製程式碼
傳進來的是當前的state陣列狀態,mStateSet
表示當前drawable正在執行的state,mStateSet預設是一個空的陣列,因此Arrays.equals(mStateSet, stateSet)肯定不相等,所以會進到if,將stateSet賦給了mStateSet,回撥給了onStateChange
方法,drawable該方法下面是個空方法,因此可以看得出來狀態的改變交給了子類去完成:
@Override
protected boolean onStateChange(int[] stateSet) {
//呼叫了父類的onStateChange方法
final boolean changed = super.onStateChange(stateSet);
//通過傳來的state在R檔案中的int值來獲取idx
int idx = mStateListState.indexOfStateSet(stateSet);
if (idx < 0) {
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}
return selectDrawable(idx) || changed;
}
//父類的onStateChange方法
@Override
protected boolean onStateChange(int[] state) {
//剛開始mLastDrawable和mCurrDrawable為空
if (mLastDrawable != null) {
return mLastDrawable.setState(state);
}
if (mCurrDrawable != null) {
return mCurrDrawable.setState(state);
}
return false;
}
複製程式碼
可以看到上面onStateChange
方法先是呼叫了父類的onStateChange
方法,然後通過mStateListState.indexOfStateSet
獲取到idx值,最後呼叫了父類的selectDrawable(idx)方法,通過日誌我們在按下的時候獲取到日誌如下:
mStateListState.indexOfStateSet
中做的工作是如果找到了StateListDrawable中和傳過來的state有對應關係,直接返回StateListDrawable.StateListState
的mStateSets
二維陣列的索引。
這裡可以看到對應的ids=0:
我們可以做個驗證,將xml中的state_pressed狀態放在後面的位置,再來看下日誌:
這裡我把pressed的item放到了第二個位置,然後通過debug日誌繼續可以看到:
看到了吧,獲取到的idx=1,正好對應了selector裡面第二個item,也就是state_pressed對應的位置。
這裡提個醒哈,如果將預設的item放到了第二個位置,按下的item放到第三個位置,按下的時候直接不顯示按下的drawable了,為啥呢,這是因為
int idx = mStateListState.indexOfStateSet(stateSet);
這句返回的idx=1,正好返回的drawable是第二個位置的item,所以按下的時候,還是顯示正常情況下的drawable。
上面最後呼叫了selectDrawable
方法,該方法在父類裡面:
public boolean selectDrawable(int index) {
if (index == mCurIndex) {
return false;
}
final long now = SystemClock.uptimeMillis();
//預設情況下mDrawableContainerState.mExitFadeDuration=0
if (mDrawableContainerState.mExitFadeDuration > 0) {
if (mLastDrawable != null) {
mLastDrawable.setVisible(false, false);
}
if (mCurrDrawable != null) {
mLastDrawable = mCurrDrawable;
mLastIndex = mCurIndex;
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}
//實際上看這裡就行,index始終是>=0的,並且mDrawableContainerState.mNumChildren=10
if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
//獲取到當前的drawable
final Drawable d = mDrawableContainerState.getChild(index);
//將d賦給mCurrDrawable
mCurrDrawable = d;
mCurIndex = index;
if (d != null) {
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
}
initializeDrawableForDisplay(d);
}
} else {
mCurrDrawable = null;
mCurIndex = -1;
}
//預設是等於0的
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
animate(true);
invalidateSelf();
}
};
} else {
unscheduleSelf(mAnimationRunnable);
}
// Compute first frame and schedule next animation.
animate(true);
}
//這裡會觸發自己的draw方法
invalidateSelf();
return true;
}
複製程式碼
上面這麼多的程式碼,其實只需要看這段邏輯就行:
if (index >= 0 && index < mDrawableContainerState.mNumChildren)
,可以看到將當前獲取到的drawable賦值給了mCurrDrawable
變數,在最後觸發了invalidateSelf
方法,如果看過我寫的第一節android中drawable顯示到view上的過程,一定會知道,最後會觸發到StateListDrawable的draw方法,draw方法在DrawContainer裡面:
@Override
public void draw(Canvas canvas) {
if (mCurrDrawable != null) {
mCurrDrawable.draw(canvas);
}
if (mLastDrawable != null) {
mLastDrawable.draw(canvas);
}
}
複製程式碼
說白了,正常情況下按下和抬起的時候,用到了上面陣列中的第二個和第三個colorDrawable,將drawable賦值給了mCurrDrawable,所以最終會繪製成mCurrDrawable的樣子。
在上面selectDrawable
方法中有這麼一句if (mDrawableContainerState.mExitFadeDuration > 0)
,該if可以通過xml或者setExitFadeDuration方法來實現:
可以看下設定該屬性之後的效果,這是按下一會兒和鬆開的時候效果:
通過該效果分析下過程,回到剛才的selectDrawable方法,public boolean selectDrawable(int index) {
if (index == mCurIndex) {
return false;
}
final long now = SystemClock.uptimeMillis();
//此時會走這裡
if (mDrawableContainerState.mExitFadeDuration > 0) {
if (mLastDrawable != null) {
mLastDrawable.setVisible(false, false);
}
//第二次mCurrDrawable不為空,
if (mCurrDrawable != null) {
mLastDrawable = mCurrDrawable;
mLastIndex = mCurIndex;
//設定動畫維持的時間
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}
//預設是等於0的
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
//動畫執行的地方
animate(true);
invalidateSelf();
}
};
} else {
//釋放任務
unscheduleSelf(mAnimationRunnable);
}
//此處會觸發動畫執行
animate(true);
}
//這裡會觸發自己的draw方法
invalidateSelf();
return true;
}
複製程式碼
如果設定了mDrawableContainerState.mExitFadeDuration > 0,mCurrDrawable
是正常state下的drawable,因此會將currentDrawable賦值給lastDrawable,此時mExitAnimationEnd
就是我們設定的淡入淡出的時間,緊接著就是在animate
方法裡面觸發mAnimationRunnable
的執行:
void animate(boolean schedule) {
mHasAlpha = true;
final long now = SystemClock.uptimeMillis();
boolean animating = false;
//此處是設定drawable進入的時候動畫,如果設定了enterAnimationEnd屬性才會走這裡
//實際上進入的動畫是不斷改變mCurrDrawable的動畫,而此時mLastDrawable是空的
//所以可以想象下,當按下的時候,按下的drawable的alpha從0到255
//當抬起的時候,正常的drawable也會從0到255
if (mCurrDrawable != null) {
if (mEnterAnimationEnd != 0) {
if (mEnterAnimationEnd <= now) {
mCurrDrawable.setAlpha(mAlpha);
mEnterAnimationEnd = 0;
} else {
int animAlpha = (int)((mEnterAnimationEnd-now)*255)
/ mDrawableContainerState.mEnterFadeDuration;
mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
animating = true;
}
}
} else {
mEnterAnimationEnd = 0;
}
if (mLastDrawable != null) {
if (mExitAnimationEnd != 0) {
if (mExitAnimationEnd <= now) {
mLastDrawable.setVisible(false, false);
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
} else {
//實際上就是不斷改變mLastDrawable的透明度,透明度到了mExitAnimationEnd的時候就為0,
//所以到了最後就只能看到mCurrDrawable
int animAlpha = (int)((mExitAnimationEnd-now)*255)
/ mDrawableContainerState.mExitFadeDuration;
mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
animating = true;
}
}
} else {
mExitAnimationEnd = 0;
}
if (schedule && animating) {
scheduleSelf(mAnimationRunnable, now + 1000 / 60);
}
}
複製程式碼
上面如果mExitAnimationEnd>0
,lastDrawable是正常狀態下的drawable,此時lastDrawable的alpha值從255到0,看到的效果就是按下的時候正常的drawable(lastDrawable)慢慢地變淡,等now到了mExitAnimationEnd
時候,按下的drawable才會顯示。而抬起的時候,此時lastDrawable是按下的drawable,此時lastDrawable的alpha值從255到0,所以看到的效果就是抬起時按下的drawable慢慢變淡。
如果mEnterAnimationEnd>0
,此時lastDrawable為空,只繪製currentDrawable,而此時currentDrawable的alpha值從0到255,所以按下的時候,按下的drawable會從淡到顯示。當抬起的時候,此時currentDrawable是正常情況的drawable,因此會出現正常drawable從淡到顯示。
enterFadeDuration
設定要顯示的drawable的淡入時間。
exitFadeDuration
設定當前drawable離開的淡出時間。
關於enterFadeDuration
在上面例子中沒有演示,大家可以自己嘗試該屬性。
上面例子中,currentDrawable和lastDrawable實際都是colorDrawable,因為我們在例子中定義的drawable只是一個顏色值,因此我們們看看colorDrawable實際繪製的過程,在說colorDrawable的繪製之前,我們先來回憶下在第一節講android中drawable顯示到view上的過程,applyBackgroundTint
方法當時提過是用來著色用的,其實它的實質是通過paint.setColorFilter(new PorterDuffColorFilter(color,mode))來實現的,下面先度娘看下paint.setColorFilter能做些啥事:
setColorFilter
ColorFiler一共有三個子類,分別是ColorMatrixColorFilter
、LightingColorFilter
、PorterDuffColorFilter
:
ColorMatrixColorFilter
是一個顏色矩陣器,它需要一個ColorMatrix
物件,ColorMatrix
需要設定顏色矩陣,下面通過一個demo來說明該問題:
public class ColorFilterView extends View {
private static final String TAG = ColorFilterView.class.getSimpleName();
Paint mPaint;
public ColorFilterView(Context context) {
this(context, null);
}
public ColorFilterView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ColorFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
int color = Color.parseColor("#666666");//R=102,G=102,B=102,A=255
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
Log.d(TAG, "red:" + red);
Log.d(TAG, "green:" + green);
Log.d(TAG, "blue:" + blue);
Log.d(TAG, "alpha:" + Color.alpha(color));
mPaint.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw");
// 生成色彩矩陣
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
0.5f, 0, 0, 0, 0,//R=0.5*102+0*102+0*102+0*255+0=51
0, 0.5f, 0, 0, 0,//G=0*102+0.5*102+0*102+0*255+0=51
0, 0, 0.5f, 0, 0,//B=0*102+0*102+0.5*102+0*255+0=51
0, 0, 0, 1, 0,//B=0*102+0*102+0*102+1*255+0=255
});
ColorMatrix colorMatrix1 = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,//R=1*102+0*102+0*102+0*255+0=102
0, 1, 0, 0, 0,//G=0*102+1*102+0*102+0*255+0=102
0, 0, 1, 0, 0,//B=0*102+0*102+1*102+0*255+0=102
0, 0, 0, 1, 0,//A=0*102+0*102+0*102+1*255+0=255
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
// 設定畫筆顏色為自定義顏色
// 繪製圓環 (x座標,y座標,半徑,畫筆)
canvas.drawCircle(240, 600 / 2, 200, mPaint);
}
}
複製程式碼
例子中的顏色值是#666666,顏色值R=102,G=102,B=102,A=255, 在colorMatrix運算的時候,矩陣第一行算出來的結果是R的值,演算法過程是每一行的每一個數分別與顏色值相乘然後相加。所以第一個矩陣算出來的結果:R=51,G=51,B=51,A=255,對應顏色的16進位制是#333333;在第二個矩陣中,每一行每一個數相乘的顏色位正好是原來的102的值,因此第二個顏色矩陣算出來的值還是原來的顏色值。 這裡借用網上一張圖:
下面來介紹下在bitmap情況下,ColorMatrix是怎麼工作的,先上一個demo看下,圖片就用android studio自帶的:
這裡寫了一個紅色通道的colorMatrix,意思是如果你想讓圖片偏向哪個顏色,對應的通道就儘量大點,最後一列表示圖片的飽和度: 這裡寫了幾種情況,第一個是原始圖片,第二個是紅色通道生成的,第三個是紅色通道+100的飽和度生成的,第四個是藍色通道生成的,第五個是綠色通道生成的,第六個透明度通道生成的,透明度是0.5。//紅色通道
ColorMatrix colorMatrixR = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 1, 0,
});
//綠色通道
ColorMatrix colorMatrixG = new ColorMatrix(new float[]{
0, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 1, 0,
});
//紅色通道+100的飽和度
ColorMatrix colorMatrixRB = new ColorMatrix(new float[]{
1, 0, 0, 0, 100,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 1, 0,
});
//藍色通道
ColorMatrix colorMatrixB = new ColorMatrix(new float[]{
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});
//透明度0.5的通道
ColorMatrix colorMatrixA = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 0.5f, 0,
});
// LightingColorFilter lightingColorFilter = new LightingColorFilter(0x0000ff, 0x000000);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixR));
// 設定畫筆顏色為自定義顏色
// 繪製圓環 (x座標,y座標,半徑,畫筆)
canvas.drawCircle(240, 600 / 2, 200, mPaint);
canvas.drawBitmap(bitmap, 100, 100, null);
canvas.drawBitmap(bitmap, 300, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixRB));
canvas.drawBitmap(bitmap, 500, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixB));
canvas.drawBitmap(bitmap, 700, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixG));
canvas.drawBitmap(bitmap, 900, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixA));
canvas.drawBitmap(bitmap, 100, 300, mPaint);
複製程式碼
colorMatrix其他的幾個方法:
setRGB2YUV
將通道設定偏向紅色通道
setSaturation
設定飽和度
setScale
設定各個通道的縮放比
setRotate
//圖二
ColorMatrix colorMatrix1 = new ColorMatrix();
colorMatrix1.setSaturation(100);//設定每個通道的飽和度
//圖三
ColorMatrix colorMatrix2 = new ColorMatrix();
colorMatrix2.setRGB2YUV();//設定偏向紅色通道
//圖四
ColorMatrix colorMatrix3 = new ColorMatrix();
colorMatrix3.setYUV2RGB();//也是偏向紅色通道
//圖五
ColorMatrix colorMatrix4 = new ColorMatrix();
colorMatrix4.setScale(50, 50, 50, 50);//指定每一個通道放大的倍數
//圖六,因為是圍繞G通道,圍繞那個通道旋轉,就偏向旋轉的通道
ColorMatrix colorMatrix5 = new ColorMatrix();
colorMatrix5.setRotate(0, 180);//圍繞某一個通道進行旋轉多少度,1.是圍繞G通道,0.是圍繞R通道,2.是圍繞B通道
複製程式碼
關於ColorMatrix
就說這麼多,大家只要記住計算公式就行,需要偏向那個通道,就設定那個通道的值偏大,其餘的通道就不用管或者調小。
LightingColorFilter
是一個光照顏色過濾器,他只有一個帶兩個引數的構造器,第一個引數是色彩倍增,第二個引數是色彩增加,兩個引數都是16進位制的顏色值,下面看看如何使用:
//光照colorFilter
//第一個引數的顏色值,通過ARGB每兩位設定通道的倍數
//第二個引數設定每一個通道的偏移量
//下面應該可以看出來繪製的顏色是偏向R通道的,因此呈現出紅色
LightingColorFilter lightingColorFilter = new LightingColorFilter(0x66660000, 0x00000000);
mPaint.setColorFilter(null);
mPaint.setColorFilter(lightingColorFilter);
canvas.drawBitmap(bitmap, 300, 300, mPaint);
複製程式碼
很簡單,如果你想讓顏色偏向什麼通道顏色,直接將通道顏色值設定大點,偏移量引數也可以適當的設定相應的通道。關於LightingColorFilter
就說這麼多,基本知道兩個引數的含義就ok。
PorterDuffColorFilter
它是我們今天drawable中用到的著色器colorFilter,PorterDuffColorFilter
提供了兩個引數,第一個引數是16進位制顏色值,第二個引數是PorterDuff.Mode
,表示顏色混合模式,而在模式裡面又分為dst和src,src表示當前PorterDuffColorFilter第一個引數的顏色,dst表示在繪製用到paint時候的圖案,下面的demo以繪製bitmap作為dst:
//繪製的顏色是src,dst是圖片區域
PorterDuffColorFilter srcPorterDuffColorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC);
mPaint.setColorFilter(null);
mPaint.setColorFilter(srcPorterDuffColorFilter);
canvas.drawBitmap(bitmap, 500, 300, mPaint);
//DST模式下只顯示圖片
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST);
mPaint.setColorFilter(null);
mPaint.setColorFilter(porterDuffColorFilter);
canvas.drawBitmap(bitmap, 700, 300, mPaint);
複製程式碼
下面來看看android中真實案例用到了PorterDuffColorFilter
的繪圖重疊,其實在imageView中的setColorFilter正是用到PorterDuffColorFilter的api,最終呼叫了drawable.setColorFilter:
PorterDuffColorFilter
的特性,通過設定mode為MULTIPLY
,顏色值為Color.GRAY
,MULTIPLY
模式表示取src和dst的交集,並且src在上面,dst在下面,通過兩者的複合達到遮罩效果。該demo樣式是在recyclerView的holder中使用的,部分程式碼如下:
public class ManagerBookShelfHolder extends ViewHolderImpl<BookShelfItem> {
private ImageView bookCoverimg;
private TextView bookName;
private ImageView select;
@Override
protected int getItemLayoutId() {
return R.layout.book_shelf_item;
}
@Override
public void initView() {
bookCoverimg = findById(R.id.bookCoverimg);
bookName = findById(R.id.bookName);
select = findById(R.id.select);
}
@Override
public void onBind(BookShelfItem data, int pos) {
Glide.with(getContext()).load(data.bookCoverimg).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
bookCoverimg.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
return false;
}
}).into(bookCoverimg);
if (data.select) {
select.setImageResource(R.mipmap.select);
bookCoverimg.clearColorFilter();
} else {
bookCoverimg.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
select.setImageResource(R.mipmap.un_select);
}
bookName.setText(data.bookName);
select.setVisibility(View.VISIBLE);
}
}
複製程式碼
關於其他的mode就都不嘗試了,大家自己根據下面的mode圖自己嘗試繪製能體會到:
其實在android中paint.setXmode方法中,也有此mode的應用,關於paint.setXmode的應用,我在仿蘋果版小黃車(ofo)app主頁選單效果中有使用到,大家如果喜歡該文章,可以關注下文章。
好了,關於ColorFilter
就說這麼多,我們再簡單的回到drawable的setColorFilter看下:
public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
if (getColorFilter() instanceof PorterDuffColorFilter) {
PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
if (existing.getColor() == color && existing.getMode() == mode) {
return;
}
}
setColorFilter(new PorterDuffColorFilter(color, mode));
}
複製程式碼
預設getColorFilter()肯定不是PorterDuffColorFilter型別的,所以會走setColorFilter(new PorterDuffColorFilter(color, mode))
,因此順著找到對應的setColorFilter
,該方法在drawable中是個抽象的方法,因此可以看下colorDrawable裡面的setColorFilter
方法:
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
複製程式碼
好吧,如此簡單呼叫了piant.setColorFilter,最終會由view觸發到drawable的繪製。
TODO
- 本來是想將StateListDrawable和
RippleDrawable
放在一起講的,限於篇幅,將RippleDrawable
放到後面部分說水波效果的實現,以及如何實現定義view的時候水波效果。 - ColorMatrixColorFilter延伸講解。
如果還不熟悉drawable在view上的顯示流程還不熟悉,請看之前的兩節內容: