首先来看下各个场景的效果,项目完整代码:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn04
上述效果的实现主要是使用图形的fermode与PorterDuff属性的叠加,下面是18中图形叠加模式
运行效果图:
1)PorterDuff.Mode.ADD: 饱和度叠加
所绘制不会提交到画布上,嗯结果...不知道是为什么了,正常是没东西的..
取两图层全部区域,交集部分颜色加深
只保留目标图的alpha和color,所以绘制出来只有目标图
源图和目标图相交处绘制目标图,不相交的地方绘制源图
两者相交的地方绘制目标图,绘制的效果会受到原图处的透明度影响
在不相交的地方绘制目标图
目标图绘制在上方
取两图层全部区域,点亮交集部分颜色
取两图层交集部分叠加后颜色
叠加
取两图层全部区域,交集部分变为透明色
只保留源图像的alpha和color,所以绘制出来只有源图
源图和目标图相交处绘制源图,不相交的地方绘制目标图
两者相交的地方绘制源图
不相交的地方绘制源图
把源图绘制在上方
不相交的地方按原样绘制源图和目标图
通过PorterDuff.Mode.DST_IN模式来实现! 我们来分析分析实现流程原理:
Xfermode无非是两层图构成,先绘制的叫DST图(目标图),后绘制的叫SRC图(原图),我们要实现 圆形或者圆角,我们可以先把要显示的图片绘制出来(DST),这里我们通过src的属性进行了设置; 接着再绘制出圆形和圆角(SRC),我们想显示的部分是他们相交的地方,而且是图片部分的内容, 所以选择:DST_IN模式!
public class RoundImageView_SRCIN extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC;
public RoundImageView_SRCIN(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.shade,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
不规则wave波浪
实现和圆角图片是一样的,只是这里我们利用属性动画不断的移动波浪这张图片,仍然使用PorterDuff.Mode.DST_IN这个属性完成:
public class IrregularWaveView_DSTIN extends View {
private Paint mPaint;
private int mItemWaveLength = 0;
private int dx = 0;
private Bitmap BmpSRC, BmpDST;
public IrregularWaveView_DSTIN(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.wav,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.circle_shape,null);
mItemWaveLength = BmpDST.getWidth();
startAnim();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(BmpSRC,0,0,mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
private void startAnim() {
ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
animator.setDuration(4000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
}
普通画板和贝塞尔画板
这两个画板是为了后面的橡皮擦效果的铺垫,在使用普通画板时连续的拐弯操作会出现不平滑现象,这是因为连线是直线在弯曲处无法做到顺滑,利用贝塞尔曲线即可,具体代码见项目。
public class BezierGestureTrackView extends View {
private Path mPath = new Path();
private Paint mPaint;
private float mPreX, mPreY;
public BezierGestureTrackView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mPath.moveTo(event.getX(), event.getY());
mPreX = event.getX();
mPreY = event.getY();
return true;
}
case MotionEvent.ACTION_MOVE: {
float endX = (mPreX + event.getX()) / 2;
float endY = (mPreY + event.getY()) / 2;
mPath.quadTo(mPreX, mPreY, endX, endY);
mPreX = event.getX();
mPreY = event.getY();
invalidate();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
canvas.drawPath(mPath, mPaint);
}
}
橡皮擦
我们来分析下实现流程:
- 其实就是一个Bitmap,然后通过一个Path来记录用户绘制出来的图形,然后为我们的画笔设置DST_OUT的模式,那么 与Path重叠部分的DST(目标图),就是完整的图,在和手指划过的路径相交就会变成透明!
- 接着设置下画笔,圆角,笔宽,抗锯齿等!
- 再接着定义一个画Path,即用户绘制区域的方法,设置Xfermode后画区域而已!
- 然后重写onTouchEvent方法,这部分和之前的自定义画图板是一样的!
- 最后重写onDraw()方法,先绘制背景图片,调用用户绘制区域的方法,再绘制前景图片!
public class EraserView_SRCOUT extends View {
private Paint mBitPaint;
private Bitmap BmpDST, BmpSRC;
private Path mPath;
private float mPreX, mPreY;
public EraserView_SRCOUT(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
mBitPaint.setColor(Color.RED);
mBitPaint.setStyle(Paint.Style.STROKE);
mBitPaint.setStrokeWidth(45);
BmpSRC = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy6, null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先把手指轨迹画到目标Bitmap上
Canvas c = new Canvas(BmpDST);
c.drawPath(mPath, mBitPaint);
//然后把目标图像画到画布上
canvas.drawBitmap(BmpDST, 0, 0, mBitPaint);
//计算源图像区域
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(BmpSRC, 0, 0, mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(), event.getY());
mPreX = event.getX();
mPreY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float endX = (mPreX + event.getX()) / 2;
float endY = (mPreY + event.getY()) / 2;
mPath.quadTo(mPreX, mPreY, endX, endY);
mPreX = event.getX();
mPreY = event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
postInvalidate();
return super.onTouchEvent(event);
}
}