之前自定义呼吸圈按钮控件的时候使用到了paint的xfermode,这次要实现涂鸦画笔自然也少不了使用它。由此可见在Android绘图中xfermode的重要性。想要了解Xfermode可以看我的文章仿抖音短视频录制按钮动画篇
首先要实现涂鸦功能就得先了解什么是涂鸦?
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBufferBitmap != null) {
canvas.drawBitmap(mBufferBitmap, 0, 0, null);
}
}
这里很简单直接绘制一个Bitmap,有人会想这怎么显示我手指在屏幕上实时滑动的效果呢?其实这里使用了绘图的双缓冲技术,就是在android绘图中其实就是再创建一个Canvas和对应的Bitmap,然后在onDraw方法里默认的Canvas通过drawBitmap画刚才new的那个bitmap从而实现双缓冲。缓冲示意图如下
- 在绘制数据量较小时,不使用双缓冲,GPU的负荷更低,即绘制性能更高;
- 在绘制数据量较大时,使用双缓冲绘图,绘制性能明显高于不使用双缓冲的情况;
- 使用双缓冲会增加内存消耗。
private void initBuffer() { mBufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); mBufferCanvas = new Canvas(mBufferBitmap); }
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
final int action = event.getAction() & MotionEvent.ACTION_MASK;
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
if (mPath == null) {
mPath = new Path();
}
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
//这里终点设为两点的中心点的目的在于使绘制的曲线更平滑,如果终点直接设置为x,y,效果和lineto是一样的,实际是折线效果
mPath.quadTo(mLastX, mLastY, (x + mLastX) / 2, (y + mLastY) / 2);
if (mBufferBitmap == null) {
initBuffer();
}
if (mMode == Mode.ERASER && !mCanEraser) {
break;
}
mBufferCanvas.drawPath(mPath, mPaint);
invalidate();
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
if (mMode == Mode.DRAW || mCanEraser) {
saveDrawingPath();
}
mPath.reset();
break;
}
return true;
}
这里主要就是使用贝塞尔曲线绘制路径,使用贝塞尔曲线绘制曲线更平滑。每一次down就会在up的时候保存当前绘制的路径和画笔以及当前的绘制模式,这样的话就可以轻松实现撤销,反撤销操作
撤销代码
public void undo() { int size = mDrawingList == null ? 0 : mDrawingList.size(); if (size > 0) { DrawingInfo info = mDrawingList.remove(size - 1); if (mRemovedList == null) { mRemovedList = new ArrayList<>(MAX_CACHE_STEP); } if (size == 1) { mCanEraser = false; } mRemovedList.add(info); reDraw(); if (mCallback != null) { mCallback.onUndoRedoStatusChanged(); } } }
采用列表存储绘制的路径和撤销的路径,然后最后都会重新绘制一边mDrawingList列表里存储的路径
重新绘制的代码reDraw()
private void reDraw() { if (mDrawingList != null) { mBufferBitmap.eraseColor(Color.TRANSPARENT); for (DrawingInfo drawingInfo : mDrawingList) { drawingInfo.draw(mBufferCanvas); } invalidate(); } }
重新在缓冲的bitmap上绘制之后就要调用invalidate刷新view的draw方法把视图显示出来就可以了。
橡皮擦和涂鸦画笔的切换
public void setMode(Mode mode) { if (mode != mMode) { mMode = mode; if (mMode == Mode.DRAW) { mPaint.setXfermode(mXferModeDraw); mPaint.setStrokeWidth(mDrawSize); } else { mPaint.setXfermode(mXferModeClear); mPaint.setStrokeWidth(mEraserSize); } } }
这里看到Xfermode已经不陌生了吧,当Xfermode模式为PorterDuff.Mode.CLEAR的时候就可以清楚画布上的颜色了,这样就实现了橡皮擦的功能。
总结
到这里就已经全部实现了涂鸦功能,刚开始接触这个功能的时候一头雾水,后来看了Android相关的绘图资料和借鉴了前人的经验后对绘图这块又有了更深的理解。当然这个还不是非常完善的功能,还有使用图片代替颜色实现涂鸦的功能没有实现,这个将在后面介绍马赛克功能实现篇做介绍。
本人菜鸟一枚,文章浅陋,有不当之处还望批评指正。