понедельник, 21 января 2013 г.

Планирование дел: Летающие ячейки

Планирование дел: Летающие ячейки
Последняя статья из цикла по кастомизации ListView. На этот раз анимируем изменение состояния выбранного элемента. В этой статье мы будем разрабатывать летающие ячейки.


Практика

Заметка: Для того чтобы комментарии в скаченном проекте были читаемы (русский язык), необходимо поменять кодировку проекта на UTF-8 (Project –> Properties –> Resource –> Text file incoding).
Для анимации летающих ячеек необходимо изменить макет main.xml, в котором находиться наш ListView. Добавим в него ImageVIew, который будет выступать в качестве летающей ячейки. Мы будем использовать данную идею по той причине, что анимировать View самого элемента, как мы это делали в предыдущей статье, у нас не получится. Потому что каждая ячейка списка распологается друг под другом, и при перемещении верхних ячеек вниз, их будут перекрывать нижнии ячейки.
Исходный код (main.xml)
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white" >

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="@android:color/transparent" />

    <ImageView
        android:id="@+id/flyingCell"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />

</FrameLayout>
Добавим необходимые атрибуты в Activity.
// Сообщения для Handler'а
public static final int MSG_ANIMATION_FLY   = 3;
// Хранит размеры экрана
private DisplayMetrics dm = new DisplayMetrics();
// Летающая ячейка
private ImageView flying_cell;

В методе onCreate() добавим инициализацию ImageView и DisplayMetrics.
flying_cell = (ImageView)findViewById(R.id.flyingCell);
getWindowManager().getDefaultDisplay().getMetrics(dm);

Разработаем метод в нашем Activity, который будет выполнять перемещение выделенного элемента от его текущего положения к заданному.
  /**
  * Запуск анимации летающей ячейки
  */
 private void startFly(Bitmap cellBitmap, Point start, Point end, ToDoItem item)
 {

  flying_cell.setVisibility(View.VISIBLE);
  flying_cell.setImageBitmap(cellBitmap);
        
  TranslateAnimation translateAnimation = new TranslateAnimation(start.x, end.x, start.y, end.y);
  translateAnimation.setStartOffset(100);
  translateAnimation.setDuration(800);
  translateAnimation.setInterpolator(AnimationUtils.loadInterpolator(this,
                    android.R.anim.accelerate_decelerate_interpolator));

  /* 
   Здесь используется AnimationSet, чтобы можно было добавить еще какую-нибудь
  анимацию (например, сначала увеличить ячейку, потом переместить и уменьшить до
  нормального размера)
   */
  AnimationSet spriteAnimation = new AnimationSet(true);
        spriteAnimation.addAnimation(translateAnimation);
        spriteAnimation.setAnimationListener(new FlyAnimationListenter());
  
  flying_cell.startAnimation(spriteAnimation);
 }

Нашей задачей также является отследить окончание анимации, после чего выполнить обновление списка.
  /**
  * Listenter служит для обновления списка после того, как ячейка прилетела в нужную позицию
  */
 public class FlyAnimationListenter implements Animation.AnimationListener
 {
  
   @Override
  public void onAnimationEnd(Animation arg0) 
  { 
    getHandler().sendEmptyMessage(MSG_CHANGE_ITEM);     
  }

  @Override
  public void onAnimationRepeat(Animation animation) {

    
  }

  @Override
  public void onAnimationStart(Animation animation) {

  } 
 }

Так же добавим метод, который позволяет определить, запущена ли анимация или нет. Это необходимо для того, чтобы одновременно можно было выполнить только одну анимацию.
  /**
  * Запущена ли анимация или нет
  */
 public boolean isAnimationStarted()
 {
  return flying_cell.isShown();
   
 }

В Handler добавляем дополнительный case. Идея летающих ячеек заключается в следующем алгоритме. При выборе ячейки мы получаем ее изображение (Bitmap). Далее расчитываем начальное положение ячейки, как верхняя координата списка + высота заголовка + общий размер всех ячеек, которые предшествуют выбранной ячейке. Затем надо отсортировать список и расчитать по той же формуле конечное положение. После откорректировать конечную точку с учетом размера экрана и выполнить анимацию.
 ...
 case MSG_CHANGE_ITEM: // Завершение анимации с летающими ячейками
    flying_cell.setVisibility(View.GONE);
    adapter.notifyDataSetChanged();
    setCountPurchaseProduct();
    break; 
 ...
 case MSG_ANIMATION_FLY: // Старт анимации летающих ячеек
    Bitmap cellBitmap = (Bitmap)msg.obj;
    ToDoItem item = list.get(msg.arg1);
    item.setCheck(!item.isCheck());
    Point startPoint, endPoint;    
    
    /*
     Нам необходимы следующие значения для поиска позиции:
     height = Высота одной ячейка
     header_height = Высота Header'a листа (так же это высота первой ячейки в листе)
     listTop = Верхняя точка нашего списка - размер проскроллированной области
     */
    int height = listview.getChildAt(1).getHeight();  
    int header_height = listview.getChildAt(0).getBottom();
    int listTop = listview.getTop() - height * listview.getFirstVisiblePosition();
    
    /*
     Поиск начальной и конечной точки происходит по следующему алгоритму:
     1. Находим начальную точку: верхняя точка листа + высота первой ячейки + общая высота всех ячеек, которые предшествуют выбранной ячейки
     2. Сортируем список с учетом новой позиции выбранной ячейки
     3. Находим конечную точку: верхняя точка листа + высота первой ячейки + общая высота всех ячеек, которые предшествуют выбранной ячейки
     */
    startPoint = new Point(0, listTop + header_height + height * getPositionInList(item.getIndex()));
    Utils.sorting(list, 0);
    saveList();
    endPoint = new Point(0, listTop + header_height + height * getPositionInList(item.getIndex())); 
    
    // Откорректируем конечную точку с учетом размеров экрана
    if (endPoint.y > dm.heightPixels)
     endPoint.y = dm.heightPixels;
    else if (endPoint.y < -1*height)
     endPoint.y = -1*height;
    
    // Если ячейка меняет позицию, то запускаем анимацию
    if (endPoint.y != startPoint.y)
     startFly(cellBitmap, startPoint, endPoint, item);
    else 
     handler.sendEmptyMessage(MSG_CHANGE_ITEM);
    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 || isAnimationStarted())
     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           
                {
                 view.setDrawingCacheEnabled(true);              
              Bitmap cellBitmap = view.getDrawingCache().copy(Config.ARGB_8888, false);
              view.setVisibility(View.INVISIBLE);
              msg.obj = cellBitmap;
     msg.what = MSG_ANIMATION_FLY;
                }

    handler.sendMessage(msg);
   }
  });

  • метод OnClickListener () для CheckBox в классе CustomListAdapter:
final View tmp = v;

listitem_check.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    if (((MainActivity)context).isAnimationStarted())
     return;
    tmp.setDrawingCacheEnabled(true);
          
          Bitmap cellBitmap = tmp.getDrawingCache().copy(Config.ARGB_8888, false);
          tmp.setVisibility(View.INVISIBLE);
          Message msg = new Message();
          msg.obj = cellBitmap;
          msg.arg1 = position;
    msg.what = MainActivity.MSG_ANIMATION_FLY;
    ((MainActivity)context).getHandler().sendMessage(msg);
    
   }
  });


Ссылки


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

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