【Android 自定义 View】-->Android 手签板

前言
Android 屏幕手写签名的原理就是把手机屏幕当作画板,把用户手指当作画笔,手指在屏幕上在屏幕上划来划去,屏幕就会显示手指的移动轨迹,就像画笔在画板上写字一样。实现手写签名需要结合绘图的路径工具 Path ,在有按下动作时调用 Path 对象的 moveTo 方法,将路径起始点移动到触摸点;在有移动操作时调用 Path 对象的 quadTo 方法,将记录本次触摸点与上次触摸点之间的路径;在有移动操作与提起动作时调用 Canvas 对象的 drawPath 方法,将本次触摸绘制在画布上。

效果图

功能


- 空白画板手写
- 实现笔锋效果
- 支持橡皮擦,撤回/恢复,清空画布功能
- 支持画笔颜色大小设置
- 支持传入初始图片
- 支持画布大小设置,文字区域裁剪
- 主题颜色设置
- 支持传入初始显示图片

代码
1. 布局文件
 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/rl_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:clipToPadding="true"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <include
        android:id="@+id/actionbar"
        layout="@layout/sign_actionbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/sign_actionbar_height"
        android:layout_alignParentTop="true" />

    <com.hkt.handwritten.view.PaintView
        android:id="@+id/paint_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/divider"
        android:layout_below="@id/actionbar"
        android:layout_margin="12dp"
        android:background="@drawable/shape_dash_bg"/>


    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_above="@+id/setting"
        android:background="@color/sign_border_gray" />

    <LinearLayout
        android:id="@+id/setting"
        android:layout_width="match_parent"
        android:layout_height="@dimen/y105"
        android:layout_alignParentBottom="true"
        android:background="@drawable/bottom_bg_shape"
        android:gravity="center"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/btn_hand"
            android:layout_width="@dimen/x38"
            android:layout_height="@dimen/y38"
            android:layout_alignParentLeft="true"
            android:layout_marginRight="@dimen/x40"
            android:scaleType="centerCrop"
            android:layout_weight="1"
            android:background="@drawable/sign_bg_btn_clicked"
            android:src="@mipmap/sign_ic_hand"
            android:visibility="gone" />


        <ImageView
            android:id="@+id/btn_undo"
            android:layout_width="@dimen/x78"
            android:layout_height="@dimen/y78"
            android:scaleType="centerCrop"
            android:layout_marginRight="@dimen/x40"
            android:layout_toLeftOf="@+id/btn_redo"
            android:padding="@dimen/x12"
            android:background="@drawable/sign_bg_btn_clicked"
            android:src="@mipmap/sign_ic_undo" />

        <ImageView
            android:id="@+id/btn_redo"
            android:layout_width="@dimen/x78"
            android:layout_height="@dimen/y78"
            android:scaleType="centerCrop"
            android:layout_marginRight="@dimen/x40"
            android:layout_toLeftOf="@+id/btn_clear"
            android:background="@drawable/sign_bg_btn_clicked"
            android:padding="@dimen/x12"
            android:src="@mipmap/sign_ic_redo" />


        <ImageView
            android:id="@+id/btn_pen"
            android:layout_width="@dimen/x78"
            android:layout_height="@dimen/y78"
            android:scaleType="centerCrop"
            android:layout_marginRight="@dimen/x40"
            android:layout_toLeftOf="@+id/btn_setting"
            android:padding="@dimen/x12"
            android:background="@drawable/sign_bg_btn_clicked"
            android:src="@mipmap/sign_ic_pen" />

        <ImageView
            android:id="@+id/btn_clear"
            android:layout_width="@dimen/x78"
            android:layout_height="@dimen/y78"
            android:layout_marginRight="@dimen/x40"
            android:layout_toLeftOf="@+id/btn_pen"
            android:padding="@dimen/x12"
            android:scaleType="centerCrop"
            android:background="@drawable/sign_bg_btn_clicked"
            android:src="@mipmap/sign_ic_clear" />

        <com.hkt.handwritten.view.CircleView
            android:id="@+id/btn_setting"
            android:layout_width="@dimen/x78"
            android:layout_height="@dimen/y78"
            android:layout_alignParentRight="true"
            android:padding="@dimen/x12"
            app:showOutBorder="false"
            app:sizeLevel="2" />
    </LinearLayout>
