ANDROID - 撤消和重做帆布 [英] ANDROID - Undo and Redo in canvas

查看:169
本文介绍了ANDROID - 撤消和重做帆布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我吸取了位图圆(触摸),以清除覆盖位图在circle.How该地区做我添加撤消和重做这个功能?

编辑:请参考安卓:在CustomView撤消重做,因为它有问题我目前所面临的给出的解决方案。

  @覆盖
    保护无效的onDraw(帆布油画){
        pcanvas.drawCircle(X,Y,10,mPaint);
        canvas.drawBitmap(位图,0,0,NULL);
        super.onDraw(画布);    }

onTouchEvent

 公共布尔onTouchEvent(MotionEvent EV)
    {
        开关(ev.getAction())
        {
            案例MotionEvent.ACTION_DOWN:
            {                X =(int)的ev.getX();
                Y =(INT)ev.getY();
                无效();                打破;
            }            案例MotionEvent.ACTION_MOVE:
            {               X =(int)的ev.getX();
                Y =(INT)ev.getY();
                无效();
                打破;            }            案例MotionEvent.ACTION_UP:
                无效();
                打破;        }
        返回true;
    }


解决方案

正如评论中提到,你可以保持堆栈跟踪XY坐标的历史。

撤销和重做操作围绕推动并从不同的堆栈弹出。

