在 Android 自定義控件中帜篇,Xfermode 知識(shí)點(diǎn)占有很重要的地位糙捺,它能幫助我們實(shí)現(xiàn)很多炫酷的效果。例如笙隙,實(shí)現(xiàn)各種形狀的圖片控件洪灯;結(jié)合屬性動(dòng)畫(huà)實(shí)現(xiàn)漸變效果。
Xfermode 介紹
Xfermode 主要是通過(guò) paint.setXfermode(Xfermode xfermode) 方法進(jìn)行設(shè)置的竟痰,其中 在 API 28 中签钩, Xfermode 類只有一個(gè)子類 PorterDuffXfermode
PorterDuffXfermode 構(gòu)造函數(shù):
public PorterDuffXfermode(PorterDuff.Mode mode)
參數(shù) mode 設(shè)置不同的混合模式,取值有以下這幾種:
Xfermode 使用方法
通常情況下坏快,需要關(guān)閉硬件加速 setLayerType(View.LAYER_TYPE_SOFTWARE, null); 然后在自定義控件的 onDraw() 方法中铅檩,保存至新的圖層中,先繪制 dest 圖像莽鸿,然后再設(shè)置 paint.setXfermode(new PorterDuffXfermode(getMode(mode))); 接著繪制 src 圖像柠并,這樣畫(huà)筆就應(yīng)用上了指定的模式了。主要流程如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//將繪制操作保存到新的圖層臼予,因?yàn)閳D像合成是很昂貴的操作,將用到硬件加速啃沪,這里將圖像合成的處理放到離屏緩存中進(jìn)行
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);
// 繪制 dest
canvas.drawBitmap(destBitmap, 0, 0, paint);
// 設(shè)置 xfermode
if (mode != 0) {
paint.setXfermode(new PorterDuffXfermode(getMode(mode)));
}
// 繪制 src
canvas.drawBitmap(srcBitmap, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(saveCount);
}
官方 Sample 測(cè)試
先準(zhǔn)備兩種圖片素材粘拾,dest 圖像為 紅色方塊 圖片,src 圖像為 藍(lán)色方塊 圖片(純?yōu)橥傅讏D)
新建自定義控件類 XfermodeBitmapView 创千,繼承 View, 在 onDraw() 方法先繪制 dest 圖像缰雇,然后將 paint xfermode 設(shè)置為 指定的模式,再繪制 src 圖像追驴。
public class XfermodeBitmapView extends View {
private Paint textPaint;
private Paint paint;
private int mode;
private Bitmap destBitmap;
private Bitmap srcBitmap;
public XfermodeBitmapView(Context context) {
this(context, null);
}
public XfermodeBitmapView(Context context, AttributeSet attrs) {
super(context, attrs);
readAttrs(context, attrs);
init();
}
private void readAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XfermodeView);
mode = typedArray.getInt(R.styleable.XfermodeView_mode, 0);
typedArray.recycle();
}
private void init() {
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(60);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
destBitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.red);
srcBitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.blue);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 設(shè)置背景
canvas.drawColor(Color.DKGRAY);
//將繪制操作保存到新的圖層械哟,因?yàn)閳D像合成是很昂貴的操作,將用到硬件加速殿雪,這里將圖像合成的處理放到離屏緩存中進(jìn)行
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);
// 繪制 dest
canvas.drawBitmap(destBitmap, 0, 0, paint);
// 設(shè)置 xfermode
if (mode != 0) {
paint.setXfermode(new PorterDuffXfermode(getMode(mode)));
}
// 繪制 src
canvas.drawBitmap(srcBitmap, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(saveCount);
canvas.drawText(getMode(mode).toString(), getWidth() - 300, getHeight() / 2f, textPaint);
}
private PorterDuff.Mode getMode(int value) {
PorterDuff.Mode mode = null;
switch (value) {
case 1:
mode = PorterDuff.Mode.CLEAR;
break;
case 2:
mode = PorterDuff.Mode.SRC;
break;
case 3:
mode = PorterDuff.Mode.DST;
break;
case 4:
mode = PorterDuff.Mode.SRC_OVER;
break;
case 5:
mode = PorterDuff.Mode.DST_OVER;
break;
case 6:
mode = PorterDuff.Mode.SRC_IN;
break;
case 7:
mode = PorterDuff.Mode.DST_IN;
break;
case 8:
mode = PorterDuff.Mode.SRC_OUT;
break;
case 9:
mode = PorterDuff.Mode.DST_OUT;
break;
case 10:
mode = PorterDuff.Mode.SRC_ATOP;
break;
case 11:
mode = PorterDuff.Mode.DST_ATOP;
break;
case 12:
mode = PorterDuff.Mode.XOR;
break;
case 13:
mode = PorterDuff.Mode.DARKEN;
break;
case 14:
mode = PorterDuff.Mode.LIGHTEN;
break;
case 15:
mode = PorterDuff.Mode.MULTIPLY;
break;
case 16:
mode = PorterDuff.Mode.SCREEN;
break;
}
return mode;
}
}
效果:
Xfermode 實(shí)現(xiàn)高亮進(jìn)度 ImageView
實(shí)現(xiàn)思路:
(1) 顯示圖片暇咆,繼承 ImageView 類 ,更方便丙曙。
(2) 圓角矩形圖片爸业,通過(guò) canvas.clipPath() 裁剪 canvas 畫(huà)布(在 super.onDraw() 之前調(diào)用),繪制的圖片就會(huì)顯示成為圓角矩形亏镰。
(3) 圖片上的灰色蒙層和圓形鏤空扯旷,通過(guò) Xfermode 模式,先繪制 dst 鏤空?qǐng)A索抓,在繪制 src 灰色蒙層钧忽,并將 paint 設(shè)置為 srcOut , 這樣 dst 鏤空?qǐng)A和灰色蒙層重疊的部分就會(huì)變成透明了,顯示出了底層的圖片
/**
* 高亮進(jìn)度 ImageView
*/
public class HighlightProgressImageView extends AppCompatImageView {
private Paint backgroundPaint;
private Paint circlePaint;
private int radius;
private int width;
private int height;
private int roundCorner;
private Path clipPath;
private RectF pathRectF;
private RectF circleRectF;
private RectF backgroundRectF;
private PorterDuffXfermode porterDuffXfermode;
private AnimatorSet animatorSet;
private ValueAnimator angleAnimator;
private ValueAnimator scaleAnimator;
// 扇形角度
private int angle;
// 縮放半徑
private float scaleRadius = radius;
private boolean needDrawArc = true;
public HighlightProgressImageView(Context context) {
this(context, null);
}
public HighlightProgressImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setColor(getResources().getColor(R.color.translucentGray));
backgroundPaint.setStyle(Paint.Style.FILL);
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(getResources().getColor(android.R.color.white));
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeWidth(DensityUtil.dp2Px(getContext(), 8));
radius = DensityUtil.dp2Px(getContext(), 40);
roundCorner = DensityUtil.dp2Px(getContext(), 10);
clipPath = new Path();
porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
pathRectF = new RectF(0, 0, width, height);
clipPath.addRoundRect(pathRectF, roundCorner, roundCorner, Path.Direction.CCW);
circleRectF = new RectF(-radius, -radius, radius, radius);
backgroundRectF = new RectF(-width / 2, -height / 2, width / 2f, height / 2f);
}
/**
* 繪制步驟: 先繪制 圓逼肯, 再在圓上繪制灰色背景耸黑,繪制灰色背景時(shí),將 Xfermode 設(shè)置為 PorterDuff.Mode.SRC_OUT汉矿, 這樣重疊的部分就會(huì)變?yōu)橥该髌榉唬@示出正常的圖片
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
// 通過(guò) path, 裁剪 canvas 畫(huà)布
canvas.clipPath(clipPath);
// 繪制圖片
super.onDraw(canvas);
//將繪制操作保存到新的圖層,因?yàn)閳D像合成是很昂貴的操作洲拇,將用到硬件加速奈揍,這里將圖像合成的處理放到離屏緩存中進(jìn)行
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), backgroundPaint, Canvas.ALL_SAVE_FLAG);
canvas.translate(width / 2f, height / 2f);
// if (needDrawArc) {
// 繪制 dst 圓環(huán)
circlePaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(0, 0, radius, circlePaint);
// 繪制 dst 扇形
circlePaint.setStyle(Paint.Style.FILL);
canvas.drawArc(circleRectF, -90, angle, true, circlePaint);
// }
circlePaint.setStyle(Paint.Style.FILL);
// 繪制 dst 圓
canvas.drawCircle(0, 0, scaleRadius, circlePaint);
// 設(shè)置 Xfermode 為 SRC_OUT
backgroundPaint.setXfermode(porterDuffXfermode);
// 繪制 src 圖片上層的灰色蒙層
canvas.drawRoundRect(backgroundRectF, roundCorner, roundCorner, backgroundPaint);
backgroundPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
}
/**
* 開(kāi)啟動(dòng)畫(huà)
*/
public void start() {
startAnimator();
}
/**
* 停止動(dòng)畫(huà)
*/
public void stop() {
if (animatorSet != null) {
animatorSet.cancel();
animatorSet = null;
}
}
private void startAnimator() {
// 扇形進(jìn)度動(dòng)畫(huà)
if (angleAnimator == null) {
angleAnimator = ValueAnimator.ofInt(0, 360);
// angleAnimator.setDuration(2000);
angleAnimator.setInterpolator(new LinearInterpolator());
angleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
angle = (int) animation.getAnimatedValue();
invalidate();
}
});
angleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
needDrawArc = false;
}
});
}
if (scaleAnimator == null) {
scaleAnimator = ValueAnimator.ofFloat(radius, width > height ? width : height);
// scaleAnimator.setDuration(2000);
scaleAnimator.setInterpolator(new LinearInterpolator());
scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
scaleRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
}
if (animatorSet == null) {
animatorSet = new AnimatorSet();
animatorSet.setDuration(2000);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
animatorSet.playSequentially(angleAnimator, scaleAnimator);
}
animatorSet.start();
}
}
效果:
Xfermode 實(shí)現(xiàn)心狀圖片
實(shí)現(xiàn)思路:
(1) 繼承自 ImageView 類,先繪制圖片作為 dest 圖像赋续,此時(shí)的畫(huà)筆 paint 需要是 ImageView 圖片的畫(huà)筆男翰,不能是 重新創(chuàng)建的新畫(huà)筆;
(2) 設(shè)置畫(huà)筆 Xfermode 模式為 SRC_IN 纽乱,并利用 path 貝塞爾曲線繪制一個(gè)心形蛾绎,這樣心形和圖片重合的部分就保留顯示了心形部分的圖片。
/**
* 心形圖片
*/
public class XfermodeHeartShapeImageView extends android.support.v7.widget.AppCompatImageView {
private int mViewWidth;
private int mViewHeight;
private Paint paint;
private PorterDuffXfermode xfermode;
private float radius;
private Path path;
public XfermodeHeartShapeImageView(Context context) {
this(context, null);
}
public XfermodeHeartShapeImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
path = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
radius = Math.min(mViewWidth, mViewHeight) / 3f;
// 獲取繪制圖片對(duì)應(yīng)的 paint
paint = ((BitmapDrawable) getDrawable()).getPaint();
// 二階貝塞爾曲線
path.moveTo(mViewWidth / 2f, mViewHeight / 4f);
path.cubicTo(mViewWidth / 10f, mViewHeight / 12f,
mViewWidth / 9f, (mViewHeight * 3) / 5f,
mViewWidth / 2f, (mViewHeight * 5) / 6f);
path.cubicTo(
mViewWidth * 8 / 9f, (mViewHeight * 3) / 5f,
mViewWidth * 9 / 10f, mViewHeight / 12f,
mViewWidth / 2f, mViewHeight / 4f);
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
@Override
protected void onDraw(Canvas canvas) {
int saveCount = canvas.saveLayer(0, 0, mViewWidth, mViewHeight, null, Canvas.ALL_SAVE_FLAG);
// 繪制 dst 心形
/***為什么 paint.setStyle() 放在 onDraw() 中才生效,放在 onSizeChanged() 中進(jìn)行不能生效***/
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
// 將繪制圖片對(duì)應(yīng)的 paint Xfermode 設(shè)置為 SRC_IN , 重疊的部分顯示為 src 圖片租冠, dst 中不重疊的部分不變鹏倘, src 中不重疊的部分顯示為透明
paint.setXfermode(xfermode);
// 繪制 src 圖片
super.onDraw(canvas);
paint.setXfermode(null);
canvas.restoreToCount(saveCount);
}
}
同樣的實(shí)現(xiàn)方式可以實(shí)現(xiàn)各種形狀的圖片控件。