淘先锋技术网

首页 1 2 3 4 5 6 7

之前自定义呼吸圈按钮控件的时候使用到了paint的xfermode,这次要实现涂鸦画笔自然也少不了使用它。由此可见在Android绘图中xfermode的重要性。想要了解Xfermode可以看我的文章仿抖音短视频录制按钮动画篇

首先要实现涂鸦功能就得先了解什么是涂鸦?

从字面上解释:涂(随意的涂涂抹抹);鸦(泛指颜色)。“涂”和“鸦”加一起就成了随意地涂抹色彩之意,比喻书法拙劣或胡乱写作(多用作谦辞)。“尚欲勉强涂鸦,以求指教”。——《 镜花缘
也指艺术上的各种颜色交融,以抽象的感觉描绘出一种色彩的特殊风格。
 
 
首先分析思路
原理技术分析:从字面意思理解涂鸦就是随意使用不同颜色进行涂抹。那么这里就涉及到了涂抹的路径,和颜色的切换。然后一般优秀的产品都会把对应的功能做到极致,比如单次操作可以撤销,反撤销,还有橡皮擦功能,更高级的还可以使用图片替代颜色作为画笔路径的填充。
路径:这里可以使用Path来记录,要实现单次操作撤销,反撤销那么需要用两个数组来存储Path集合
颜色:这个就直接可以使用paint.setColor(Color.RED),
 
具体实现
 
还是从自定义View绘制部分说起
 
@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从而实现双缓冲。缓冲示意图如下

  1.  在绘制数据量较小时,不使用双缓冲,GPU的负荷更低,即绘制性能更高;
  2. 在绘制数据量较大时,使用双缓冲绘图,绘制性能明显高于不使用双缓冲的情况;
  3. 使用双缓冲会增加内存消耗。
 考虑到绘图的时候数据量会随着涂鸦的次数也来越多,就会数据量更大所以涂鸦采用双缓冲技术
初始化双缓冲Bitmap也很简单
  private void initBuffer() {
        mBufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        mBufferCanvas = new Canvas(mBufferBitmap);
    }

 

接下来就要涉及到手势操作绘制手指跟随效果,肯定要处理View的触摸事件
 
@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相关的绘图资料和借鉴了前人的经验后对绘图这块又有了更深的理解。当然这个还不是非常完善的功能,还有使用图片代替颜色实现涂鸦的功能没有实现,这个将在后面介绍马赛克功能实现篇做介绍。

本人菜鸟一枚,文章浅陋,有不当之处还望批评指正。

涂鸦源码

 

 

 

 
 
 

转载于:https://www.cnblogs.com/xuzhiyong/p/9224600.html