該篇是繼上兩篇文章分析的,主要分析了background到view顯示的流程,以及后面也分析了foregrounds在view上顯示的過程,后面也介紹了foreground顯示水波效果,以及如何自定義foreground的水波效果顏色,如果還沒有看前面兩節(jié)的內(nèi)容,大家先看看前兩節(jié)的內(nèi)容:
StateListDrawable的state初始化
還記得在第一篇介紹drawable顯示到view的過程說過,通過xml各種標(biāo)簽名生成drawable的時候,后面繼續(xù)調(diào)用了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);
//獲取完屬性之后,觸發(fā)state的改變
onStateChange(getState());
}
看注釋一,調(diào)用了inflateChildElements方法:
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attire
Theme theme) throws XmlPullParserException, IOException {
final StateListState state = mStateListState;
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
//遍歷里面的每一個item標(biāo)簽
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//如果里面的標(biāo)簽不是item直接跳出該處循環(huán)
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
final TypedArray a = obtainAttributes(r, theme, attire
R.styleable.StateListDrawableItem);
//如果當(dāng)前屬性值直接是一個drawable的話,而不是一個xml文件,直接返回drawable
Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
a.recycle();
//拿到item下面的各種狀態(tài)值
final int[] states = extractStateSet(attires
if (dr == null) {
//如果drawable屬性是單獨(dú)的xml文件,還得繼續(xù)去解析drawable下面的xml文件
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
//最后將不同的狀態(tài)加到StateListState里面
state.addStateSet(states, dr);
}
}
上面代碼是解析每一個item標(biāo)簽,如果標(biāo)簽里面drawable的值直接是一個值,而不是一個drawable的xml文件的時候直接返回dr,通過extraStateSet方法,將各個狀態(tài)下對應(yīng)的state是true或者false的狀態(tài)值獲取到:
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:
//通過屬性的布爾值,返回對應(yīng)state_***的整型值
states[j++] = attrs.getAttributeBooleanValue(i, false)
? stateResId : -stateResId;
}
}
states = StateSet.trimStateSet(states, j);
return states;
}
上面方法如果item標(biāo)簽里面有drawable或者是id的屬性,直接不要;如果獲取的state_*** 是true,那么返回它對應(yīng)的state_*** 對應(yīng)的id,如果為false,則返回它對應(yīng)的-id。
獲取到對應(yīng)的state_***的對應(yīng)的id之后放到數(shù)組states里面。如果上面定義的drawable值不能通過上面獲取到,那么通過Drawable.createFromXmlInner方法獲取。最后將各個state下對應(yīng)的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>
三種狀態(tài),對應(yīng)的都是colorDrawable,然后在布局中給view設(shè)置背景:
然后通過debug,可以看下代碼跟蹤的情況:
第一次獲取的dr是colorDrawable,然后我們看下對應(yīng)的狀態(tài)id:
對應(yīng)的id是16842919,這個怎么看對應(yīng)的state_*** 呢,我們可以android.R.attr下面找到對應(yīng)的state_*** 可以看到:
正好對應(yīng)的id是state_press的id。看到這里,第二次循環(huán)應(yīng)該是
state_selected對應(yīng)的id吧:
哈哈哈,還真的是
state_pressed對應(yīng)的id,第三次對應(yīng)的就是普通時候的state了。第三次對應(yīng)的states是空的,因?yàn)樵谡G闆r下在extractStateSet方法里面獲取的stateResId=0,因此直接跳出循環(huán)extractStateSet方法的循環(huán)了,所以下面通過日志打印到不同狀態(tài)下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方法,將不同狀態(tài)下對應(yīng)的drawable放到mDrawable[]數(shù)組里面,但是有人好奇了,為什么在打印日志里面,除了前面三個狀態(tài)下的drawable都是colorDrawable,而后面7個是為空呢,其實(shí)這個當(dāng)時我也很好奇,為什么輸出的長度是10個,翻開StateSet類發(fā)現(xiàn),所有關(guān)于state_***的屬性總共10個:
所以我在想是不是有什么地方做了mDrawable長度的限制,果然在
StateListDrawable.StateListState類調(diào)用addStateSet方法的時候,調(diào)用了父類DrawableContainer的addChild方法的時候有這么一句:
第一次addState的時候,mNumChildren=0,這個時候mDrawables.length=0,此時調(diào)用了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作為拷貝的數(shù)組,放到了mDrawables里面。所以印證了上面日志上輸出的長度=10的打印結(jié)果。
StateListDrawable的繪制過程
還記得我們在第一節(jié)android中drawable顯示到view上的過程的時候,說過view在action_down和action_up的時候會觸發(fā)drawable的setState方法:
public boolean setState(@NonNull final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
傳進(jìn)來的是當(dāng)前的state數(shù)組狀態(tài),mStateSet表示當(dāng)前drawable正在執(zhí)行的state,mStateSet默認(rèn)是一個空的數(shù)組,因此Arrays.equals(mStateSet, stateSet)肯定不相等,所以會進(jìn)到if,將stateSet賦給了mStateSet,回調(diào)給了onStateChange方法,drawable該方法下面是個空方法,因此可以看得出來狀態(tài)的改變交給了子類去完成:
@Override
protected boolean onStateChange(int[] stateSet) {
//調(diào)用了父類的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方法先是調(diào)用了父類的onStateChange方法,然后通過mStateListState.indexOfStateSet獲取到idx值,最后調(diào)用了父類的selectDrawable(idx)方法,通過日志我們在按下的時候獲取到日志如下:
通過android.R.attr文件找到了對應(yīng)的state_***:
在
mStateListState.indexOfStateSet中做的工作是如果找到了StateListDrawable中和傳過來的state有對應(yīng)關(guān)系,直接返回StateListDrawable.StateListState的mStateSets二維數(shù)組的索引。這里可以看到對應(yīng)的ids=0:
我們可以做個驗(yàn)證,將xml中的state_pressed狀態(tài)放在后面的位置,再來看下日志:
這里我把pressed的item放到了第二個位置,然后通過debug日志繼續(xù)可以看到:
看到了吧,獲取到的idx=1,正好對應(yīng)了selector里面第二個item,也就是state_pressed對應(yīng)的位置。
這里提個醒哈,如果將默認(rèn)的item放到了第二個位置,按下的item放到第三個位置,按下的時候直接不顯示按下的drawable了,為啥呢,這是因?yàn)?br>
int idx = mStateListState.indexOfStateSet(stateSet);這句返回的idx=1,正好返回的drawable是第二個位置的item,所以按下的時候,還是顯示正常情況下的drawable。
上面最后調(diào)用了selectDrawable方法,該方法在父類里面:
public boolean selectDrawable(int index) {
if (index == mCurIndex) {
return false;
}
final long now = SystemClock.uptimeMillis();
//默認(rèn)情況下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);
}
//實(shí)際上看這里就行,index始終是>=0的,并且mDrawableContainerState.mNumChildren=10
if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
//獲取到當(dāng)前的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;
}
//默認(rèn)是等于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);
}
//這里會觸發(fā)自己的draw方法
invalidateSelf();
return true;
}
上面這么多的代碼,其實(shí)只需要看這段邏輯就行:
if (index >= 0 && index < mDrawableContainerState.mNumChildren),可以看到將當(dāng)前獲取到的drawable賦值給了mCurrDrawable變量,在最后觸發(fā)了invalidateSelf方法,如果看過我寫的第一節(jié)android中drawable顯示到view上的過程,一定會知道,最后會觸發(fā)到StateListDrawable的draw方法,draw方法在DrawContainer里面:
@Override
public void draw(Canvas canvas) {
if (mCurrDrawable != null) {
mCurrDrawable.draw(canvas);
}
if (mLastDrawable != null) {
mLastDrawable.draw(canvas);
}
}
說白了,正常情況下按下和抬起的時候,用到了上面數(shù)組中的第二個和第三個colorDrawable,將drawable賦值給了mCurrDrawable,所以最終會繪制成mCurrDrawable的樣子。
在上面selectDrawable方法中有這么一句if (mDrawableContainerState.mExitFadeDuration > 0),該if可以通過xml或者setExitFadeDuration方法來實(shí)現(xiàn):
英語不好的筒子們,可以看下翻譯該方法啥意思:在drawable離開時淡入淡出的時間間隔
可以看下設(shè)置該屬性之后的效果,這是按下一會兒和松開的時候效果:
通過該效果分析下過程,回到剛才的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;
//設(shè)置動畫維持的時間
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}
//默認(rèn)是等于0的
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
//動畫執(zhí)行的地方
animate(true);
invalidateSelf();
}
};
} else {
//釋放任務(wù)
unscheduleSelf(mAnimationRunnable);
}
//此處會觸發(fā)動畫執(zhí)行
animate(true);
}
//這里會觸發(fā)自己的draw方法
invalidateSelf();
return true;
}
如果設(shè)置了mDrawableContainerState.mExitFadeDuration > 0,mCurrDrawable是正常state下的drawable,因此會將currentDrawable賦值給lastDrawable,此時mExitAnimationEnd就是我們設(shè)置的淡入淡出的時間,緊接著就是在animate方法里面觸發(fā)mAnimationRunnable的執(zhí)行:
void animate(boolean schedule) {
mHasAlpha = true;
final long now = SystemClock.uptimeMillis();
boolean animating = false;
//此處是設(shè)置drawable進(jìn)入的時候動畫,如果設(shè)置了enterAnimationEnd屬性才會走這里
//實(shí)際上進(jìn)入的動畫是不斷改變mCurrDrawable的動畫,而此時mLastDrawable是空的
//所以可以想象下,當(dāng)按下的時候,按下的drawable的alpha從0到255
//當(dāng)抬起的時候,正常的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 {
//實(shí)際上就是不斷改變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是正常狀態(tài)下的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會從淡到顯示。當(dāng)抬起的時候,此時currentDrawable是正常情況的drawable,因此會出現(xiàn)正常drawable從淡到顯示。
enterFadeDuration設(shè)置要顯示的drawable的淡入時間。
exitFadeDuration設(shè)置當(dāng)前drawable離開的淡出時間。
關(guān)于enterFadeDuration在上面例子中沒有演示,大家可以自己嘗試該屬性。
上面例子中,currentDrawable和lastDrawable實(shí)際都是colorDrawable,因?yàn)槲覀冊诶又卸x的drawable只是一個顏色值,因此咱們看看colorDrawable實(shí)際繪制的過程,在說colorDrawable的繪制之前,我們先來回憶下在第一節(jié)講android中drawable顯示到view上的過程,applyBackgroundTint方法當(dāng)時提過是用來著色用的,其實(shí)它的實(shí)質(zhì)是通過paint.setColorFilter(new PorterDuffColorFilter(color,mode))來實(shí)現(xiàn)的,下面先度娘看下paint.setColorFilter能做些啥事:
setColorFilter
ColorFiler一共有三個子類,分別是ColorMatrixColorFilter、LightingColorFilter、PorterDuffColorFilter:
ColorMatrixColorFilter
是一個顏色矩陣器,它需要一個ColorMatrix對象,ColorMatrix需要設(shè)置顏色矩陣,下面通過一個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));
// 設(shè)置畫筆顏色為自定義顏色
// 繪制圓環(huán) (x坐標(biāo),y坐標(biāo),半徑,畫筆)
canvas.drawCircle(240, 600 / 2, 200, mPaint);
}
}
例子中的顏色值是#666666,顏色值R=102,G=102,B=102,A=255,
在colorMatrix運(yùn)算的時候,矩陣第一行算出來的結(jié)果是R的值,算法過程是每一行的每一個數(shù)分別與顏色值相乘然后相加。所以第一個矩陣算出來的結(jié)果:R=51,G=51,B=51,A=255,對應(yīng)顏色的16進(jìn)制是#333333;在第二個矩陣中,每一行每一個數(shù)相乘的顏色位正好是原來的102的值,因此第二個顏色矩陣算出來的值還是原來的顏色值。 這里借用網(wǎng)上一張圖:
下面來介紹下在bitmap情況下,ColorMatrix是怎么工作的,先上一個demo看下,圖片就用android studio自帶的:
這里寫了一個紅色通道的colorMatrix,意思是如果你想讓圖片偏向哪個顏色,對應(yīng)的通道就盡量大點(diǎn),最后一列表示圖片的飽和度:
這里寫了幾種情況,第一個是原始圖片,第二個是紅色通道生成的,第三個是紅色通道+100的飽和度生成的,第四個是藍(lán)色通道生成的,第五個是綠色通道生成的,第六個透明度通道生成的,透明度是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,
});
//藍(lán)色通道
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));
// 設(shè)置畫筆顏色為自定義顏色
// 繪制圓環(huán) (x坐標(biāo),y坐標(biāo),半徑,畫筆)
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將通道設(shè)置偏向紅色通道
setSaturation設(shè)置飽和度
setScale設(shè)置各個通道的縮放比
setRotate
該注釋說明如果參數(shù)axis=0,設(shè)置紅色通道旋轉(zhuǎn)的角度,axis=1,設(shè)置綠色通道旋轉(zhuǎn)的角度,axis=2,設(shè)置藍(lán)色通道的旋轉(zhuǎn)角度。下面也是將每個api跑了一遍demo:
//圖二
ColorMatrix colorMatrix1 = new ColorMatrix();
colorMatrix1.setSaturation(100);//設(shè)置每個通道的飽和度
//圖三
ColorMatrix colorMatrix2 = new ColorMatrix();
colorMatrix2.setRGB2YUV();//設(shè)置偏向紅色通道
//圖四
ColorMatrix colorMatrix3 = new ColorMatrix();
colorMatrix3.setYUV2RGB();//也是偏向紅色通道
//圖五
ColorMatrix colorMatrix4 = new ColorMatrix();
colorMatrix4.setScale(50, 50, 50, 50);//指定每一個通道放大的倍數(shù)
//圖六,因?yàn)槭菄@G通道,圍繞那個通道旋轉(zhuǎn),就偏向旋轉(zhuǎn)的通道
ColorMatrix colorMatrix5 = new ColorMatrix();
colorMatrix5.setRotate(0, 180);//圍繞某一個通道進(jìn)行旋轉(zhuǎn)多少度,1.是圍繞G通道,0.是圍繞R通道,2.是圍繞B通道
關(guān)于ColorMatrix就說這么多,大家只要記住計(jì)算公式就行,需要偏向那個通道,就設(shè)置那個通道的值偏大,其余的通道就不用管或者調(diào)小。
LightingColorFilter
是一個光照顏色過濾器,他只有一個帶兩個參數(shù)的構(gòu)造器,第一個參數(shù)是色彩倍增,第二個參數(shù)是色彩增加,兩個參數(shù)都是16進(jìn)制的顏色值,下面看看如何使用:
//光照colorFilter
//第一個參數(shù)的顏色值,通過ARGB每兩位設(shè)置通道的倍數(shù)
//第二個參數(shù)設(shè)置每一個通道的偏移量
//下面應(yīng)該可以看出來繪制的顏色是偏向R通道的,因此呈現(xiàn)出紅色
LightingColorFilter lightingColorFilter = new LightingColorFilter(0x66660000, 0x00000000);
mPaint.setColorFilter(null);
mPaint.setColorFilter(lightingColorFilter);
canvas.drawBitmap(bitmap, 300, 300, mPaint);
很簡單,如果你想讓顏色偏向什么通道顏色,直接將通道顏色值設(shè)置大點(diǎn),偏移量參數(shù)也可以適當(dāng)?shù)脑O(shè)置相應(yīng)的通道。關(guān)于LightingColorFilter就說這么多,基本知道兩個參數(shù)的含義就ok。
PorterDuffColorFilter
它是我們今天drawable中用到的著色器colorFilter,PorterDuffColorFilter提供了兩個參數(shù),第一個參數(shù)是16進(jìn)制顏色值,第二個參數(shù)是PorterDuff.Mode,表示顏色混合模式,而在模式里面又分為dst和src,src表示當(dāng)前PorterDuffColorFilter第一個參數(shù)的顏色,dst表示在繪制用到paint時候的圖案,下面的demo以繪制bitmap作為dat:
//繪制的顏色是src,dst是圖片區(qū)域
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中真實(shí)案例用到了PorterDuffColorFilter的繪圖重疊,其實(shí)在imageView中的setColorFilter正是用到PorterDuffColorFilter的api,最終調(diào)用了drawable.setColorFilter:
上面運(yùn)行出來的結(jié)果是項(xiàng)目中用到
PorterDuffColorFilter的特性,通過設(shè)置mode為MULTIPLY,顏色值為Color.GRAY,MULTIPLY模式表示取src和dst的交集,并且src在上面,dst在下面,通過兩者的復(fù)合達(dá)到遮罩效果。該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);
}
}
關(guān)于其他的mode就都不嘗試了,大家自己根據(jù)下面的mode圖自己嘗試?yán)L制能體會到:
其實(shí)在android中paint.setXmode方法中,也有此mode的應(yīng)用,關(guān)于paint.setXmode的應(yīng)用,我在仿蘋果版小黃車(ofo)app主頁菜單效果中有使用到,大家如果喜歡該文章,可以關(guān)注下文章。
好了,關(guān)于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));
}
默認(rèn)getColorFilter()肯定不是PorterDuffColorFilter類型的,所以會走setColorFilter(new PorterDuffColorFilter(color, mode)),因此順著找到對應(yīng)的setColorFilter,該方法在drawable中是個抽象的方法,因此可以看下colorDrawable里面的setColorFilter方法:
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
好吧,如此簡單調(diào)用了piant.setColorFilter,最終會由view觸發(fā)到drawable的繪制。
TODO
- 本來是想將StateListDrawable和
RippleDrawable放在一起講的,限于篇幅,將RippleDrawable放到后面部分說水波效果的實(shí)現(xiàn),以及如何實(shí)現(xiàn)定義view的時候水波效果。 - ColorMatrixColorFilter延伸講解。
如果還不熟悉drawable在view上的顯示流程還不熟悉,請看之前的兩節(jié)內(nèi)容:
);s,s,