该篇是继上两篇文章分析的,主要分析了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 attire
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, attire
R.styleable.StateListDrawableItem);
//如果当前属性值直接是一个drawable的话,而不是一个xml文件,直接返回drawable
Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
a.recycle();
//拿到item下面的各种状态值
final int[] states = extractStateSet(attires
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个:
所以我在想是不是有什么地方做了mDrawable长度的限制,果然在
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)方法,通过日志我们在按下的时候获取到日志如下:
通过android.R.attr文件找到了对应的state_***:
在
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方法来实现:
英语不好的筒子们,可以看下翻译该方法啥意思:在drawable离开时淡入淡出的时间间隔
可以看下设置该属性之后的效果,这是按下一会儿和松开的时候效果:
通过该效果分析下过程,回到刚才的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
该注释说明如果参数axis=0,设置红色通道旋转的角度,axis=1,设置绿色通道旋转的角度,axis=2,设置蓝色通道的旋转角度。下面也是将每个api跑了一遍demo:
//图二
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作为dat:
//绘制的颜色是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上的显示流程还不熟悉,请看之前的两节内容:
);s,s,