UndoCanvas

 公共类UndoCanvas扩展视图{
    私人最终诠释MAX_STACK_SIZE = 50;
    私人堆叠式和LT;对<浮球,浮球GT;> undoStack =新的堆栈<>();
    私人堆叠式和LT;对<浮球,浮球GT;> redoStack =新的堆栈<>();    私人位图originalBitmap;
    私人位图maskedBitmap;
    私人帆布originalCanvas;
    私人帆布maskedCanvas;
    私人涂料粉刷;    私人浮动drawRadius;    私人StackListener侦听器;    公共UndoCanvas(上下文的背景下){
        超级(上下文);        在里面();
    }    公共UndoCanvas(上下文的背景下,ATTRS的AttributeSet){
        超(背景下,ATTRS);        在里面();
    }    公共UndoCanvas(上下文的背景下,ATTRS的AttributeSet,诠释defStyleAttr){
        超(背景下,ATTRS,defStyleAttr);        在里面();
    }    私人无效的init(){
        drawRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,5,getResources()getDisplayMetrics());        油漆=新的油漆();
        // paint.setColor(Color.RED);        paint.setAlpha(0);
        paint.setXfermode(新PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setAntiAlias​​(真);
        paint.setMaskFilter(新BlurMaskFilter(15,BlurMaskFilter.Blur.SOLID));
    }    公共无效setBitmap(位图位图){
        如果(位图!= NULL){
            originalBitmap = bitmap.copy(bitmap.getConfig(),TRUE); //正本复印件,因为我们将有可能更改本
            maskedBitmap = originalBitmap.copy(originalBitmap.getConfig(),TRUE);
            originalCanvas =新的Canvas(originalBitmap);
            maskedCanvas =新的Canvas(maskedBitmap);
        }其他{
            originalBitmap = NULL;
            originalCanvas = NULL;
            maskedBitmap = NULL;
            maskedCanvas = NULL;
        }        INT undoSize = undoStack.size();
        INT redoSize = redoStack.size();        undoStack.clear();
        redoStack.clear();        无效();        如果(听众!= NULL){
            如果(undoSize!= undoStack.size()){
                listener.onUndoStackChanged(undoSize,undoStack.size());
            }
            如果(redoSize!= redoStack.size()){
                listener.onRedoStackChanged(redoSize,redoStack.size());
            }
        }
    }    公共StackListener getListener(){
        返回侦听器;
    }    公共无效使用setListener(StackListener监听){
        this.listener =侦听器;
    }    公共布尔onTouchEvent(MotionEvent EV){
        开关(ev.getAction()){
            案例MotionEvent.ACTION_DOWN:{
                INT undoSize = undoStack.size();
                INT redoSize = redoStack.size();                //最大堆栈大小。增加新的前删除最早的项目
                如果(undoStack.size()== MAX_STACK_SIZE){
                    //撤消历史不会走得更远回来,所以确认修改,通过更新原来的帆布/位图
                    对<浮球,浮球GT;对= undoStack.remove(0);
                    maskPoint(originalCanvas,pair.first,pair.second);
                }                undoStack.push(新配对<>(ev.getX(),ev.getY()));
                redoStack.clear();
                无效();                如果(听众!= NULL){
                    如果(undoSize!= undoStack.size()){
                        listener.onUndoStackChanged(undoSize,undoStack.size());
                    }
                    如果(redoSize!= redoStack.size()){
                        listener.onRedoStackChanged(redoSize,redoStack.size());
                    }
                }                打破;
            }            案例MotionEvent.ACTION_MOVE:{
                INT undoSize = undoStack.size();
                INT redoSize = redoStack.size();                //最大堆栈大小。增加新的前删除最早的项目
                如果(undoStack.size()== MAX_STACK_SIZE){
                    //撤消历史不会走得更远回来,所以确认修改,通过更新原来的帆布/位图
                    对<浮球,浮球GT;对= undoStack.remove(0);
                    maskPoint(originalCanvas,pair.first,pair.second);
                }                maskPoint(maskedCanvas,ev.getX(),ev.getY());
                undoStack.push(新配对<>(ev.getX(),ev.getY()));
                redoStack.clear();
                无效();                如果(听众!= NULL){
                    如果(undoSize!= undoStack.size()){
                        listener.onUndoStackChanged(undoSize,undoStack.size());
                    }
                    如果(redoSize!= redoStack.size()){
                        listener.onRedoStackChanged(redoSize,redoStack.size());
                    }
                }
                打破;            }            案例MotionEvent.ACTION_UP:
                无效();
                打破;        }
        返回true;
    }    @覆盖
    保护无效的onDraw(帆布油画){
        如果(maskedBitmap!= NULL){
            canvas.drawBitmap(maskedBitmap,0,0,NULL);
        }
        super.onDraw(画布);    }    公共布尔撤消(){
        如果(!undoStack.empty()){
            INT undoSize = undoStack.size();
            INT redoSize = redoStack.size();            对<浮球,浮球GT;对= undoStack.pop();
            //重绘原始位图的单个部分
            // unmaskPoint(maskedCanvas,pair.first,pair.second);            //重绘原始位图,在撤消堆栈的所有点沿
            remaskCanvas(maskedCanvas);            redoStack.push(对); //不需要检查> 50在这里,因为redoStack只能包含什么在undoStack
            无效();            如果(听众!= NULL){
                如果(undoSize!= undoStack.size()){
                    listener.onUndoStackChanged(undoSize,undoStack.size());
                }
                如果(redoSize!= redoStack.size()){
                    listener.onRedoStackChanged(redoSize,redoStack.size());
                }
            }            返回true;
        }        返回false;
    }    公共布尔重做(){
        如果(!redoStack.empty()){
            INT undoSize = undoStack.size();
            INT redoSize = redoStack.size();            对<浮球,浮球GT;对= redoStack.pop();
            maskPoint(maskedCanvas,pair.first,pair.second);
            undoStack.push(对); //不需要检查> 50在这里,因为redoStack只能包含什么在undoStack
            无效();            如果(听众!= NULL){
                如果(undoSize!= undoStack.size()){
                    listener.onUndoStackChanged(undoSize,undoStack.size());
                }
                如果(redoSize!= redoStack.size()){
                    listener.onRedoStackChanged(redoSize,redoStack.size());
                }
            }            返回true;
        }        返回false;
    }    私人无效maskPoint(帆布油画,浮法X,浮法Y){
        如果(帆布!= NULL){
            canvas.drawCircle(X,Y,drawRadius,油漆);
        }
    }    私人无效unmaskPoint(帆布油画,浮法X,浮法Y){
        如果(帆布!= NULL){
            路径path =新路径();
            path.addCircle(X,Y,drawRadius,Path.Direction.CW);            canvas.save();
            canvas.clipPath(路径);
            canvas.drawBitmap(originalBitmap,0,0,新的油漆());
            canvas.restore();
        }
    }    私人无效remaskCanvas(帆布油画){
        如果(帆布!= NULL){
            canvas.drawBitmap(originalBitmap,0,0,新的油漆());            的for(int i = 0; I< undoStack.size();我++){
                对<浮球,浮球GT;对= undoStack.get(I)
                maskPoint(帆布,pair.first,pair.second);
            }
        }
    }    公共接口StackListener {
        无效onUndoStackChanged(INT previousSize,INT newSize);        无效onRedoStackChanged(INT previousSize,INT newSize);
    }
}

