DrawingView

Project Url: hcz017/DrawingView
Introduction: android 上的一个涂鸦控件。可以设置画笔的粗细,颜色,撤销上一笔涂鸦,提供保存图片的接口。
More: Author   ReportBugs   
Tags:

DrawingView 的原型来自DrawingView-Android,是 android 的一个可涂鸦控件。

之所以做这个控件是因为前段时间写了一个截图应用需要用到涂鸦功能,现在把涂鸦的控件单独拿出来写一个 demo。

这个控件代码超简单的哦,还不到 300 行。

效果展示

gif 效果展示:

保存的图片:

DrawImg.png

基本功能

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

DrawingView-Android相比较,这个版本的控件大小可以适应图片大小和尺寸,去除了“橡皮擦”功能,增加“撤销功能”,解决了在图像上涂鸦路径不能保存的问题。

具体实现

控件适应图片

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

重写 onMeasure()方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (mBitmap != null) {
            // 根据加载的图片尺寸缩放控件,这里只考虑了高大于宽的情况,其他情况请读者自行完善(代码中已完善)
            if (mBitmap.getHeight() > heightSize) {
                widthSize = heightSize * mBitmap.getWidth() / mBitmap.getHeight();
            } else {
                heightSize = mBitmap.getHeight();
                widthSize = mBitmap.getWidth();
            }
        }
        Log.d(TAG, "onMeasure: heightSize: " + heightSize + " widthSize: " + widthSize);
        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();
        } else {
            // TODO: 2017/7/12 这里留一个小 bug
            // 当第一笔和最后一笔画笔的样式(颜色和宽度)不同的时候,全部撤销后,这时画笔的样式被设置成了和
            // 第一笔一样,此时再绘画使用的是和第一笔一样的样式,而工具栏上我们看到的是最后一笔的样式。
            // UPFATE: 2018/05/31 这里的说明不完全正确
            // 我们需要的是在撤销任意步后,新的画笔颜色和宽度应和当前工具栏上的属性一致。
            // 之前的说法问题在于,我们随时可以更改工具栏上画笔属性,所以新画出来的画笔属性不应和最后一笔一样,
            // 而是要和工具栏上一样。(已修复)
        }
    }

注释里面提到了一个小 bug,现象和原因和说明了,有兴趣的读者可以自行完善一下这份代码(已解决)。

其他

DrawingView 中的 mDrawMode 用来判断当前是不是涂鸦模式。假如你的应用有其他功能模块的话,增加一个模式判断,避免涂鸦功能带来不必要的影响。

DrawingView 还提供了 getPenSize()和 getPenColor()等接口。

更多详情请参考源码。

使用方法

使用超简单

布局文件中添加控件

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@color/translucent">

        <com.hcz017.drawingview.DrawingView
            android:id="@+id/img_screenshot"
            android:layout_width="wrap_content"
            android:layout_width="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

PS: 因为我们在 java 代码中设置控件大小随加载的图片变化,所以这里的 layout_width 和 layout_width 属性值意义不大。

提供的接口

    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,可用于保存文件

github 地址:https://github.com/hcz017/DrawingView

感谢看到这里的大家~

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools