先看效果图
技术点:
1.画笔的XferMode,XferMode是指定两张图相交后的效果,这里主要是灰色涂层和手指移动痕迹的相交效果。
2.根据onTouchEvent点击事件记录手指移动的坐标,再用这些坐标用drawPath来画出不规则的移动痕迹。
Xfermode
Xfermode有三个子类:
- AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
- PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素XOR操作。
- PorterDuffXfermode 有16种规则来控制Paint如何与已有的Canvas图像进行交互,我们用的就是这个。
其中16种规则效果如下图所示,圆为Dst,正方形为Src。
public enum Mode {
/** [0, 0] */
CLEAR (0),
/** [Sa, Sc] */
SRC (1),
/** [Da, Dc] */
DST (2),
/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
SRC_OVER (3),
/** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
DST_OVER (4),
/** [Sa * Da, Sc * Da] */
SRC_IN (5),
/** [Sa * Da, Sa * Dc] */
DST_IN (6),
/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT (7),
/** [Da * (1 - Sa), Dc * (1 - Sa)] */
DST_OUT (8),
/** [Da, Sc * Da + (1 - Sa) * Dc] */
SRC_ATOP (9),
/** [Sa, Sa * Dc + Sc * (1 - Da)] */
DST_ATOP (10),
/** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
XOR (11),
/** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
DARKEN (16),
/** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
LIGHTEN (17),
/** [Sa * Da, Sc * Dc] */
MULTIPLY (13),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
SCREEN (14),
/** Saturate(S + D) */
ADD (12),
OVERLAY (15);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
RabbleView代码
全部代码都在下面了,其中注释已经说的很清楚了。
public class RabbleView extends TextView{
//要擦拭的背景
private Bitmap mBitmap;
//擦拭背景颜色
private int bgColor = 0XFFCECECE;
//擦拭画布
private Canvas mCanvas;
//擦拭画笔
private Paint mPaint;
//擦拭画笔宽度
private int mStrokeWidth = DisplayUtils.dp2px(getContext(),40);
//擦拭痕迹
private Path mPath;
private float mX, mY;
private boolean isUp = false;
public RabbleView(Context context) {
this(context,null);
}
public RabbleView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RabbleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initRabble();
}
private void initRabble(){
mPaint = new Paint();
//透明
mPaint.setAlpha(0);
//抗锯齿
mPaint.setDither(true);
//防抖动
mPaint.setAntiAlias(true);
// 此处不能为透明色
mPaint.setColor(Color.BLACK);
//两图的相交模式
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mPaint.setStyle(Paint.Style.STROKE);
//设置结合处的样子,Miter:结合处为锐角, Round:结合处为圆弧:BEVEL:结合处为直线
mPaint.setStrokeJoin(Paint.Join.ROUND); // 前圆角
//当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mStrokeWidth);
// 痕迹
mPath = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//初始化擦拭背景
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mCanvas.drawColor(bgColor);
}
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
//要是宽度或者高度为0就没必要加上擦拭涂层了
if (getWidth() == 0 || getHeight() == 0) {
return;
}
if (!isUp) {
mCanvas.drawPath(mPath, mPaint);
} else{
//当手指离开就把涂层变成透明的
mBitmap.eraseColor(0X00);
}
canvas.drawBitmap(mBitmap, 0, 0, null);
}
@Override
public boolean onTouchEvent(MotionEvent event){
super.onTouchEvent(event);
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
down(event.getX(),event.getY());
break;
case MotionEvent.ACTION_MOVE:
move(event.getX(),event.getY());
break;
case MotionEvent.ACTION_UP:
up(event.getX(),event.getY());
break;
}
return true;
}
private void down(float x, float y){
mPath.reset();
mX = x;
mY = y;
mPath.moveTo(x,y);
}
private void move(float x, float y){
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= 3 || dy >= 3) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
// mPath.lineTo(x,y);
mX = x;
mY = y;
}
invalidate();
}
private void up(float x, float y){
mPath.lineTo(x, y);
mPath.reset();
isUp = true;
invalidate();
}