当用户在屏幕上拖动你想限制这些堆栈的大小,使他们不溢出。你可以玩的数目,但50似乎是一个良好的开端我。

修改

作为一个侧面说明,这会是个不错重做/同时撤消多个条目。由于 onTouchEvent 将触发非常精细的动作。运动时撤销/重做是pressed用户不会注意到。

编辑2

我已加入到上述实施方式中,以处理撤消为好。我发现,只有重绘在特定点的战略是不够的重叠点是不正确的。 (点A和B的重叠,在A的第取出b。结果被清除)。

由于此我remask上撤消操作的整个位图,这意味着它们的中间的位图需要撤消。没有中间位,撤消操作将导致在这点从被删除,以及在堆栈(最多50个)不再。既然我们不支持撤消通过了这一点,使用原始位图作为中间位图就足够了。

这两种方法都在code,所以你可以测试他们两个。

最后,我添加了一个监听器,以使活动要知道堆栈的状态。要启用/禁用的按钮。

MainActivity

 公共类MainActivity扩展AppCompatActivity {
    @覆盖
    保护无效的onCreate(捆绑savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.activity_main);        最后UndoCanvas帆布=(UndoCanvas)findViewById(R.id.undoCanvas);
        最终按钮undoButton =(按钮)findViewById(R.id.buttonUndo);
        最终按钮redoButton =(按钮)findViewById(R.id.buttonRedo);
        undoButton.setEnabled(假);
        redoButton.setEnabled(假);        canvas.setListener(新UndoCanvas.StackListener(){
            @覆盖
            公共无效onUndoStackChanged(INT previousSize,INT newSize){
                undoButton.setEnabled(newSize大于0);
            }            @覆盖
            公共无效onRedoStackChanged(INT previousSize,INT newSize){
                redoButton.setEnabled(newSize大于0);
            }
        });        undoButton.setOnClickListener(新View.OnClickListener(){
            @覆盖
            公共无效的onClick(视图v){
                canvas.undo();
            }
        });        redoButton.setOnClickListener(新View.OnClickListener(){
            @覆盖
            公共无效的onClick(视图v){
                canvas.redo();
            }
        });        canvas.setBitmap(BitmapFactory.de codeResource(getResources(),R.drawable.image));
    }
}

屏幕截图

撤消之前

撤消操作(S)前屏幕

撤消后

撤消操作(S)后,屏幕

I am drawing a circle (on touch) on bitmap to erase the overlay bitmap for that area in the circle.How do I add undo and redo functionality for this?

EDIT:Please refer Android: Undo redo in CustomView as it has the problem I'm currently facing with the given solution.

@Override
    protected void onDraw(Canvas canvas) {
        pcanvas.drawCircle(x, y, 10, mPaint);
        canvas.drawBitmap(bitmap, 0, 0, null);
        super.onDraw(canvas);

    }

onTouchEvent

public boolean onTouchEvent(MotionEvent ev) 
    {
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
            {

                x = (int) ev.getX();
                y = (int) ev.getY();
                invalidate();

                break;
            }

            case MotionEvent.ACTION_MOVE:
            {

               x = (int) ev.getX();
                y = (int) ev.getY();
                invalidate();
                break;

            }

            case MotionEvent.ACTION_UP:
                invalidate();
                break;

        }
        return true;
    }

解决方案

As mentioned in the comments, you could keep Stacks to track the xy coordinate history.

Undo and Redo operations revolve around pushing and popping from the separate stacks.

UndoCanvas

public class UndoCanvas extends View {
    private final int MAX_STACK_SIZE = 50;
    private Stack<Pair<Float, Float>> undoStack = new Stack<>();
    private Stack<Pair<Float, Float>> redoStack = new Stack<>();

