Android 自定义 View 进阶 - Xfermode
Posted 星火燎原2016
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义 View 进阶 - Xfermode相关的知识,希望对你有一定的参考价值。
在 android 自定义控件中,Xfermode 知识点占有很重要的地位,它能帮助我们实现很多炫酷的效果。例如,实现各种形状的图片控件;结合属性动画实现渐变效果。
Xfermode 介绍
Xfermode 主要是通过 paint.setXfermode(Xfermode xfermode) 方法进行设置的,其中 在 API 28 中, Xfermode 类只有一个子类 PorterDuffXfermode
PorterDuffXfermode 构造函数:
public PorterDuffXfermode(PorterDuff.Mode mode)
参数 mode 设置不同的混合模式,取值有以下这几种:
Xfermode 使用方法
通常情况下,需要关闭硬件加速 setLayerType(View.LAYER_TYPE_SOFTWARE, null); 然后在自定义控件的 onDraw() 方法中,保存至新的图层中,先绘制 dest 图像,然后再设置 paint.setXfermode(new PorterDuffXfermode(getMode(mode))); 接着绘制 src 图像,这样画笔就应用上了指定的模式了。主要流程如下:
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
//将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);
// 绘制 dest
canvas.drawBitmap(destBitmap, 0, 0, paint);
// 设置 xfermode
if (mode != 0)
paint.setXfermode(new PorterDuffXfermode(getMode(mode)));
// 绘制 src
canvas.drawBitmap(srcBitmap, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(saveCount);
官方 Sample 测试
先准备两种图片素材,dest 图像为 红色方块 图片,src 图像为 蓝色方块 图片(纯为透底图)
新建自定义控件类 XfermodeBitmapView ,继承 View, 在 onDraw() 方法先绘制 dest 图像,然后将 paint xfermode 设置为 指定的模式,再绘制 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);
// 设置背景
canvas.drawColor(Color.DKGRAY);
//将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);
// 绘制 dest
canvas.drawBitmap(destBitmap, 0, 0, paint);
// 设置 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 实现高亮进度 ImageView
实现思路:
(1) 显示图片,继承 ImageView 类 ,更方便。
(2) 圆角矩形图片,通过 canvas.clipPath() 裁剪 canvas 画布(在 super.onDraw() 之前调用),绘制的图片就会显示成为圆角矩形。
(3) 图片上的灰色蒙层和圆形镂空,通过 Xfermode 模式,先绘制 dst 镂空圆,在绘制 src 灰色蒙层,并将 paint 设置为 srcOut , 这样 dst 镂空圆和灰色蒙层重叠的部分就会变成透明了,显示出了底层的图片
/**
* 高亮进度 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);
/**
* 绘制步骤: 先绘制 圆, 再在圆上绘制灰色背景,绘制灰色背景时,将 Xfermode 设置为 PorterDuff.Mode.SRC_OUT, 这样重叠的部分就会变为透明,显示出正常的图片
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas)
// 通过 path, 裁剪 canvas 画布
canvas.clipPath(clipPath);
// 绘制图片
super.onDraw(canvas);
//将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), backgroundPaint, Canvas.ALL_SAVE_FLAG);
canvas.translate(width / 2f, height / 2f);
// if (needDrawArc)
// 绘制 dst 圆环
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);
// 设置 Xfermode 为 SRC_OUT
backgroundPaint.setXfermode(porterDuffXfermode);
// 绘制 src 图片上层的灰色蒙层
canvas.drawRoundRect(backgroundRectF, roundCorner, roundCorner, backgroundPaint);
backgroundPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
/**
* 开启动画
*/
public void start()
startAnimator();
/**
* 停止动画
*/
public void stop()
if (animatorSet != null)
animatorSet.cancel();
animatorSet = null;
private void startAnimator()
// 扇形进度动画
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 实现心状图片
实现思路:
(1) 继承自 ImageView 类,先绘制图片作为 dest 图像,此时的画笔 paint 需要是 ImageView 图片的画笔,不能是 重新创建的新画笔;
(2) 设置画笔 Xfermode 模式为 SRC_IN ,并利用 path 贝塞尔曲线绘制一个心形,这样心形和图片重合的部分就保留显示了心形部分的图片。
/**
* 心形图片
*/
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;
// 获取绘制图片对应的 paint
paint Android进阶之旅 - 自定义View篇
我的Android进阶之旅------>Android自定义View实现带数字的进度条(NumberProgressBar)