在 Android 自定义控件中,Xfermode 是一个难点也是一个重点,我们有必要掌握它。之前学习过,没有充分理解,今天重新学习了下,完成了一个高亮进度条的 ImageView 控件(逐步优化中)。
效果图:
实现代码
/**
* 高亮进度的 imageview
*/
public class HightlightProgressImageView extends AppCompatImageView {
private Paint backgroundPaint;
private Paint circlePaint;
private int radius;
private int width;
private int height;
private int roundCorner;
private Path clipPath;
private ValueAnimator valueAnimator;
public HightlightProgressImageView(Context context) {
super(context);
init();
}
public HightlightProgressImageView(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.FILL);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
radius = dp2Px(30);
roundCorner = dp2Px(10);
clipPath = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
clipPath.addRoundRect(new RectF(0, 0, width, height), roundCorner, roundCorner, Path.Direction.CCW);
}
/**
* 绘制步骤: 先绘制 圆, 再在圆上绘制灰色背景,绘制灰色背景时,将 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.save();
// 平移画布到中心点位置
canvas.translate(getWidth() / 2f, getHeight() / 2f);
canvas.drawCircle(0, 0, value, circlePaint);
canvas.restore();
backgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
// 在图片上层绘制背景
canvas.drawPaint(backgroundPaint);
backgroundPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
}
/**
* 开启动画
*/
public void start() {
startAnimator();
}
/**
* 停止动画
*/
public void stop() {
if (valueAnimator != null) {
valueAnimator.cancel();
valueAnimator = null;
}
}
private float value;
private void startAnimator() {
if (valueAnimator == null) {
valueAnimator = ValueAnimator.ofFloat(radius, width > height ? width : height);
valueAnimator.setDuration(3000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
value = (float) animation.getAnimatedValue();
invalidate();
}
});
}
valueAnimator.start();
}
private int dp2Px(int value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics());
}
}