Undo and redo in Canvas for Android
Asked Answered
N

2

6

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

Nit answered 20/6, 2011 at 9:16 Comment(2)
How is indexOfUndoRedo initialized?Muhammadan
I initialize it to -1 (so, when I add the first element, the value will be 0). But still not workingNit
C
9

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!

Chenille answered 15/5, 2012 at 12:41 Comment(3)
+1 nice one just change this line mPaint.setColor(0xFFFFFFFF); to mPaint.setColor(0xff00ff00); working nice becouse 0xFFFFFFFF is white color already background color is white that's why not show in draw path.....Ellene
It's not working perfectly because at the first click on the undo button it's not working, and on the second click it works.Diba
@Diba see this post it's work properly #11115125Viewy
D
2

This is working code. I test it on my own app and it is working very good, may be it's helpful to others. Please comment on it.

  public class Main extends Activity implements OnColorChangedListener {
    // public static int selectedColor = Color.BLACK;
    public static ArrayList<Path> mMaindialog;
    // private ArrayList<Path> undonePaths;
    // public int selectedcolor;
    private static final String COLOR_PREFERENCE_KEY = "color";
    private FrameLayout relativelayout;
    static String sdpath, location;
    Boolean i;
    // Instance variables
    private Bitmap mBitmap = null;
    Bitmap bitmap;
    private Paint mPaint, mBitmapPaint, mPaint1;
    private MyView mView;
    ImageView idd;
    // private Path mPath;
    int slll = Color.BLACK;
    Bitmap map = ListView5.bMap;
    private Button ClearPaint, Colorpaint;
    Ghostdatabase gost;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        idd = (ImageView) findViewById(R.id.imageView1);
        relativelayout = (FrameLayout) findViewById(R.id.frameLayout);

        DisplayMetrics metrics = getBaseContext().getResources()
                .getDisplayMetrics();
        int w = metrics.widthPixels;
        int h = metrics.heightPixels;

        System.out.println(" width " + w);
        System.out.println(" height " + h);

        mView = new MyView(this, w, h);
        mView.setDrawingCacheEnabled(true);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(4);

        ClearPaint = (Button) findViewById(R.id.ne);
        ClearPaint.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                // mBitmap.eraseColor(Color.TRANSPARENT);
                // mPath.reset();
                // mView.invalidate();

                mView.onClickUndo();

            }
        });
        Button save22 = (Button) findViewById(R.id.save);
        save22.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                File cacheDir;
                Toast.makeText(Main.this, "Photo", 500).show();
                Bitmap icon;
                relativelayout.setDrawingCacheEnabled(true);

                icon = Bitmap.createBitmap(relativelayout.getDrawingCache());
                Bitmap bitmap = icon;
                relativelayout.setDrawingCacheEnabled(false);
                // File mFile1 = Environment.getExternalStorageDirectory();
                Date d = new Date();
                String fileName = d.getTime() + "mg1.jpg";

                File storagePath = (Environment.getExternalStorageDirectory());
                File dest = new File(storagePath + "/CityAppImages");

                if (!dest.exists()) {
                    dest.mkdirs();

                }

                File mFile2 = new File(dest, fileName);
                sdpath = mFile2.getAbsolutePath();

                Log.d("qqqqqqqqqqqqqqqqqqqqqqq", "zzzzzzzz" + sdpath);
                try {
                    FileOutputStream outStream;

                    outStream = new FileOutputStream(mFile2);

                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);

                    outStream.flush();

                    outStream.close();
                    Toast.makeText(Main.this, "Photo Saved Sucessfully", 500)
                            .show();
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {

                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    Toast.makeText(Main.this, "Photo Not Saved Sucessfully",
                            500).show();
                }

                gost = new Ghostdatabase(Main.this);
                gost.open();

                gost.insertTitle(sdpath);
            }
        });

        Button view = (Button) findViewById(R.id.listtt);
        view.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {

                Intent ii = new Intent(Main.this, ListView5.class);
                startActivity(ii);

            }
        });

        Button Colorpaint = (Button) findViewById(R.id.Color);
        Colorpaint.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                int color = PreferenceManager.getDefaultSharedPreferences(
                        Main.this).getInt(COLOR_PREFERENCE_KEY, Color.WHITE);
                // int _color = R.color.red;
                new ColorPickerDialog(v.getContext(),
                        new OnColorChangedListener() {

                            public void colorChanged(int color) {
                                mPaint.setColor(color);

                                slll = color;

                                Log.i("TAG", "mpaint one" + mPaint);
                            }
                        }, mPaint.getColor()).show();
                Log.i("TAG", "mpaint two" + mPaint);
            }
        });
        relativelayout.addView(mView);
    }

    // //////////******************* Pinting view
    // *******************///////////////////

    public class MyView extends View implements OnTouchListener {
        private Map<Path, Integer> colorsMap = new HashMap<Path, Integer>();
        private ArrayList<Path> mMaindialog = new ArrayList<Path>();
        private ArrayList<Path> undonePaths = new ArrayList<Path>();
        int colorPicked = slll;
        // Paint mPaint1;

        private Canvas mCanvas;
        private Path mPath;

        public MyView(Context c, int w, int h) {
            super(c);
            if (GlobalVariable.impath == 1) {
                Log.d("", "111111" + GlobalVariable.impath);
                System.out.println(GlobalVariable.impath);
                Intent ii = getIntent();
                location = ii.getStringExtra("IMAGE");
                // bitmap.recycle();
                Log.d("", "location" + location);
                bitmap = BitmapFactory.decodeFile(location);
                mBitmap = Bitmap.createScaledBitmap(bitmap, 300, 300, false);
                Log.d("hhhhhhhhhhhhhhhssssssss", "mBitmap" + mBitmap);
                // mBitmap = Bitmap.createBitmap(100, 100,
                // Bitmap.Config.ARGB_8888);
                // idd.setImageBitmap(mBitmap);
                Log.d("hhhhhhhhhhhhhhhssssssss", "GlobalVariable.impath"
                        + GlobalVariable.impath);
            } else if (GlobalVariable.impath == 2) {
                // bitmap.recycle();
                Log.d("", "22222222222222222222222" + GlobalVariable.impath);
                bitmap = BitmapFactory.decodeResource(getResources(),
                        R.drawable.base);
                mBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, false);
                // mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                Log.d("hhhhhhhhhhhhhhhssssssss1111111", "mBitmap" + mBitmap);
            }

            //
            mCanvas = new Canvas(mBitmap);
            mPath = new Path();

        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);

        }

        @Override
        protected void onDraw(Canvas canvas) {

            canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

            for (Path p : mMaindialog) {
                mPaint.setColor(colorsMap.get(p));
                canvas.drawPath(p, mPaint);
            }
            mPaint.setColor(slll);
            canvas.drawPath(mPath, mPaint);
        }

        // //////************touching evants for painting**************///////
        private float mX, mY;
        private static final float TOUCH_TOLERANCE = 0;

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

        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();
            mPath.reset();
            mMaindialog.add(mPath);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            float x = event.getX();
            float y = event.getY();
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // touch_start(x, y);
                // invalidate();
                undonePaths.clear();
                mPath.reset();
                mPath.moveTo(x, y);
                mX = x;
                mY = y;
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                // touch_move(x, y);
                // invalidate();
                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;
                }
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                // touch_up();
                // invalidate();
                mPath.lineTo(mX, mY);
                mMaindialog.add(mPath);
                colorsMap.put(mPath, slll);
                mPath = new Path();
                mPath.reset();
                invalidate();
                break;
            }
            return true;
        } // end of touch events for image

        private Paint createPen(int colorPicked) {
            // TODO Auto-generated method stub
            mPaint1 = new Paint();
            mPaint1.setColor(colorPicked);
            mPaint1.setAntiAlias(true);
            mPaint1.setDither(true);
            mPaint1.setStyle(Paint.Style.STROKE);
            mPaint1.setStrokeJoin(Paint.Join.ROUND);
            mPaint1.setStrokeCap(Paint.Cap.ROUND);
            // mPaint1.setStrokeWidth(3);
            return mPaint1;
        }

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

            } else {

            }
            // toast the user
        }

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

            else {

            }
        }

        @Override
        public boolean onTouch(View arg0, MotionEvent arg1) {
            // TODO Auto-generated method stub
            return false;
        }
    }// end MyView

    @Override
    public void colorChanged(int color) {
        // TODO Auto-generated method stub
        PreferenceManager.getDefaultSharedPreferences(this).edit()
                .putInt(COLOR_PREFERENCE_KEY, color).commit();
        slll = color;
    }

}
Duna answered 26/7, 2013 at 12:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.