воскресенье, 20 января 2013 г.

Планирование дел: Свайп. Анимация удаления элементов

Планирование дел: Свайп. Анимация удаления элементов
Продолжаем цикл статей по разработке приложения для планирования дел. Сегодня мы улучшим юзабилити нашего листа за счет удобного и анимированного удаления элементов.


Практика

Заметка: Для того чтобы комментарии в скаченном проекте были читаемы (русский язык), необходимо поменять кодировку проекта на UTF-8 (Project –> Properties –> Resource –> Text file incoding).
В сегодняшней статье мы решим две проблемы:
  • обнаружение свайпа по элементу;
  • анимирование View.


1. Обнаружение свайпа.
Для того чтобы определить какое движение совершил пользователь на View, разработаем класс SwipeDetector, который будет реализовывать интерфейс View.OnTouchListener. Этот класс будет универсальным, то есть позволять определять свайп во все четыре направления (сверху вниз, снизу вверх, слева направо, справа налево). 
Минимальное расстояние для свайпа установим следующее:
  • горизонтальный свайп: 100 px;
  • вертикальный свайп: 80 px.
Все идея заключается в определение разности начальной и конечной координаты и в сравнении ее с минимальным расстоянием.

Исходный код (SwipeDetector):
import android.view.MotionEvent;
import android.view.View;

/**
 * Класс для обнаружения свайпа на View
 */
public class SwipeDetector implements View.OnTouchListener {

 public static enum Action {
  LR, // Слева направо
  RL, // Справа налево
  TB, // Сверху вниз
  BT, // Снизу вверх
  None // не обнаружено действий
 }

 private static final int HORIZONTAL_MIN_DISTANCE = 100; // Минимальное расстояние для свайпа по горизонтали
 private static final int VERTICAL_MIN_DISTANCE = 80; // Минимальное расстояние для свайпа по вертикали
 private float downX, downY, upX, upY; // Координаты
 private Action mSwipeDetected = Action.None; // Последнее дейтсвие

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

 public Action getAction() {
  return mSwipeDetected;
 }

 /**
  * Определение свайпа
  */
 @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;

   // Обнаружение горизонтального свайпа
   if (Math.abs(deltaX) > HORIZONTAL_MIN_DISTANCE) {
    // Слева направо
    if (deltaX < 0) {
     mSwipeDetected = Action.LR;
     return true;
    }
    // Справа налево
    if (deltaX > 0) {
     mSwipeDetected = Action.RL;
     return true;
    }
   } else

   // Обнаружение вертикального свайпа
   if (Math.abs(deltaY) > VERTICAL_MIN_DISTANCE) {
    // Сверху вниз
    if (deltaY < 0) {
     mSwipeDetected = Action.TB;
     return false;
    }
    // Снизу вверх
    if (deltaY > 0) {
     mSwipeDetected = Action.BT;
     return false;
    }
   }
   return true;
  }
  }
  return false;
 }
}

После разработки класса необходимо данный Listener установить для ListView.
final SwipeDetector swipeDetector = new SwipeDetector();
listview.setOnTouchListener(swipeDetector);

2. Анимация удаления элемента
Для анимации мы будем использовать класс Animation (android.view.animation. Animation). Данный класс позволяет анимировать View, используя различную информацию, такую как: начальная и конечная позиции элемента, размер, поворот и другое.
Разработаем метод в нашем Activity, который будет выполнять перемещение нашего элемента от его текущего положения за края экрана. Класс TranslateAnimation позволяет выполнять анимацию перемещения от одной точки до другой.
  /**
  * Запуск анимации удаления
  */
 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;
 }
Нашей задачей также является отследить окончание анимации, после чего выполнить удаление элемента и обновление списка. Для этого используется интерфейс  Animation.AnimationListener. Он позволяет отследить начало, окончание и повтор анимации.
Добавим следующий код в наше Activity. Атрибут position служит для хранения позиции удаляемого элемента.
  /**
  * Listenter служит для удаления айтема после того, как анимация удаления завершилась
  */
 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) {

  } 
 }
Теперь добавим основную логику для анимации удаления элемента в нашем Activity.
Добавляем дополнительный статический атрибут – сообщение для Handlera:
public static final int MSG_ANIMATION_REMOVE  = 2;
В Handler добавляем дополнительный case (msg.arg2 показывает в какую сторону необходимо анимировать View):
 case MSG_ANIMATION_REMOVE: // Старт анимации удаления
    View view = (View)msg.obj;
    view.startAnimation(getDeleteAnimation(0, (msg.arg2 == 0) ? -view.getWidth() : 2 * view.getWidth(), msg.arg1));
    break;
Расширим метод OnItemClickListener() для Listview. Если был произведен свайп, то происходит удаление элемента, иначе его выбор:
 listview.setOnItemClickListener(new OnItemClickListener() {

   @Override
   public void onItemClick(AdapterView parent, View view, int position,
     long id) {  
    // Если произошло нажатие по Header или Footer, то ничего не делаем
    if (position == 0 || position == list.size() + 1)
     return;

    Message msg = new Message();
    msg.arg1 = position - 1;
    // Если был обнаружен свайп, то удаляем айтем
    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;
                    }
                } 
    // Иначе выбираем айтем
    else           
     msg.what = MSG_CHANGE_ITEM;

    handler.sendMessage(msg);
   }
  });
Так же не забываем о контекстном меню. При удалении элемента через контекстное меню направление, в котором View должно будет перемещаться, задается случайным образом:
 case MSG_REMOVE_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;

Ссылки

Комментариев нет:

Отправить комментарий