    private Bitmap originalBitmap;
    private Bitmap maskedBitmap;
    private Canvas originalCanvas;
    private Canvas maskedCanvas;
    private Paint paint;

    private float drawRadius;

    private StackListener listener;

    public UndoCanvas(Context context) {
        super(context);

        init();
    }

    public UndoCanvas(Context context, AttributeSet attrs) {
        super(context, attrs);

        init();
    }

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

        init();
    }

    private void init() {
        drawRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());

        paint = new Paint();
        // paint.setColor(Color.RED);

        paint.setAlpha(0);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setAntiAlias(true);
        paint.setMaskFilter(new BlurMaskFilter(15, BlurMaskFilter.Blur.SOLID));
    }

    public void setBitmap(Bitmap bitmap) {
        if (bitmap != null) {
            originalBitmap = bitmap.copy(bitmap.getConfig(), true); // Copy of the original, because we will potentially make changes to this
            maskedBitmap = originalBitmap.copy(originalBitmap.getConfig(), true);
            originalCanvas = new Canvas(originalBitmap);
            maskedCanvas = new Canvas(maskedBitmap);
        } else {
            originalBitmap = null;
            originalCanvas = null;
            maskedBitmap = null;
            maskedCanvas = null;
        }

        int undoSize = undoStack.size();
        int redoSize = redoStack.size();

        undoStack.clear();
        redoStack.clear();

        invalidate();

        if (listener != null) {
            if (undoSize != undoStack.size()) {
                listener.onUndoStackChanged(undoSize, undoStack.size());
            }
            if (redoSize != redoStack.size()) {
                listener.onRedoStackChanged(redoSize, redoStack.size());
            }
        }
    }

    public StackListener getListener() {
        return listener;
    }

    public void setListener(StackListener listener) {
        this.listener = listener;
    }

    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                int undoSize = undoStack.size();
                int redoSize = redoStack.size();

                // Max stack size. Remove oldest item before adding new
                if (undoStack.size() == MAX_STACK_SIZE) {
                    // The undo history does not go further back, so make the change permanent by updating the original canvas/bitmap
                    Pair<Float, Float> pair = undoStack.remove(0);
                    maskPoint(originalCanvas, pair.first, pair.second);
                }

                undoStack.push(new Pair<>(ev.getX(), ev.getY()));
                redoStack.clear();
                invalidate();

                if (listener != null) {
                    if (undoSize != undoStack.size()) {
                        listener.onUndoStackChanged(undoSize, undoStack.size());
                    }
                    if (redoSize != redoStack.size()) {
                        listener.onRedoStackChanged(redoSize, redoStack.size());
                    }
                }

                break;
            }

            case MotionEvent.ACTION_MOVE: {
                int undoSize = undoStack.size();
                int redoSize = redoStack.size();

                // Max stack size. Remove oldest item before adding new
                if (undoStack.size() == MAX_STACK_SIZE) {
                    // The undo history does not go further back, so make the change permanent by updating the original canvas/bitmap
                    Pair<Float, Float> pair = undoStack.remove(0);
                    maskPoint(originalCanvas, pair.first, pair.second);
                }

                maskPoint(maskedCanvas, ev.getX(), ev.getY());
                undoStack.push(new Pair<>(ev.getX(), ev.getY()));
                redoStack.clear();
                invalidate();

                if (listener != null) {
                    if (undoSize != undoStack.size()) {
                        listener.onUndoStackChanged(undoSize, undoStack.size());
                    }
                    if (redoSize != redoStack.size()) {
                        listener.onRedoStackChanged(redoSize, redoStack.size());
                    }
                }
                break;

            }

            case MotionEvent.ACTION_UP:
                invalidate();
                break;

        }
        return true;
    }

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

    }

    public boolean undo() {
        if (!undoStack.empty()) {
            int undoSize = undoStack.size();
            int redoSize = redoStack.size();

            Pair<Float, Float> pair = undoStack.pop();
            // Redraw a single part of the original bitmap
            //unmaskPoint(maskedCanvas, pair.first, pair.second);

            // Redraw the original bitmap, along with all the points in the undo stack
            remaskCanvas(maskedCanvas);

            redoStack.push(pair); // Do not need to check for > 50 here, since redoStack can only contain what was in undoStack
            invalidate();

            if (listener != null) {
                if (undoSize != undoStack.size()) {
                    listener.onUndoStackChanged(undoSize, undoStack.size());
                }
                if (redoSize != redoStack.size()) {
                    listener.onRedoStackChanged(redoSize, redoStack.size());
                }
            }

            return true;
        }

        return false;
    }

    public boolean redo() {
        if (!redoStack.empty()) {
            int undoSize = undoStack.size();
            int redoSize = redoStack.size();

            Pair<Float, Float> pair = redoStack.pop();
            maskPoint(maskedCanvas, pair.first, pair.second);
            undoStack.push(pair); // Do not need to check for > 50 here, since redoStack can only contain what was in undoStack
            invalidate();

            if (listener != null) {
                if (undoSize != undoStack.size()) {
                    listener.onUndoStackChanged(undoSize, undoStack.size());
                }
                if (redoSize != redoStack.size()) {
                    listener.onRedoStackChanged(redoSize, redoStack.size());
                }
            }

            return true;
        }

        return false;
    }

    private void maskPoint(Canvas canvas, float x, float y) {
        if (canvas != null) {
            canvas.drawCircle(x, y, drawRadius, paint);
        }
    }

    private void unmaskPoint(Canvas canvas, float x, float y) {
        if (canvas != null) {
            Path path = new Path();
            path.addCircle(x, y, drawRadius, Path.Direction.CW);

            canvas.save();
            canvas.clipPath(path);
            canvas.drawBitmap(originalBitmap, 0, 0, new Paint());
            canvas.restore();
        }
    }

    private void remaskCanvas(Canvas canvas) {
        if (canvas != null) {
            canvas.drawBitmap(originalBitmap, 0, 0, new Paint());

            for (int i = 0; i < undoStack.size(); i++) {
                Pair<Float, Float> pair = undoStack.get(i);
                maskPoint(canvas, pair.first, pair.second);
            }
        }
    }

    public interface StackListener {
        void onUndoStackChanged(int previousSize, int newSize);

        void onRedoStackChanged(int previousSize, int newSize);
    }
}