</RelativeLayout>

2. 核心代码 PaintView.java
 

/**
 * Created on 2021/12/1 11:58
 *
 * @author Gong Youqiang
 */
public class PaintView extends View {
    public static final int TYPE_PEN = 0;
    public static final int TYPE_ERASER = 1;

    private Paint mPaint;
    private Canvas mCanvas;
    private Bitmap mBitmap;
    private int strokeWidth;
    private BasePen mStokeBrushPen;

    /**
     * 是否允许写字
     */
    private boolean isFingerEnable = true;
    /**
     * 是否橡皮擦模式
     */
    private boolean isEraser = false;

    /**
     * 是否有绘制
     */
    private boolean hasDraw = false;


    /**
     * 画笔轨迹记录
     */
    private StepOperator mStepOperation;

    private StepCallback mCallback;

    /**
     * 是否可以撤销
     */
    private boolean mCanUndo;
    /**
     * 是否可以恢复
     */
    private boolean mCanRedo;

    private int mWidth;
    private int mHeight;

    private boolean isDrawing = false;//是否正在绘制
    private int toolType = 0;  //记录手写笔类型:触控笔/手指

    private Eraser eraser;

    public PaintView(Context context) {
        this(context, null);
    }

    public PaintView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PaintView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    /**
     * 初始化画板
     *
     * @param width  画板宽度
     * @param height 画板高度
     * @param path   初始图片路径
     */
    public void init(int width, int height, String path) {
        this.mWidth = width;
        this.mHeight = height;

        mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_4444);
        mStokeBrushPen = new SteelPen();

        initPaint();
        initCanvas();

