自定义涂鸦控件DrawingView

android上的一个涂鸦控件

Posted by wushiqian on October 5, 2018

自定义涂鸦控件DrawingView

android上的一个涂鸦控件。可以设置画笔的粗细,颜色,撤销上一笔涂鸦,提供保存图片的接口。

基本功能

可以设置画笔的粗细,颜色,撤销上一笔涂鸦,清空涂鸦,提供保存图片的接口。

具体实现

核心代码

	  @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 多个模式,检测当前模式可否绘画
        if (!mDrawMode) {
            return false;
        }
        float x;
        float y;
      // 若已缩放,则除以比例尺
        if (mProportion != 0) {
            x = (event.getX()) / mProportion;
            y = event.getY() / mProportion;
        } else {
            x = event.getX();
            y = event.getY();
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // This happens when we undo a path
                if (mLastDrawPath != null) {
                    mPaint.setColor(mPaintBarPenColor);
                    mPaint.setStrokeWidth(mPaintBarPenSize);
                }
                mPath = new Path();
                mPath.reset();
                mPath.moveTo(x, y);
                mX = x;
                mY = y;
                mCanvas.drawPath(mPath, mPaint);
                break;
            case MotionEvent.ACTION_MOVE:
            // 移动时绘画
                float dx = Math.abs(x - mX);
                float dy = Math.abs(y - mY);
                if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
                    mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
                    mX = x;
                    mY = y;
                }
                mCanvas.drawPath(mPath, mPaint);
                break;
            case MotionEvent.ACTION_UP:
                mPath.lineTo(mX, mY);
                mCanvas.drawPath(mPath, mPaint);
            // 保存绘画的路径,以便撤回
                mLastDrawPath = new DrawPath(mPath, mPaint.getColor(), mPaint.getStrokeWidth());
                savePath.add(mLastDrawPath);
                mPath = null;
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

控件适应图片

因为我们需要这个控件居中显示,而且canvas必须和加载的图片一样大(否则可以涂鸦的范围和图片大小不一样)所以在绘制这个控件的时候要测量图片大小。

重写onMeasure()方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
				super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = getMeasureWidth(widthMeasureSpec);
        int heightSize = getMeasureHeight(heightMeasureSpec);
  			// 修改控件的大小
        if (mBitmap != null) {
            if ((mBitmap.getHeight() > heightSize) && (mBitmap.getHeight() > mBitmap.getWidth())) {
                widthSize = heightSize * mBitmap.getWidth() / mBitmap.getHeight();
            } else if ((mBitmap.getWidth() > widthSize) && (mBitmap.getWidth() > mBitmap.getHeight())) {
                heightSize = widthSize * mBitmap.getHeight() / mBitmap.getWidth();
            } else {
                heightSize = mBitmap.getHeight();
                widthSize = mBitmap.getWidth();
            }
        }
        setMeasuredDimension(widthSize, heightSize);  //必须调用此方法,否则会抛出异常
    }

撤销功能

创建一个列表,在每次手指抬起时(MotionEvent.ACTION_UP)记录下paint和path,需要“撤销”的时候先清空画布,然后重新加载图片,之后移除列表中的最后一笔,最后把列表中记录的paint和path重绘一次。

记录画笔和路径,注意如果你是直接保存mPaint和mPath的话,每次手指下落的时候都要新建这两个对象,不然会导致路径列表里所有路径都是一样的,因为他们保存的对象最终指向同样的内容。

这里做了一点小改变。不保存mPaint,只保存了mPaint的两个属性,这样就不用每次new Paint()了。

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            	// 手指下落 新建Path对象
                mPath = new Path();
                ...
                break;
            case MotionEvent.ACTION_MOVE:
                ...
                mCanvas.drawPath(mPath, mPaint);
                break;
            case MotionEvent.ACTION_UP:
                mPath.lineTo(mX, mY);
                mCanvas.drawPath(mPath, mPaint);
            	// 保存path和paint的两个属性
                savePath.add(new DrawPath(mPath, mPaint.getColor(), mPaint.getStrokeWidth()));
                mPath = null;
                break;
            default:
                break;
        }
    /**
     * 撤销上一步
     */
    public void undo() {
        Log.d(TAG, "undo: recall last path");
        if (savePath != null && savePath.size() > 0) {
            // 清空画布
            mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            loadImage(mOriginBitmap);

            savePath.removeLast();

            // 将路径保存列表中的路径重绘在画布上 遍历绘制
            for (DrawPath dp : savePath) {
                mPaint.setColor(dp.getPaintColor());
                mPaint.setStrokeWidth(dp.getPaintWidth());
                mCanvas.drawPath(dp.path, mPaint);
            }
            invalidate();
        }
    }

清空画布

    /**
     * 清空画布
     */
    public void clear() {
        Log.d(TAG, "clear the path");
        if (savedPath != null && savedPath.size() > 0) {
            // 清空画布
            mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            loadImage(mOriginBitmap);
            invalidate();
        }
    }

提供的接口

自定义控件的其它实现的功能

    mDrawingView = (DrawingView) findViewById(R.id.img_screenshot);
    mDrawingView.initializePen();// 初始化画笔
    mDrawingView.setPenSize(10);// 设置画笔大小
    mDrawingView.setPenColor(getColor(R.color.red));// 设置画笔颜色
    mDrawingView.loadImage(bitmap);// 加载图片
    mDrawingView.saveImage(sdcardPath, "DrawImg", Bitmap.CompressFormat.PNG, 100);//保存图片
    mDrawingView.undo();// 撤销上一步
    mDrawingView.getImageBitmap();// 返回控件上的bitmap,可用于保存文件