Sunday, January 20, 2013

Planning: Swipe. Animation remove items


Planning: Swipe. Animation remove items
We continue the series of articles on the development of applications for planning cases. Today, we will improve the usability of our list due to comfortable and animated removing items.


Practice

In today's article we will solve two problems:
  • swipe detection on the item;
  • view animating.

1. Swipe detection.
In order to determine what movement made ​​by the user, developed class SwipeDetector, which will implement the interface View.OnTouchListener. This class will be universal, that is possible to determine the swipe in all four directions (top to bottom, bottom to top, left to right, right to left).
The minimum distance for swipe establish the following:
- horizontal swipe: 100 px;
- vertical swipe: 80 px.
All the idea is to determine the difference between the start and end coordinates and compared with a minimum distance.

Source code (SwipeDetector):
/**
 * Class swipe detection  to View
 */
public class SwipeDetector implements View.OnTouchListener {

 public static enum Action {
  LR, // Left to right
  RL, // Right to left
  TB, // Top to bottom
  BT, // Bottom to top
  None // Action not found
 }

 private static final int HORIZONTAL_MIN_DISTANCE = 100; // The minimum distance for horizontal swipe
 private static final int VERTICAL_MIN_DISTANCE = 80; // The minimum distance for vertical swipe
 private float downX, downY, upX, upY; // Coordinates
 private Action mSwipeDetected = Action.None; // Last action

 public boolean swipeDetected() {
  return mSwipeDetected != Action.None;
 }

 public Action getAction() {
  return mSwipeDetected;
 }

 /**
  * Swipe detection
  */
 @Override
 public boolean onTouch(View v, MotionEvent event) {
  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN: {
   downX = event.getX();
   downY = event.getY();
   mSwipeDetected = Action.None;
   return false;
  }
  case MotionEvent.ACTION_MOVE: {
   upX = event.getX();
   upY = event.getY();

   float deltaX = downX - upX;
   float deltaY = downY - upY;
   
   
   // Detection of horizontal swipe
   if (Math.abs(deltaX) > HORIZONTAL_MIN_DISTANCE) {
    // Left to right
    if (deltaX < 0) {
     mSwipeDetected = Action.LR;
     return true;
    }
    // Right to left
    if (deltaX > 0) {
     mSwipeDetected = Action.RL;
     return true;
    }
   } else

   // Detection of vertical swipe
   if (Math.abs(deltaY) > VERTICAL_MIN_DISTANCE) {
    // Top to bottom
    if (deltaY < 0) {
     mSwipeDetected = Action.TB;
     return false;
    }
    // Bottom to top
    if (deltaY > 0) {
     mSwipeDetected = Action.BT;
     return false;
    }
   }
   return true;
  }
  }
  return false;
 }
}

Following the development of the class, this Listener must be set for View.
final SwipeDetector swipeDetector = new SwipeDetector();
listview.setOnTouchListener(swipeDetector);

2. Animation remove items
For animation, we will use the class Animation (android.view.animation. Animation). This class allows you to animate View, using a variety of information such as the start and end position of the item, size, rotation, and more.
Develop a method in our Activity, that will perform the movement of our element from its present location at the edge of the screen. TranslateAnimation class allows you to animate moving from one point to another.
 /**
  * Starting animation remove
  */
 private Animation getDeleteAnimation(float fromX, float toX, int position)
 {
  Animation animation = new TranslateAnimation(fromX, toX, 0, 0);
  animation.setStartOffset(100);
  animation.setDuration(800);
  animation.setAnimationListener(new DeleteAnimationListenter(position));
  animation.setInterpolator(AnimationUtils.loadInterpolator(this,
    android.R.anim.anticipate_overshoot_interpolator));
  return animation;
 }

Our task is to keep track of the ending animation, and then removing item and updating the list. It uses an interface Animation.AnimationListener. It allows you to keep track of the beginning, the end and repeat the animation.
Add the following code in our Activity. Position attribute is used to store the position of the deleted item.
 /**
  * Listenter used to remove an item after the animation has finished remove
  */
 public class DeleteAnimationListenter implements Animation.AnimationListener
 {
   private int position;
   public DeleteAnimationListenter(int position)
   {
    this.position = position;
   }
   @Override
   public void onAnimationEnd(Animation arg0) {  
    removeItem(position);
  }

  @Override
  public void onAnimationRepeat(Animation animation) {

    
  }

  @Override
  public void onAnimationStart(Animation animation) {

  } 
 }


Now add the main logic for removing an element of animation in our Activity.
Add additional static attribute - message to Handler:
public static final int MSG_ANIMATION_REMOVE  = 2;
In Handler add additional case (msg.arg2 shows in which direction you want to animate View):
 case MSG_ANIMATION_REMOVE: // Start animation removing
    View view = (View)msg.obj;
    view.startAnimation(getDeleteAnimation(0, (msg.arg2 == 0) ? -view.getWidth() : 2 * view.getWidth(), msg.arg1));
    break;
We extend the method of OnItemClickListener() for Listview. If swipe been made, the item is removed, or his choice:
 listview.setOnItemClickListener(new OnItemClickListener() {

   @Override
   public void onItemClick(AdapterView parent, View view, int position,
     long id) {  
    // If there is a tap on the Header or Footer, do not do anything
    if (position == 0 || position == list.size() + 1)
     return;
    Message msg = new Message();
    msg.arg1 = position - 1;// If was detected swipe we delete an item
    if (swipeDetector.swipeDetected()){
                    if (swipeDetector.getAction() == SwipeDetector.Action.LR || 
                      swipeDetector.getAction() == SwipeDetector.Action.RL)
                    {
                     msg.what = MSG_ANIMATION_REMOVE;
                     msg.arg2 = swipeDetector.getAction() == SwipeDetector.Action.LR ? 1 : 0;
                     msg.obj = view;
                    }
                } 
    // Otherwise, select an item
    else 
     msg.what = MSG_CHANGE_ITEM;
    handler.sendMessage(msg);   }
  });
Just do not forget the context menu. When you remove an item from the context menu of the direction in which the View will have to move, set at random:
 case MSG_REMOVE_ITEM:  // Remove an item
   Message msg = new Message();
   msg.arg1 = index;
   msg.arg2 = (new Random()).nextInt(2);
   msg.obj = listview.getChildAt(index + 1);
   msg.what = MSG_ANIMATION_REMOVE;
   getHandler().sendMessage(msg);
   //removeItem(index);
   return true;

Links