Search code examples
javaandroidandroid-canvasundoredo

Undo and redo in Canvas for Android


I am using a customized version of FingerPaint for Android with some other features, like inserting images and moving them. I decided to implement an Undo&Redo, since it will make life just easier. In order to implement it, I finally decided to use a Stack where I push the Drawing Cache of the view, and from where I push the content every time I want to go back to a previous state. So, using the FingerPaint as a basis, I have the following:

private void touch_up() {
    mPath.lineTo(mX, mY);
    // commit the path to our offscreen
    mCanvas.drawPath(mPath, mPaint);
    // I enable the set drawing cache...       
    myView.setDrawingCacheEnabled(true);
    // ... and I add the cache to the stack
    undoStack.add(myView.getDrawingCache());
    indexOfUndoRedo++;
    // kill this so we don't double draw
    mPath.reset();
} 

The stack is being updated only after the touch up at the moment, since I am still figuring out how to solve this. When I want to apply redo, I do the following:

private void undo() {
    myView = new MyView(getActivity());
    myView.setBackgroundDrawable(new BitmapDrawable(undoStack.get(indexOfUndoRedo)));
    indexOfUndoRedo--;
    myView.invalidate();
} 

So far, the application shows the original state of the screen with no change. I also tried to paint it with a white background in order to reset it, but this approach is also not working.

Any idea or suggestion on how to fix this? I would be really thankful :)

Regards


Solution

  • Try below code Draw View:

    package com.draw;
    import java.util.ArrayList;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Path;
    
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnTouchListener;
    import android.widget.ImageView;
    
    public class DrawView extends View implements OnTouchListener {
        private Canvas  mCanvas;
        private Path    mPath;
        private Paint       mPaint;   
        private ArrayList<Path> paths = new ArrayList<Path>();
        private ArrayList<Path> undonePaths = new ArrayList<Path>(); 
    
        private Bitmap im;
        public DrawView(Context context) 
        {
            super(context);
            setFocusable(true);
            setFocusableInTouchMode(true);      
            this.setOnTouchListener(this);
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setDither(true);
            mPaint.setColor(0xFFFFFFFF);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeJoin(Paint.Join.ROUND);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            mPaint.setStrokeWidth(6);
            mCanvas = new Canvas();
            mPath = new Path();
            paths.add(mPath);
    
            im=BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_launcher);
    
    
        }               
            @Override
            protected void onSizeChanged(int w, int h, int oldw, int oldh) {
                super.onSizeChanged(w, h, oldw, oldh);
            }
    
            @Override
            protected void onDraw(Canvas canvas) {            
    
                for (Path p : paths){
                    canvas.drawPath(p, mPaint);
                }
            }
    
            private float mX, mY;
            private static final float TOUCH_TOLERANCE = 4;
    
            private void touch_start(float x, float y) {
                mPath.reset();
                mPath.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) {
                    mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
                    mX = x;
                    mY = y;
                }
            }
            private void touch_up() {
                mPath.lineTo(mX, mY);
                // commit the path to our offscreen
                mCanvas.drawPath(mPath, mPaint);
                // kill this so we don't double draw            
                mPath = new Path();
                paths.add(mPath);
            }
    
            public void onClickUndo () { 
                if (paths.size()>0) 
                { 
                   undonePaths.add(paths.remove(paths.size()-1));
                   invalidate();
                 }
                else
                {
    
                }
                 //toast the user 
            }
    
            public void onClickRedo (){
               if (undonePaths.size()>0) 
               { 
                   paths.add(undonePaths.remove(undonePaths.size()-1)); 
                   invalidate();
               } 
               else 
               {
    
               }
                 //toast the user 
            }
    
        @Override
        public boolean onTouch(View arg0, MotionEvent event) {
              float x = event.getX();
              float y = event.getY();
    
              switch (event.getAction()) {
                  case MotionEvent.ACTION_DOWN:
                      touch_start(x, y);
                      invalidate();
                      break;
                  case MotionEvent.ACTION_MOVE:
                      touch_move(x, y);
                      invalidate();
                      break;
                  case MotionEvent.ACTION_UP:
                      touch_up();
                      invalidate();
                      break;
              }
              return true;
        }
    }
    

    and Draw Activity layout code below:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
     <FrameLayout android:id="@+id/main_frame"
         android:layout_width="fill_parent" android:layout_height="250dp">
    
     </FrameLayout>
            <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Redo" />
    
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Undo" />
    
    </LinearLayout>
    

    and Draw Activity Class below code:

    package com.draw;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.FrameLayout;
    import android.widget.ImageButton;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.Toast;
    
    public class Draw extends Activity {
         ImageView iv1;
        @Override   
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final DrawView drawView = new DrawView(this);
            setContentView(R.layout.main);
            FrameLayout frm_layout=(FrameLayout) findViewById(R.id.main_frame);
            frm_layout.addView(drawView);
            Button btn_undo=(Button) findViewById(R.id.button1);
            btn_undo.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    drawView.onClickUndo();
                }
            });
    
            Button btn_redo=(Button) findViewById(R.id.button2);
            btn_redo.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    drawView.onClickRedo();
                }
            });
        }
    
    }
    

    This is sample paint app with undo and redo operations in android,it work's perfectly for me!