pearmak
pearmak

Reputation: 5027

android (multitouch) drawing app undone function

I am working on a drawing app, the onTouchEvents are standard, and would like to add Undo() function to remove the last drawn path.

Declarations:

int thelastLineId=0;
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 HashMap<Integer, Path> pathMap; // current Paths being drawn
private HashMap<Integer, Path> reservedpathMap; // for saving the paths being undone
private HashMap<Integer, Point> previousPointMap; // current Points

Constructor:

pathMap = new HashMap<Integer, Path>();
reservedpathMap = new HashMap <Integer,Path>(); // for storing path being undone
previousPointMap = new HashMap<Integer, Point>();

onDraw:

   @Override
   protected void onDraw(Canvas canvas) 
   {
       canvas.drawBitmap(bitmap, 0, 0, paintScreen);
       // for each path currently being drawn
       for (Integer key : pathMap.keySet()) 
            canvas.drawPath(pathMap.get(key), paintLine); // draw line     
   } 

onTouchEvent:

   @Override
   public boolean onTouchEvent(MotionEvent event) 
   {                  
      int action = event.getActionMasked(); // event type 
      int actionIndex = event.getActionIndex(); // pointer (i.e., finger)

      if (action == MotionEvent.ACTION_DOWN) 
      {       
          touchStarted(event.getX(actionIndex), event.getY(actionIndex), event.getPointerId(actionIndex));
      } 
      else if (action == MotionEvent.ACTION_UP) 
      {
          touchEnded(event.getPointerId(actionIndex));
      } 
      else 
      {
         touchMoved(event); 
      } 

      invalidate();
      return true; 
   } 

touchStarted:

   private void touchStarted(float x, float y, int lineID) // lineID represents how many fingers, 1 finger 1 line
   {      
      Path path; // used to store the path for the given touch id
      Point point; // used to store the last point in path

      // if there is already a path for lineID
      if (pathMap.containsKey(lineID)) 
      {
         path = pathMap.get(lineID); // get the Path
         path.reset(); // reset the Path because a new touch has started
         point = previousPointMap.get(lineID); // get Path's last point
      } 
      else 
      {
         path = new Path(); // create a new Path
         pathMap.put(lineID, path); // add the Path to Map
         point = new Point(); // create a new Point
         previousPointMap.put(lineID, point); // add the Point to the Map
      } 

      path.moveTo(x, y);
      point.x = (int) x;  
      point.y = (int) y;  
   } 

touchMoved:

   private void touchMoved(MotionEvent event) 
   {
      // for each of the pointers in the given MotionEvent
      for (int i = 0; i < event.getPointerCount(); i++) 
      {
         // get the pointer ID and pointer index
         int pointerID = event.getPointerId(i);
         int pointerIndex = event.findPointerIndex(pointerID);

         // if there is a path associated with the pointer
         if (pathMap.containsKey(pointerID)) 
         {
            float newX = event.getX(pointerIndex);
            float newY = event.getY(pointerIndex);

            // get the Path and previous Point associated with this pointer
            Path path = pathMap.get(pointerID);
            Point point = previousPointMap.get(pointerID);

            float deltaX = Math.abs(newX - point.x);
            float deltaY = Math.abs(newY - point.y);
            if (deltaX >= TOUCH_TOLERANCE || deltaY >= TOUCH_TOLERANCE) 
            {
               path.quadTo(point.x, point.y, ((newX + point.x)/2),((newY + point.y)/2));

               point.x = (int) newX ; 
               point.y = (int) newY ; 
         } 
      }      
   } 

touchEnded:

   private void touchEnded(int lineID)
   {
      Path path = pathMap.get(lineID); // get the corresponding Path
      bitmapCanvas.drawPath(path, paintLine); 
      path.reset();           
   }

UNDO:

   public void undo()
   {
       Toast.makeText(getContext(), "undo button pressed" + thelastLineId, Toast.LENGTH_SHORT).show();

       Path path = pathMap.get(thelastLineId); 
       reservedpathMap.put(thelastLineId, path); // add the Path to reservedpathMap for later redo
       pathMap.remove(thelastLineId);   

       invalidate();          
   } 

Question:

I am trying to implement the UNDO method using the code as shown above: trying to remove the thelastLindId key from the HashMap pathmap (and put to HashMap reservedpathMap for later Redo) such that when invalidate() it will invoke the OnDraw() and to redraw by

for (Integer key : pathMap.keySet()) 
                canvas.drawPath(pathMap.get(key), paintLine); 

However, pressing the undo button can initiate the toast of "undo is clicked" but the last drawn line fails to disappear.

Could anybody please give me a clue for the Undo() and Redo() ? Many thanks in advance!!

Upvotes: 4

Views: 981

Answers (1)

hardartcore
hardartcore

Reputation: 17037

As I can understand what you want to achieve, you want to be able to draw lines on canvas and after that make UNDO functionality to your project. First of all I think the moment when you add the path to your array should be when the user lift his finger , in your touchEnded method. Second of all I don't really get the thing which you are explaining about two / three fingers? Are you supporting multi touch in your canvas? Here is an implementation which I was using before in some example for drawing on canvas with undo implementation. Hope it will help you to make the things more clear :

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 
}

and here are the equalents of your touch methods :

@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 = 0;

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

As you can see from here paths is the arraylist where I am storing my paths. If you tell me why you need to put your paths in hashmap maybe I can be more helpful.

Upvotes: 1

Related Questions