android (singletouch) drawing app undo method not working properly

android, bitmap, canvas, path, touch-event

Solution

updated and works:

- removed Paint paintScreen;

in onDraw do not put `canvas.drawBitmap(bitmap, 0, 0, paintScreen);` or `canvas.drawBitmap(bitmap, 0, 0, paintLine);`

private Bitmap bitmap; // drawing area for display or saving
private Canvas bitmapCanvas; // used to draw on bitmap
private Paint paintLine; // used to draw lines onto bitmap   
private Path mPath; 
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;    

// DoodleView constructor initializes the DoodleView

public DoodleView(Context context, AttributeSet attrs) 
{
   super(context, attrs); // pass context to View's constructor
   this.context_new=context;
   setFocusable(true);
   setFocusableInTouchMode(true);      

   // set the initial display settings for the painted line
   paintLine = new Paint();
   paintLine.setAntiAlias(true); // smooth edges of drawn line
   paintLine.setDither(true);
   paintLine.setColor(Color.BLACK); // default color is black
   paintLine.setStyle(Paint.Style.STROKE); // solid line
   paintLine.setStrokeJoin(Paint.Join.ROUND);
   paintLine.setStrokeWidth(5); // set the default line width
   paintLine.setStrokeCap(Paint.Cap.ROUND); // rounded line ends

   bitmapCanvas = new Canvas();
   mPath = new Path();            
 } // end DoodleView constructor

 // Method onSizeChanged creates BitMap and Canvas after app displays

 @Override
 public void onSizeChanged(int w, int h, int oldW, int oldH)
 {
    super.onSizeChanged(w, h, oldW, oldH);
    DoodlzViewWidth = w;       
    DoodlzViewHeight = h;           
 } 

 @Override

 protected void onDraw(Canvas canvas) 
 {         
   for (Path p : paths){canvas.drawPath(p, paintLine);}  
   canvas.drawPath(mPath, paintLine);
   Log.i("OnDRAWING", "REACH ON DRAW");        
} 

// START TOUCH: handle touch event @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();
              break;
          case MotionEvent.ACTION_MOVE:
             touch_move(x, y);
              invalidate();
              break;
          case MotionEvent.ACTION_UP:
              touch_up();
              invalidate();
              break;
      }
      return true;
}

 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);      
    bitmapCanvas.drawPath(mPath, paintLine);// commit the path to our offscreen  
    paths.add(mPath);
    mPath = new Path(); 
}

public void onClickUndo() 
{ 
   if (paths.size()>0) 
    { 
       undonePaths.add(paths.remove(paths.size()-1));
       invalidate();
    }      
   else Toast.makeText(getContext(), "nothing more to undo", Toast.LENGTH_SHORT).show();  
}

Problem

I am working on a drawing app but facing some undo problems. The coding is as follows: ``` public class DoodleView extends View { Context context_new; private static final float TOUCH_TOLERANCE = 5; private Bitmap bitmap; // drawing area for display or saving private Canvas bitmapCanvas; // used to draw on bitmap private Paint paintScreen; // use to draw bitmap onto screen private Paint paintLine; // used to draw lines onto bitmap private Path mPath; private ArrayList<Path> paths = new ArrayList<Path>(); private ArrayList<Path> undonePaths = new ArrayList<Path>(); private float mX, mY; // DoodleView constructor initializes the DoodleView public DoodleView(Context context, AttributeSet attrs) { super(context, attrs); // pass context to View's constructor this.context_new=context; paintScreen = new Paint(); // used to display bitmap onto screen // set the initial display settings for the painted line paintLine = new Paint(); paintLine.setAntiAlias(true); // smooth edges of drawn line paintLine.setColor(Color.BLACK); // default color is black paintLine.setStyle(Paint.Style.STROKE); // solid line mPath = new Path(); paths.add(mPath); } // end DoodleView constructor ``` OnSizeChanged: ``` @Override public void onSizeChanged(int w, int h, int oldW, int oldH) { super.onSizeChanged(w, h, oldW, oldH); DoodlzViewWidth = w; DoodlzViewHeight = h; bitmap = Bitmap.createBitmap(getWidth(), DoodlzViewHeight, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); bitmap.eraseColor(Color.WHITE); // erase the BitMap with white } ``` onDraw: ``` @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(bitmap, 0, 0, paintScreen); // for each path currently being drawn for (Path p : paths){canvas.drawPath(p, paintLine);} } ``` onTouchEvent: ``` @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchStarted(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: touchMoved(x, y); invalidate(); break; case MotionEvent.ACTION_UP: touchEnded(); invalidate(); break; } return true; } ``` touchStarted: ``` private void touchStarted(float x, float y) { mPath.reset(); mPath.moveTo(x, y); mX = x; mY = y; } ``` touchMoved: ``` private void touchMoved(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; } } ``` touchEnded: ``` private void touchEnded() { mPath.lineTo(mX, mY); bitmapCanvas.drawPath(mPath, paintLine); mPath = new Path(); paths.add(mPath); Toast.makeText(getContext(), "touchEnded" + paths.size(), Toast.LENGTH_SHORT).show(); } ``` Undo: ``` public void onClickUndo() { Toast.makeText(getContext(), "before undo button pressed " + paths.size(), Toast.LENGTH_SHORT).show(); if (paths.size()>0) { undonePaths.add(paths.remove(paths.size()-1)); Toast.makeText(getContext(), "after undo button pressed " + paths.size(), Toast.LENGTH_SHORT).show(); Log.i("UNDOING", "PREPARE INVALIDATE"); invalidate(); Log.i("UNDOING", "FINISH INVALIDATE"); } else Toast.makeText(getContext(), "nothing to undo" + paths.size(), Toast.LENGTH_SHORT).show(); } ``` Question: The above is sourced from other examples searched online. Dont know why need to set path.reset() when implementing `touchStarted`? Q1. When I press the undo the button, it would properly show the `toast` undo button pressed, and reporting the `path.size()` being 0, and hence the immediately previously drawn line is not removed. I really dont know why it is 0?? Isnt it added to the path array already? How could the code be modified? ** code modified after taking android-developer's advice! Thanks!! It now correctly showing path.size(). Sorry for the silly missing out!* But the previous line drawn is still cannot be removed? =( Q2. While the app is functioning properly when the finger is moving on the screen and showing the line immediately, When I press the undo the button, in addition to the above that previous line is not removed, the lines further draw to the screen after pressing the button will not show out until the finger is lifted. answer to Q2: moving the below 2 lines from `touchEnded()` to `touchStarted()` ``` mPath = new Path(); paths.add(mPath); ``` . ``` private void touchStarted(float x, float y) { mPath.reset(); mPath = new Path(); paths.add(mPath); mPath.moveTo(x, y); mX = x; mY = y; } private void touchEnded() { mPath.lineTo(mX, mY); bitmapCanvas.drawPath(mPath, paintLine);// commit the path to our offscreen Toast.makeText(getContext(), "touchEnded" + paths.size(), Toast.LENGTH_SHORT).show(); } ``` Thanks!!!

Original source