安卓手写板,以及提高Android应用手写流畅度

本文章是把网上的PaletteView(文章地址 https://www.jianshu.com/p/548d2799fd6e)手写板进行了优化,使得手写更加流畅。
参考资料:https://blog.csdn.net/ekeuy/article/details/37961199

优化方式

  • 增加触摸点个数
  • 减少每次刷新的区域(每次只刷新一个小矩形区域)

不直接说了,直接上代码。

public class PaletteView extends View {
    private Paint mPaint;
    private Path mPath;
    private float mLastX;
    private float mLastY;
    private Bitmap mBufferBitmap;
    private Canvas mBufferCanvas;

    private static final int MAX_CACHE_STEP = 20;

    private List<DrawingInfo> mDrawingList;
    private List<DrawingInfo> mRemovedList;

    private Xfermode mXferModeClear;
    private Xfermode mXferModeDraw;
    private int mDrawSize;
    private int mEraserSize;
    private int mPenAlpha = 255;

    private boolean mCanEraser;

    private Callback mCallback;

    public enum Mode {
        DRAW,
        ERASER
    }

    private Mode mMode = Mode.DRAW;


    public PaletteView(Context context) {
        super(context);
        init();
    }

    public PaletteView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PaletteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public interface Callback {
        void onUndoRedoStatusChanged();
    }

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    private void init() {
        setDrawingCacheEnabled(true);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setFilterBitmap(true);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mDrawSize = DimenUtils.dp2pxInt(3);
        mEraserSize = DimenUtils.dp2pxInt(30);
        mPaint.setStrokeWidth(mDrawSize);
        mPaint.setColor(Color.BLACK);
        mXferModeDraw = new PorterDuffXfermode(PorterDuff.Mode.SRC);
        mXferModeClear = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
        mPaint.setXfermode(mXferModeDraw);
    }

    private void initBuffer() {
        mBufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        mBufferCanvas = new Canvas(mBufferBitmap);
    }

    private abstract static class DrawingInfo {
        Paint paint;

        abstract void draw(Canvas canvas);
    }

    private static class PathDrawingInfo extends DrawingInfo {

        Path path;

        @Override
        void draw(Canvas canvas) {
            canvas.drawPath(path, paint);
        }
    }

    public Mode getMode() {
        return mMode;
    }

    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);
            }
        }
    }

    public void setEraserSize(int size) {
        mEraserSize = size;
    }

    public void setPenRawSize(int size) {
        mDrawSize = size;
        if (mMode == Mode.DRAW) {
            mPaint.setStrokeWidth(mDrawSize);
        }
    }

    public void setPenColor(int color) {
        mPaint.setColor(color);
    }

    private void reDraw() {
        if (mDrawingList != null) {
            mBufferBitmap.eraseColor(Color.TRANSPARENT);
            for (DrawingInfo drawingInfo : mDrawingList) {
                drawingInfo.draw(mBufferCanvas);
            }
            invalidate();
        }
    }

    public int getPenColor() {
        return mPaint.getColor();
    }

    public int getPenSize() {
        return mDrawSize;
    }

    public int getEraserSize() {
        return mEraserSize;
    }

    public void setPenAlpha(int alpha) {
        mPenAlpha = alpha;
        if (mMode == Mode.DRAW) {
            mPaint.setAlpha(alpha);
        }
    }

    public int getPenAlpha() {
        return mPenAlpha;
    }

    public boolean canRedo() {
        return mRemovedList != null && mRemovedList.size() > 0;
    }

    public boolean canUndo() {
        return mDrawingList != null && mDrawingList.size() > 0;
    }

    public void redo() {
        int size = mRemovedList == null ? 0 : mRemovedList.size();
        if (size > 0) {
            DrawingInfo info = mRemovedList.remove(size - 1);
            mDrawingList.add(info);
            mCanEraser = true;
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    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();
            }
        }
    }

    public void clear() {
        if (mBufferBitmap != null) {
            if (mDrawingList != null) {
                mDrawingList.clear();
            }
            if (mRemovedList != null) {
                mRemovedList.clear();
            }
            mCanEraser = false;
            mBufferBitmap.eraseColor(Color.TRANSPARENT);
            invalidate();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public Bitmap buildBitmap() {
        Bitmap bm = getDrawingCache();
        Bitmap result = Bitmap.createBitmap(bm);
        destroyDrawingCache();
        return result;
    }

    private void saveDrawingPath() {
        if (mDrawingList == null) {
            mDrawingList = new ArrayList<>(MAX_CACHE_STEP);
        } else if (mDrawingList.size() == MAX_CACHE_STEP) {
            mDrawingList.remove(0);
        }
        Path cachePath = new Path(mPath);
        Paint cachePaint = new Paint(mPaint);
        PathDrawingInfo info = new PathDrawingInfo();
        info.path = cachePath;
        info.paint = cachePaint;
        mDrawingList.add(info);
        mCanEraser = true;
        if (mCallback != null) {
            mCallback.onUndoRedoStatusChanged();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBufferBitmap != null) {
            canvas.drawBitmap(mBufferBitmap, 0, 0, null);
        }
    }

    @SuppressWarnings("all")
    @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:
                if (mMode == Mode.DRAW) {
                    //橡皮擦模式,绘图模式
                    resetDirtyRect(x, y);
                    int historySize = event.getHistorySize();
                    for (int i = 0; i < historySize; i++) {
                        float historicalX = event.getHistoricalX(i);
                        float historicalY = event.getHistoricalY(i);
                        getDirtyRect(historicalX, historicalY);
                        mPath.lineTo(historicalX, historicalY);
                    }

                    mPath.lineTo(x, y);
                    if (mBufferBitmap == null) {
                        initBuffer();
                    }
                    mBufferCanvas.drawPath(mPath, mPaint);
                    invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
                            (int) (mDirtyRect.top - HALF_STROKE_WIDTH),
                            (int) (mDirtyRect.right + HALF_STROKE_WIDTH),
                            (int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));
                } else {
                    //橡皮擦模式
                    //这里终点设为两点的中心点的目的在于使绘制的曲线更平滑,如果终点直接设置为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;
    }

    private void resetDirtyRect(float eventX, float eventY) {
        mDirtyRect.left = Math.min(mLastX, eventX);
        mDirtyRect.right = Math.max(mLastX, eventX);
        mDirtyRect.top = Math.min(mLastY, eventY);
        mDirtyRect.bottom = Math.max(mLastY, eventY);
    }

    private void getDirtyRect(float historicalX, float historicalY) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX;
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX;
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY;
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY;
        }
    }

    private static final float STROKE_WIDTH = 5f;
    private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
    private final RectF mDirtyRect = new RectF();
}

