I am working on a drawing project. My code is working perfectly other than canvas redo and undo operations. My undo operation removes paths from the paths
ArrayList and saves to the undonePaths
ArrayList, and the redo operation removes the last element from undonePaths
and saves to paths
.
Here's my code:
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; 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) { //mPath = new Path(); //canvas.drawPath(mPath, mPaint); 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; } }
This code is working perfectly for drawing but not working perfectly for undo and redo operations. What's wrong with my code?
Here's my full source code:
http://www.4shared.com/rar/8PQQEZdH/test_draw.html
Finally my problem was solved; here's my drawing class:
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; 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(); 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) { //mPath = new Path(); //canvas.drawPath(mPath, mPaint); for (Path p : paths){ canvas.drawPath(p, mPaint); } canvas.drawPath(mPath, mPaint); } private float mX, mY; private static final float TOUCH_TOLERANCE = 4; private void touch_start(float x, float y) { undonePaths.clear(); 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 paths.add(mPath); mPath = new Path(); } 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; } }
That code is working perfectly...!
if you draw something else and release it will do the same. However if you click undo it will pop the top image of undo array and print that to canvas and then push it onto the redo stack. redo when clicked will pop from itself and push to undo. the top of undo will be printed after each mouse off.
If “UNDO” string is encountered, pop the top element from Undo stack and push it to Redo stack. If “REDO” string is encountered, pop the top element of Redo stack and push it into the Undo stack. If “READ” string is encountered, print all the elements of the Undo stack in reverse order.
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
At first glance I see the following problems:
Path
to paths
as soon as you make it, you're going to have a problem as soon as you undo: you're popping that empty Path
first, making the first undo not seem to work. Then if you draw into that Path
, it's not added to paths
. The solution is to add the completed Path
to paths in touch_up()
before creating a new one.That is, remove
paths.add(mPath);
from the constructor, and in touch_up()
, change
mPath = new Path(); paths.add(mPath);
to
paths.add(mPath); mPath = new Path();
You'll also want to add
canvas.drawPath(mPath, mPaint);
after your for
loop in onDraw()
in order to draw the in-progress Path
.
undonePaths
when the user starts drawing again.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With