You would want to limit the size of these stack so they don't overflow as a user drags across the screen. You can play around with the number, but 50 seems like a good start for me.

EDIT

As a side note, it might be good to redo / undo multiple entries at a time. Since onTouchEvent will trigger for very fine movements. Movements that the user would not notice when undo / redo are pressed.

EDIT 2

I have added to the above implementation, to handle the undo as well. I found that the strategy that only redraws at the specific point is insufficient as overlapping points are incorrect. (Point A and B overlap, removing B results in a subsection of A being cleared).

Because of this I remask the entire bitmap on an undo operation, this means that an intermediate bitmap is required for undoing. Without the intermediate bitmap, the undo operation will result in points no longer in the stack (50 max) from being removed as well. Since we do not support undo passed that point, using the original bitmap as the intermediate bitmap is sufficient.

Both methods are in the code so you can test both of them.

Lastly, I added a Listener to allow the Activity to know the state of the stacks. To Enable / Disable the buttons.

MainActivity

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final UndoCanvas canvas = (UndoCanvas) findViewById(R.id.undoCanvas);
        final Button undoButton = (Button) findViewById(R.id.buttonUndo);
        final Button redoButton = (Button) findViewById(R.id.buttonRedo);
        undoButton.setEnabled(false);
        redoButton.setEnabled(false);

        canvas.setListener(new UndoCanvas.StackListener() {
            @Override
            public void onUndoStackChanged(int previousSize, int newSize) {
                undoButton.setEnabled(newSize > 0);
            }

            @Override
            public void onRedoStackChanged(int previousSize, int newSize) {
                redoButton.setEnabled(newSize > 0);
            }
        });

        undoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                canvas.undo();
            }
        });

        redoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                canvas.redo();
            }
        });

        canvas.setBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.image));
    }
}

Screenshots

Before Undo

After Undo

这篇关于ANDROID - 撤消和重做帆布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