使用方式

public class HandWritingFragment extends BaseFragment implements View.OnClickListener, PaletteView.Callback {

    @BindView(R.id.mPaletteView)
    PaletteView paletteView;

    private ImageView mPenView, mClearView, mEraserView, mSaveView, mQuitView;
    private ImageView blackPen, redPen, yellowPen, greenPen, pinkPen, purplePen;

    private ProgressDialog progressDialog;
    private UpLoadImagePresenter upLoadImagePresenter;
    private HandWritePrestener handWritePrestener;

    @Override
    protected int getLayoutId() {
        return R.layout.fragment_hand_writing;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        upLoadImagePresenter = new UpLoadImagePresenter(getActivity(), handWriteView);
        handWritePrestener = new HandWritePrestener(getActivity(), handWriteView);

        View containerView = getActivity().getWindow().getDecorView().findViewById(R.id.task_info_container);
        Bitmap bitmap = Bitmap.createBitmap(containerView.getWidth(), containerView.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(Color.WHITE);
        containerView.draw(canvas);
//        paletteView.setBackgroundColor(Color.WHITE);
        paletteView.setBackground(new BitmapDrawable(bitmap));
    }

    @Override
    protected void initialized() {
        blackPen = view.findViewById(R.id.black);
        redPen = view.findViewById(R.id.red);
        yellowPen = view.findViewById(R.id.yellow);
        greenPen = view.findViewById(R.id.green);
        pinkPen = view.findViewById(R.id.pink);
        purplePen = view.findViewById(R.id.purple);
        mPenView = view.findViewById(R.id.pen);
        mClearView = view.findViewById(R.id.clear);
        mEraserView = view.findViewById(R.id.eraser);
        mSaveView = view.findViewById(R.id.save);
        mQuitView = view.findViewById(R.id.quit);
        initProgressDialog();
    }

    @Override
    protected void initListener() {
//        paletteView.setCallback(this);
        mSaveView.setOnClickListener(this);
        mQuitView.setOnClickListener(this);
        mPenView.setOnClickListener(this);
        mClearView.setOnClickListener(this);
        mEraserView.setOnClickListener(this);
        redPen.setOnClickListener(this);
        yellowPen.setOnClickListener(this);
        pinkPen.setOnClickListener(this);
        greenPen.setOnClickListener(this);
        purplePen.setOnClickListener(this);
        blackPen.setOnClickListener(this);
        mPenView.setSelected(true);
        blackPen.setSelected(true);
    }


    public static HandWritingFragment newsInstance() {
        return new HandWritingFragment();
    }

    @Override
    public void widgetClick(View view) {
        switch (view.getId()) {
            case R.id.pen:
                paletteView.setMode(PaletteView.Mode.DRAW);
                paletteView.setPenColor(Color.BLACK);
                mPenView.setSelected(true);
                mEraserView.setSelected(false);
                blackPen.setSelected(true);
                greenPen.setSelected(false);
                pinkPen.setSelected(false);
                yellowPen.setSelected(false);
                redPen.setSelected(false);
                purplePen.setSelected(false);
                break;
            case R.id.eraser:
                paletteView.setMode(PaletteView.Mode.ERASER);
                mEraserView.setSelected(true);
                mPenView.setSelected(false);
                redPen.setSelected(false);
                greenPen.setSelected(false);
                pinkPen.setSelected(false);
                yellowPen.setSelected(false);
                purplePen.setSelected(false);
                blackPen.setSelected(false);
                break;
            case R.id.clear:
                paletteView.clear();
                break;
            case R.id.save:

                progressDialog.show();
                Bitmap bitmap = paletteView.buildBitmap();
                handWritePrestener.saveImageToLocal(bitmap);

                break;
            case R.id.quit:
                popOutBackStack();

                break;
            case R.id.black:
                paletteView.setPenColor(Color.BLACK);
                blackPen.setSelected(true);
                greenPen.setSelected(false);
                pinkPen.setSelected(false);
                yellowPen.setSelected(false);
                purplePen.setSelected(false);
                redPen.setSelected(false);
                break;
            case R.id.red:
                paletteView.setPenColor(Color.RED);
                redPen.setSelected(true);
                greenPen.setSelected(false);
                pinkPen.setSelected(false);
                yellowPen.setSelected(false);
                purplePen.setSelected(false);
                blackPen.setSelected(false);
                break;
            case R.id.green:
                paletteView.setPenColor(Color.GREEN);
                greenPen.setSelected(true);
                pinkPen.setSelected(false);
                yellowPen.setSelected(false);
                purplePen.setSelected(false);
                redPen.setSelected(false);
                blackPen.setSelected(false);
                break;
            case R.id.yellow:
                paletteView.setPenColor(Color.YELLOW);
                yellowPen.setSelected(true);
                redPen.setSelected(false);
                greenPen.setSelected(false);
                pinkPen.setSelected(false);
                purplePen.setSelected(false);
                blackPen.setSelected(false);
                break;
            case R.id.pink:
                paletteView.setPenColor(Color.parseColor("#E771F6"));
                pinkPen.setSelected(true);
                redPen.setSelected(false);
                greenPen.setSelected(false);
                yellowPen.setSelected(false);
                purplePen.setSelected(false);
                blackPen.setSelected(false);
                break;
            case R.id.purple:
                paletteView.setPenColor(Color.parseColor("#6376EB"));
                purplePen.setSelected(true);
                redPen.setSelected(false);
                greenPen.setSelected(false);
                yellowPen.setSelected(false);
                pinkPen.setSelected(false);
                blackPen.setSelected(false);
                break;


        }
    }


    private void initProgressDialog() {
        progressDialog = new ProgressDialog(getActivity());
        progressDialog.setMessage("正在保存,请稍候.....");
        progressDialog.setCancelable(false);

    }

    @Override
    public void onUndoRedoStatusChanged() {

    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().post(new HandWriteExitEvent());
        handWritePrestener.onDestory();
        upLoadImagePresenter.onDestory();

    }

    HandWriteView handWriteView = new HandWriteView() {


        @Override
        public void onError(String msg) {
            if (progressDialog.isShowing())
                progressDialog.dismiss();
            ToastUtil.showToast(getContext(), msg);
        }

        @Override
        public void onFileUploadSuccess(String fileid, String imgURL) {//上传成功
            String teacherId = SPStoreUtil.getString(SystemConstant.sharedName, SystemConstant.teacherID);
            String skjlID = SPStoreUtil.getString(SystemConstant.sharedName, SystemConstant.SKJLID);
            SXBRequestBean sxbRequestBean = new SXBRequestBean();
            sxbRequestBean.setSczid(teacherId);
            sxbRequestBean.setSkjlid(skjlID);
            sxbRequestBean.setFileid(fileid);
            sxbRequestBean.setTplj(imgURL);
            handWritePrestener.saveSXB(sxbRequestBean);
        }

        @Override
        public void onSXBSave() {//保存成功
            if (progressDialog.isShowing())
                progressDialog.dismiss();
            ToastUtil.showToast(getActivity(), "图片保存成功");
        }

        @Override
        public void onLocalSaveImageSuccess(HandWriteSaveBean fileBean) {

            upLoadImagePresenter.updateFile(
                    fileBean.getAppDir() + "/" + fileBean.getFileName()
                    , fileBean.getFileName());

            Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            scanIntent.setData(Uri.fromFile(new File(fileBean.getAbsoluteFilePath())));
            getContext().sendBroadcast(scanIntent);
        }


    };

}

效果还是比之前流畅多了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容