        mStepOperation = new StepOperator();
        if (!TextUtils.isEmpty(path)) {
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            resize(bitmap, mWidth, mHeight);
        } else {
            mStepOperation.addBitmap(mBitmap);
        }
        //橡皮擦
        eraser = new Eraser(getResources().getDimensionPixelSize(R.dimen.sign_eraser_size));
    }

    /**
     * 初始画笔设置
     */
    private void initPaint() {
        strokeWidth = DisplayUtil.dip2px(getContext(), PaintSettingWindow.PEN_SIZES[PenConfig.PAINT_SIZE_LEVEL]);
        mPaint = new Paint();
        mPaint.setColor(PenConfig.PAINT_COLOR);
        mPaint.setStrokeWidth(strokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAlpha(0xFF);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeMiter(1.0f);
        mStokeBrushPen.setPaint(mPaint);
    }

    private void initCanvas() {
        mCanvas = new Canvas(mBitmap);
        //设置画布的背景色为透明
        mCanvas.drawColor(Color.TRANSPARENT);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, 0, 0, mPaint);
        if (!isEraser) {
            mStokeBrushPen.draw(canvas);
        }
        super.onDraw(canvas);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        toolType = event.getToolType(event.getActionIndex());
        if (!isFingerEnable && toolType != MotionEvent.TOOL_TYPE_STYLUS) {
            return false;
        }
        if (isEraser) {
            eraser.handleEraserEvent(event, mCanvas);
        } else {
            mStokeBrushPen.onTouchEvent(event, mCanvas);
        }

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                isDrawing = false;
                break;
            case MotionEvent.ACTION_MOVE:
                hasDraw = true;
                mCanUndo = true;
                isDrawing = true;
                break;
            case MotionEvent.ACTION_CANCEL:
                isDrawing = false;
                break;
            case MotionEvent.ACTION_UP:
                if (mStepOperation != null && isDrawing) {
                    mStepOperation.addBitmap(mBitmap);
                }
                mCanUndo = !mStepOperation.currentIsFirst();
                mCanRedo = !mStepOperation.currentIsLast();
                if (mCallback != null) {
                    mCallback.onOperateStatusChanged();
                }
                isDrawing = false;
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

    /**
     * @return 判断是否有绘制内容在画布上
     */
    public boolean isEmpty() {
        return !hasDraw;
    }

    /**
     * 撤销
     */
    public void undo() {

        if (mStepOperation == null || !mCanUndo) {
            return;
        }
        if (!mStepOperation.currentIsFirst()) {
            mCanUndo = true;
            mStepOperation.undo(mBitmap);
            hasDraw = true;
            invalidate();

            if (mStepOperation.currentIsFirst()) {
                mCanUndo = false;
                hasDraw = false;
            }
        } else {
            mCanUndo = false;
            hasDraw = false;
        }
        if (!mStepOperation.currentIsLast()) {
            mCanRedo = true;
        }
        if (mCallback != null) {
            mCallback.onOperateStatusChanged();
        }
    }

    /**
     * 恢复
     */
    public void redo() {
        if (mStepOperation == null || !mCanRedo) {
            return;
        }
        if (!mStepOperation.currentIsLast()) {
            mCanRedo = true;
            mStepOperation.redo(mBitmap);
            hasDraw = true;
            invalidate();
            if (mStepOperation.currentIsLast()) {
                mCanRedo = false;
            }
        } else {
            mCanRedo = false;
        }
        if (!mStepOperation.currentIsFirst()) {
            mCanUndo = true;
        }
        if (mCallback != null) {
            mCallback.onOperateStatusChanged();
        }
    }

    /**
     * 清除画布,记得清除点的集合
     */
    public void reset() {
        mBitmap.eraseColor(Color.TRANSPARENT);
        hasDraw = false;
        mStokeBrushPen.clear();
        if (mStepOperation != null) {
            mStepOperation.reset();
            mStepOperation.addBitmap(mBitmap);
        }
        mCanRedo = false;
        mCanUndo = false;
        if (mCallback != null) {
            mCallback.onOperateStatusChanged();
        }
        invalidate();
    }


    public void release() {
        destroyDrawingCache();
        if (mBitmap != null) {
            mBitmap.recycle();
            mBitmap = null;
        }
        if (mStepOperation != null) {
            mStepOperation.freeBitmaps();
            mStepOperation = null;
        }
    }

    public interface StepCallback {
        /**
         * 操作变更
         */
        void onOperateStatusChanged();
    }

    public void setStepCallback(StepCallback callback) {
        this.mCallback = callback;
    }

    /**
     * 设置画笔样式
     *
     * @param penType
     */
    public void setPenType(int penType) {
        isEraser = false;
        switch (penType) {
            case TYPE_PEN:
                mStokeBrushPen = new SteelPen();
                break;
            case TYPE_ERASER:
                isEraser = true;
                break;
        }
        //设置
        if (mStokeBrushPen.isNullPaint()) {
            mStokeBrushPen.setPaint(mPaint);
        }
        invalidate();
    }

    /**
     * 设置画笔大小
     *
     * @param width 大小
     */
    public void setPaintWidth(int width) {
        if (mPaint != null) {
            mPaint.setStrokeWidth(DisplayUtil.dip2px(getContext(), width));
//            eraser.setPaintWidth(DisplayUtil.dip2px(getContext(), width));
            mStokeBrushPen.setPaint(mPaint);
            invalidate();
        }
    }


    /**
     * 设置画笔颜色
     *
     * @param color 颜色
     */
    public void setPaintColor(int color) {
        if (mPaint != null) {
            mPaint.setColor(color);
            mStokeBrushPen.setPaint(mPaint);
            invalidate();
        }
    }

    /**
     * 构建Bitmap
     *
     * @return 所绘制的bitmap
     */
    public Bitmap buildAreaBitmap(boolean isCrop) {
        if (!hasDraw) {
            return null;
        }
        Bitmap result;
        if (isCrop) {
            result = BitmapUtil.clearBlank(mBitmap, 50, Color.TRANSPARENT);
        } else {
            result = mBitmap;
        }
        destroyDrawingCache();
        return result;
    }

    public boolean isFingerEnable() {
        return isFingerEnable;
    }

    public void setFingerEnable(boolean fingerEnable) {
        isFingerEnable = fingerEnable;
    }

    public boolean isEraser() {
        return isEraser;
    }

    public boolean canUndo() {
        return mCanUndo;
    }

    public boolean canRedo() {
        return mCanRedo;
    }

    public Bitmap getBitmap() {
        return mBitmap;
    }

    /**
     * 图片大小调整适配画布宽高
     *
     * @param bitmap 源图
     * @param width  新宽度
     * @param height 新高度
     */
    public void resize(Bitmap bitmap, int width, int height) {

        if (mBitmap != null) {
            if (width >= this.mWidth) {
                height = width * mBitmap.getHeight() / mBitmap.getWidth();
            }
            this.mWidth = width;
            this.mHeight = height;

            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
            restoreLastBitmap(bitmap, mBitmap);
            initCanvas();
            if (mStepOperation != null) {
                mStepOperation.addBitmap(mBitmap);
            }
            invalidate();
        }
    }

    /**
     * 恢复最后画的bitmap
     *
     * @param srcBitmap 最后的bitmap
     * @param newBitmap 新bitmap
     */
    private void restoreLastBitmap(Bitmap srcBitmap, Bitmap newBitmap) {
        try {
            if (srcBitmap == null || srcBitmap.isRecycled()) {
                return;
            }
            srcBitmap = BitmapUtil.zoomImg(srcBitmap, newBitmap.getWidth());
            //缩放后如果还是超出新图宽高,继续缩放
            if (srcBitmap.getWidth() > newBitmap.getWidth() || srcBitmap.getHeight() > newBitmap.getHeight()) {
                srcBitmap = BitmapUtil.zoomImage(srcBitmap, newBitmap.getWidth(), newBitmap.getHeight());
            }
            //保存所有的像素的数组,图片宽×高
            int[] pixels = new int[srcBitmap.getWidth() * srcBitmap.getHeight()];
            srcBitmap.getPixels(pixels, 0, srcBitmap.getWidth(), 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
            newBitmap.setPixels(pixels, 0, srcBitmap.getWidth(), 0, 0,
                    srcBitmap.getWidth(), srcBitmap.getHeight());
        } catch (OutOfMemoryError e) {
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = onMeasureR(0, widthMeasureSpec);
        int height = onMeasureR(1, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    /**
     * 计算控件宽高
     */
    public int onMeasureR(int attr, int oldMeasure) {

        int newSize = 0;
        int mode = MeasureSpec.getMode(oldMeasure);
        int oldSize = MeasureSpec.getSize(oldMeasure);

        switch (mode) {
            case MeasureSpec.EXACTLY:
                newSize = oldSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:

                float value = mWidth;

                if (attr == 0) {
                    if (mBitmap != null) {
                        value = mBitmap.getWidth();
                    }
                    // 控件的宽度
                    newSize = (int) (getPaddingLeft() + value + getPaddingRight());

                } else if (attr == 1) {
                    value = mHeight;
                    if (mBitmap != null) {
                        value = mBitmap.getHeight();
                    }
                    // 控件的高度
                    newSize = (int) (getPaddingTop() + value + getPaddingBottom());
                }
                break;
            default:
                break;
        }
        return newSize;
    }

    public Bitmap getLastBitmap() {
        return mBitmap;
    }
}

3. 核心代码 SteelPen.java
 

/**
 * Created on 2021/12/1 11:38
 * 钢笔
 * @author Gong Youqiang
 */
public class SteelPen extends BasePen {
    @Override
    protected void doPreDraw(Canvas canvas) {
        for (int i = 1; i < mHWPointList.size(); i++) {
            ControllerPoint point = mHWPointList.get(i);
            drawToPoint(canvas, point, mPaint);
            mCurPoint = point;
        }
    }

    @Override
    protected void doMove(double curDis) {
        int steps = 1 + (int) curDis / STEP_FACTOR;
        double step = 1.0 / steps;
        for (double t = 0; t < 1.0; t += step) {
            ControllerPoint point = mBezier.getPoint(t);
            mHWPointList.add(point);
        }
    }

    @Override
    protected void doDraw(Canvas canvas, ControllerPoint point, Paint paint) {
        drawLine(canvas, mCurPoint.x, mCurPoint.y, mCurPoint.width, point.x,
                point.y, point.width, paint);
    }

    /**
     * 绘制方法,实现笔锋效果
     */
    private void drawLine(Canvas canvas, double x0, double y0, double w0, double x1, double y1, double w1, Paint paint) {
        //求两个数字的平方根 x的平方+y的平方在开方记得X的平方+y的平方=1,这就是一个园
        double curDis = Math.hypot(x0 - x1, y0 - y1);
        int steps;
        //绘制的笔的宽度是多少,绘制多少个椭圆
        if (paint.getStrokeWidth() < 6) {
            steps = 1 + (int) (curDis / 2);
        } else if (paint.getStrokeWidth() > 60) {
            steps = 1 + (int) (curDis / 4);
        } else {
            steps = 1 + (int) (curDis / 3);
        }
        double deltaX = (x1 - x0) / steps;
        double deltaY = (y1 - y0) / steps;
        double deltaW = (w1 - w0) / steps;
        double x = x0;
        double y = y0;
        double w = w0;

        for (int i = 0; i < steps; i++) {
            RectF oval = new RectF();
            float top = (float) (y - w / 2.0f);
            float left = (float) (x - w / 4.0f);
            float right = (float) (x + w / 4.0f);
            float bottom = (float) (y + w / 2.0f);
            oval.set(left, top, right, bottom);
            //最基本的实现,通过点控制线,绘制椭圆
            canvas.drawOval(oval, paint);
            x += deltaX;
            y += deltaY;
            w += deltaW;
        }
    }
}

4. 使用

public class HandWrittenBoardActivity extends BaseActivity implements View.OnClickListener, PaintView.StepCallback {
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private ImageView mHandView; //切换 滚动/手写
    private ImageView mUndoView;
    private ImageView mRedoView;
    private ImageView mPenView;
    private ImageView mClearView;
    private CircleView mSettingView;

    private PaintView mPaintView;

    private ProgressDialog mSaveProgressDlg;
    private static final int MSG_SAVE_SUCCESS = 1;
    private static final int MSG_SAVE_FAILED = 2;

    private String mSavePath;
    private boolean hasSize = false;

    private float mWidth;
    private float mHeight;
    private float widthRate = 1.0f;
    private float heightRate = 1.0f;
    private int bgColor;
    private boolean isCrop;
    private String format;

    private PaintSettingWindow settingWindow;

    private static String[] PERMISSIONS_STORAGE = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE" };

    @Override
    protected int getLayout() {
        return R.layout.activity_hand_written_board;
    }

    @Override
    protected void initView() {
        verifyStoragePermissions(this);
        View mCancelView = findViewById(R.id.tv_cancel);
        View mOkView = findViewById(R.id.tv_ok);

        mPaintView = findViewById(R.id.paint_view);
        mHandView = findViewById(R.id.btn_hand);
        mUndoView = findViewById(R.id.btn_undo);
        mRedoView = findViewById(R.id.btn_redo);
        mPenView = findViewById(R.id.btn_pen);
        mClearView = findViewById(R.id.btn_clear);
        mSettingView = findViewById(R.id.btn_setting);
        mUndoView.setOnClickListener(this);
        mRedoView.setOnClickListener(this);
        mPenView.setOnClickListener(this);
        mClearView.setOnClickListener(this);
        mSettingView.setOnClickListener(this);
        mHandView.setOnClickListener(this);
        mCancelView.setOnClickListener(this);
        mOkView.setOnClickListener(this);

        mPenView.setSelected(true);
        mUndoView.setEnabled(false);
        mRedoView.setEnabled(false);
        mClearView.setEnabled(!mPaintView.isEmpty());

        mPaintView.setStepCallback(this);

        PenConfig.PAINT_SIZE_LEVEL = PenConfig.getPaintTextLevel(this);
        PenConfig.PAINT_COLOR = PenConfig.getPaintColor(this);

        mSettingView.setPaintColor(PenConfig.PAINT_COLOR);
        mSettingView.setRadiusLevel(PenConfig.PAINT_SIZE_LEVEL);

        setThemeColor(PenConfig.THEME_COLOR);
        BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, Color.WHITE);
        BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, Color.WHITE);
        BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? Color.WHITE : Color.LTGRAY);
        BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? Color.WHITE : Color.LTGRAY);
        BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? Color.WHITE : Color.LTGRAY);

    }


    /**
     * 获取画布默认宽度
     *
     * @return
     */
    private int getResizeWidth() {
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels) {
            return (int) (dm.heightPixels * widthRate);
        }
        return (int) (dm.widthPixels * widthRate);
    }

    /**
     * 获取画布默认高度
     *
     * @return
     */
    private int getResizeHeight() {
        int toolBarHeight = getResources().getDimensionPixelSize(R.dimen.sign_grid_toolbar_height);
        int actionbarHeight = getResources().getDimensionPixelSize(R.dimen.sign_actionbar_height);
        int statusBarHeight = StatusBarCompat.getStatusBarHeight(this);
        int otherHeight = toolBarHeight + actionbarHeight + statusBarHeight;
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels) {
            return (int) ((dm.widthPixels - otherHeight) * heightRate);
        }
        return (int) ((dm.heightPixels - otherHeight) * heightRate);
    }

    @Override
    protected void initData() {
        isCrop = getIntent().getBooleanExtra("crop", false);
        format = getIntent().getStringExtra("format");
        bgColor = getIntent().getIntExtra("background", Color.TRANSPARENT);
        String mInitPath = getIntent().getStringExtra("image");
        float bitmapWidth = getIntent().getFloatExtra("width", 1.0f);
        float bitmapHeight = getIntent().getFloatExtra("height", 1.0f);

        if (bitmapWidth > 0 && bitmapWidth <= 1.0f) {
            widthRate = bitmapWidth;
            mWidth = getResizeWidth();
        } else {
            hasSize = true;
            mWidth = bitmapWidth;
        }
        if (bitmapHeight > 0 && bitmapHeight <= 1.0f) {
            heightRate = bitmapHeight;
            mHeight = getResizeHeight();
        } else {
            hasSize = true;
            mHeight = bitmapHeight;
        }

        //初始画板设置
        if (!hasSize && !TextUtils.isEmpty(mInitPath)) {
            Bitmap bitmap = BitmapFactory.decodeFile(mInitPath);
            mWidth = bitmap.getWidth();
            mHeight = bitmap.getHeight();
            hasSize = true;
        }
        mPaintView.init((int) mWidth, (int) mHeight, mInitPath);
        if (bgColor != Color.TRANSPARENT) {
            mPaintView.setBackgroundColor(bgColor);
        }
    }

    /**
     * 横竖屏切换
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (settingWindow != null) {
            settingWindow.dismiss();
        }

        int resizeWidth = getResizeWidth();
        int resizeHeight = getResizeHeight();
        if (mPaintView != null && !hasSize) {
            mPaintView.resize(mPaintView.getLastBitmap(), resizeWidth, resizeHeight);
        }
    }

    public static void verifyStoragePermissions(Activity activity) {
        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void onClick(View v) {
        int i = v.getId();
        if (i == R.id.btn_setting) {
            showPaintSettingWindow();

        } else if (i == R.id.btn_hand) {
            //切换是否允许写字
            mPaintView.setFingerEnable(!mPaintView.isFingerEnable());
            if (mPaintView.isFingerEnable()) {
                BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_hand, PenConfig.THEME_COLOR);
            } else {
                BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_drag, PenConfig.THEME_COLOR);
            }

        } else if (i == R.id.btn_clear) {
            mPaintView.reset();

        } else if (i == R.id.btn_undo) {
            mPaintView.undo();

        } else if (i == R.id.btn_redo) {
            mPaintView.redo();

        } else if (i == R.id.btn_pen) {
            if (!mPaintView.isEraser()) {
                mPaintView.setPenType(PaintView.TYPE_ERASER);
                BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_eraser, Color.WHITE);
            } else {
                mPaintView.setPenType(PaintView.TYPE_PEN);
                BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, Color.WHITE);
            }
        } else if (i == R.id.tv_ok) {
            save();

        } else if (i == R.id.tv_cancel) {
            if (!mPaintView.isEmpty()) {
                showQuitTip();
            } else {
                setResult(RESULT_CANCELED);
                finish();
            }
        }
    }

    @Override
    protected void onDestroy() {
        if (mPaintView != null) {
            mPaintView.release();
        }
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }


    /**
     * 弹出画笔设置
     */
    private void showPaintSettingWindow() {
        settingWindow = new PaintSettingWindow(this);
        settingWindow.setSettingListener(new PaintSettingWindow.OnSettingListener() {
            @Override
            public void onColorSetting(int color) {
                mPaintView.setPaintColor(color);
                mSettingView.setPaintColor(color);
            }

            @Override
            public void onSizeSetting(int index) {
                mSettingView.setRadiusLevel(index);
                mPaintView.setPaintWidth(PaintSettingWindow.PEN_SIZES[index]);
            }
        });

        View contentView = settingWindow.getContentView();
        //需要先测量,PopupWindow还未弹出时,宽高为0
        contentView.measure(SystemUtil.makeDropDownMeasureSpec(settingWindow.getWidth()),
                SystemUtil.makeDropDownMeasureSpec(settingWindow.getHeight()));

        int padding = DisplayUtil.dip2px(this, 45);
        settingWindow.popAtTopRight();
        settingWindow.showAsDropDown(mSettingView, -540, -2 * padding - settingWindow.getContentView().getMeasuredHeight());

    }


    private void initSaveProgressDlg() {
        mSaveProgressDlg = new ProgressDialog(this);
        mSaveProgressDlg.setMessage("正在保存,请稍候...");
        mSaveProgressDlg.setCancelable(false);
    }

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAVE_FAILED:
                    mSaveProgressDlg.dismiss();
                    Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_SHORT).show();
                    break;
                case MSG_SAVE_SUCCESS:
                    mSaveProgressDlg.dismiss();
                    Intent intent = new Intent();
                    intent.putExtra(PenConfig.SAVE_PATH, mSavePath);
                    setResult(RESULT_OK, intent);
                    break;
                default:
                    break;
            }
        }
    };

    /**
     * 保存
     */
    private void save() {
        if (mPaintView.isEmpty()) {
            Toast.makeText(getApplicationContext(), "没有写入任何文字", Toast.LENGTH_SHORT).show();
            return;
        }
        //先检查是否有存储权限
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(getApplicationContext(), "没有读写存储的权限", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mSaveProgressDlg == null) {
            initSaveProgressDlg();
        }
        mSaveProgressDlg.show();
        new Thread(() -> {
            try {
                Bitmap result = mPaintView.buildAreaBitmap(isCrop);
                if (PenConfig.FORMAT_JPG.equals(format) && bgColor == Color.TRANSPARENT) {
                    bgColor = Color.WHITE;
                }
                if (bgColor != Color.TRANSPARENT) {
                    result = BitmapUtil.drawBgToBitmap(result, bgColor);
                }
                if (result == null) {
                    mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
                    return;
                }
                mSavePath = BitmapUtil.saveImage(HandWrittenBoardActivity.this, result, 100, format);
                if (mSavePath != null) {
                    mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget();
                } else {
                    mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
                }
            } catch (Exception e) {

            }
        }).start();

    }

    /**
     * 画布有操作
     */
    @Override
    public void onOperateStatusChanged() {
        mUndoView.setEnabled(mPaintView.canUndo());
        mRedoView.setEnabled(mPaintView.canRedo());
        mClearView.setEnabled(!mPaintView.isEmpty());

        BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? Color.WHITE : Color.LTGRAY);
        BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? Color.WHITE : Color.LTGRAY);
        BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? Color.WHITE : Color.LTGRAY);

    }

    @Override
    public void onBackPressed() {
        if (!mPaintView.isEmpty()) {
            showQuitTip();
        } else {
            setResult(RESULT_CANCELED);
            finish();
        }
    }

    /**
     * 弹出退出提示
     */
    private void showQuitTip() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("提示")
                .setMessage("当前文字未保存,是否退出?")
                .setNegativeButton("取消", null)
                .setPositiveButton("确定", (dialog, which) -> {
                    setResult(RESULT_CANCELED);
                    finish();
                });
        builder.show();
    }
}


[Demo](https://download.csdn.net/download/duoduo_11011/63793446)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin-Dev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值