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
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!
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;
}
}