Undo and redo in Canvas for Android

2019-03-21 12:49发布

问题:

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

回答1:

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!



回答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;
    }

}