Пользовательская кисть с операциями отмены и повторения в Android Canvas

Я хочу реализовать приложение для рисования на холсте с помощью пользовательской кисти и операции отмены / повторения. Прежде всего, мой код отлично работает без использования специальной кисти (включая операцию отмены / возврата). Согласно этому ответуКак сделать кастомную кисть для холста в андроиде? Я использовал простые всплески изображений для растрового рисования.

Теперь проблема в том,

Операция отмены, повтора не работает, пользовательские кисти раз за разом рисуют при перемещении точек касания

Q: Как заставить работать операцию отмены / возврата?

Кастомные щетки не гладкие, как должны. Прямо сейчас они выглядят грубыми и искусственными.

В. Как сделать рисование гладким и естественным с помощью пользовательских мазков?

Проверьте мой пример кода здесь,

public class DrawingView extends View {

    private Context ctx;

    private ArrayList<Path> paths = new ArrayList<Path>();
    private ArrayList<Path> undonePaths = new ArrayList<Path>();

    private Map<Path, Float> brushMap = new HashMap<Path, Float>();
    private Map<Path, List<Vector2>> customBrushMap = new HashMap<Path, List<Vector2>>();

    private Bitmap mBitmapBrush;
    private Vector2 mBitmapBrushDimensions;
    private List<Vector2> mPositions = new ArrayList<Vector2>(100);
    private boolean isCustomBrush = false;

    private int selectedColor;
    private float brushSize, lastBrushSize;

    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;

    private Path drawPath;
    private Paint drawPaint, canvasPaint;
    private int paintColor = 0xFF660000, paintAlpha = 255;
    private Canvas drawCanvas;
    private Bitmap canvasBitmap;

    private static final class Vector2 {
        public Vector2(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public final float x;
        public final float y;
    }

    public DrawingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ctx = context;
        setupDrawing();
    }

    private void setupDrawing() {
        brushSize = getResources().getInteger(R.integer.small_size);
        lastBrushSize = brushSize;

        drawPath = new Path();
        drawPaint = new Paint();
        drawPaint.setColor(paintColor);
        drawPaint.setAntiAlias(true);
        drawPaint.setDither(true);
        drawPaint.setStrokeWidth(brushSize);
        drawPaint.setStyle(Paint.Style.STROKE);
        drawPaint.setStrokeJoin(Paint.Join.ROUND);
        drawPaint.setStrokeCap(Paint.Cap.ROUND);
        canvasPaint = new Paint(Paint.DITHER_FLAG);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        drawCanvas = new Canvas(canvasBitmap);
    }

    private void touch_start(float x, float y) {
        undonePaths.clear();
        drawPath.reset();
        drawPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            drawPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
        customBrushMap.put(drawPath, mPositions);
    }

    private void touch_up() {
        drawPath.lineTo(mX, mY);
        drawCanvas.drawPath(drawPath, drawPaint);
        paths.add(drawPath);
        brushMap.put(drawPath, brushSize);
        drawPath = new Path();
        drawPath.reset();
        invalidate();
    }

    public void onClickUndo() {
        if (paths.size() > 0) {
            undonePaths.add(paths.remove(paths.size() - 1));
            invalidate();
        }
    }

    public void onClickRedo() {
        if (undonePaths.size() > 0) {
            paths.add(undonePaths.remove(undonePaths.size() - 1));
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        //detect user touch
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction() & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                invalidate();
                break;

            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                if (isCustomBrush) {
                mPositions.add(new Vector2(x - mBitmapBrushDimensions.x / 2, y - mBitmapBrushDimensions.y / 2));
                }
                touch_move(x, y);
                invalidate();
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                invalidate();
                break;

            case MotionEvent.ACTION_UP:
                touch_up();
                invalidate();
                break;
        }

        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        for (Path p : paths) {

        drawPaint.setColor(colorsMap.get(p));
        drawPaint.setShader(shaderMap.get(p));
        drawPaint.setStrokeWidth(brushMap.get(p));
        drawPaint.setAlpha(opacityMap.get(p));

        if (isCustomBrush) {
            if (customBrushMap.get(p) != null) {
                for (Vector2 pos : customBrushMap.get(p)) {
                    Paint paint = new Paint();
                    ColorFilter filter = new PorterDuffColorFilter(selectedColor, PorterDuff.Mode.SRC_IN);
                    paint.setColorFilter(filter);
                    canvas.drawBitmap(mBitmapBrush, pos.x, pos.y, paint);
                }
            }
        } else {
            canvas.drawPath(p, drawPaint);
            drawPaint.setColor(selectedColor);
            drawPaint.setStrokeWidth(brushSize);
            canvas.drawPath(drawPath, drawPaint);
        }
    }
        canvas.restore();
    }

    public void setCustomBrush(Activity activity, String customBrush) {
        isCustomBrush = true;
        invalidate();
        int patternID = getResources().getIdentifier(customBrush, "drawable", "com.androidapp.drawingstutorial");
        mBitmapBrush = BitmapFactory.decodeResource(getResources(), patternID);
        mBitmapBrushDimensions = new Vector2(mBitmapBrush.getWidth(), mBitmapBrush.getHeight());
    }
}

Ответы на вопрос(1)

Ваш ответ на вопрос