Home | History | Annotate | Download | only in listviewremovalanimation
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.listviewremovalanimation;
     18 
     19 import java.util.ArrayList;
     20 import java.util.HashMap;
     21 
     22 import android.app.Activity;
     23 import android.os.Bundle;
     24 import android.view.MotionEvent;
     25 import android.view.View;
     26 import android.view.ViewConfiguration;
     27 import android.view.ViewTreeObserver;
     28 import android.widget.ListView;
     29 
     30 /**
     31  * This example shows how to use a swipe effect to remove items from a ListView,
     32  * and how to use animations to complete the swipe as well as to animate the other
     33  * items in the list into their final places.
     34  *
     35  * Watch the associated video for this demo on the DevBytes channel of developer.android.com
     36  * or on YouTube at https://www.youtube.com/watch?v=NewCSg2JKLk.
     37  */
     38 public class ListViewRemovalAnimation extends Activity {
     39 
     40     StableArrayAdapter mAdapter;
     41     ListView mListView;
     42     BackgroundContainer mBackgroundContainer;
     43     boolean mSwiping = false;
     44     boolean mItemPressed = false;
     45     HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>();
     46 
     47     private static final int SWIPE_DURATION = 250;
     48     private static final int MOVE_DURATION = 150;
     49 
     50     @Override
     51     protected void onCreate(Bundle savedInstanceState) {
     52         super.onCreate(savedInstanceState);
     53         setContentView(R.layout.activity_list_view_deletion);
     54 
     55         mBackgroundContainer = (BackgroundContainer) findViewById(R.id.listViewBackground);
     56         mListView = (ListView) findViewById(R.id.listview);
     57         android.util.Log.d("Debug", "d=" + mListView.getDivider());
     58         final ArrayList<String> cheeseList = new ArrayList<String>();
     59         for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) {
     60             cheeseList.add(Cheeses.sCheeseStrings[i]);
     61         }
     62         mAdapter = new StableArrayAdapter(this,R.layout.opaque_text_view, cheeseList,
     63                 mTouchListener);
     64         mListView.setAdapter(mAdapter);
     65     }
     66 
     67     /**
     68      * Handle touch events to fade/move dragged items as they are swiped out
     69      */
     70     private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
     71 
     72         float mDownX;
     73         private int mSwipeSlop = -1;
     74 
     75         @Override
     76         public boolean onTouch(final View v, MotionEvent event) {
     77             if (mSwipeSlop < 0) {
     78                 mSwipeSlop = ViewConfiguration.get(ListViewRemovalAnimation.this).
     79                         getScaledTouchSlop();
     80             }
     81             switch (event.getAction()) {
     82             case MotionEvent.ACTION_DOWN:
     83                 if (mItemPressed) {
     84                     // Multi-item swipes not handled
     85                     return false;
     86                 }
     87                 mItemPressed = true;
     88                 mDownX = event.getX();
     89                 break;
     90             case MotionEvent.ACTION_CANCEL:
     91                 v.setAlpha(1);
     92                 v.setTranslationX(0);
     93                 mItemPressed = false;
     94                 break;
     95             case MotionEvent.ACTION_MOVE:
     96                 {
     97                     float x = event.getX() + v.getTranslationX();
     98                     float deltaX = x - mDownX;
     99                     float deltaXAbs = Math.abs(deltaX);
    100                     if (!mSwiping) {
    101                         if (deltaXAbs > mSwipeSlop) {
    102                             mSwiping = true;
    103                             mListView.requestDisallowInterceptTouchEvent(true);
    104                             mBackgroundContainer.showBackground(v.getTop(), v.getHeight());
    105                         }
    106                     }
    107                     if (mSwiping) {
    108                         v.setTranslationX((x - mDownX));
    109                         v.setAlpha(1 - deltaXAbs / v.getWidth());
    110                     }
    111                 }
    112                 break;
    113             case MotionEvent.ACTION_UP:
    114                 {
    115                     // User let go - figure out whether to animate the view out, or back into place
    116                     if (mSwiping) {
    117                         float x = event.getX() + v.getTranslationX();
    118                         float deltaX = x - mDownX;
    119                         float deltaXAbs = Math.abs(deltaX);
    120                         float fractionCovered;
    121                         float endX;
    122                         float endAlpha;
    123                         final boolean remove;
    124                         if (deltaXAbs > v.getWidth() / 4) {
    125                             // Greater than a quarter of the width - animate it out
    126                             fractionCovered = deltaXAbs / v.getWidth();
    127                             endX = deltaX < 0 ? -v.getWidth() : v.getWidth();
    128                             endAlpha = 0;
    129                             remove = true;
    130                         } else {
    131                             // Not far enough - animate it back
    132                             fractionCovered = 1 - (deltaXAbs / v.getWidth());
    133                             endX = 0;
    134                             endAlpha = 1;
    135                             remove = false;
    136                         }
    137                         // Animate position and alpha of swiped item
    138                         // NOTE: This is a simplified version of swipe behavior, for the
    139                         // purposes of this demo about animation. A real version should use
    140                         // velocity (via the VelocityTracker class) to send the item off or
    141                         // back at an appropriate speed.
    142                         long duration = (int) ((1 - fractionCovered) * SWIPE_DURATION);
    143                         mListView.setEnabled(false);
    144                         v.animate().setDuration(duration).
    145                                 alpha(endAlpha).translationX(endX).
    146                                 withEndAction(new Runnable() {
    147                                     @Override
    148                                     public void run() {
    149                                         // Restore animated values
    150                                         v.setAlpha(1);
    151                                         v.setTranslationX(0);
    152                                         if (remove) {
    153                                             animateRemoval(mListView, v);
    154                                         } else {
    155                                             mBackgroundContainer.hideBackground();
    156                                             mSwiping = false;
    157                                             mListView.setEnabled(true);
    158                                         }
    159                                     }
    160                                 });
    161                     }
    162                 }
    163                 mItemPressed = false;
    164                 break;
    165             default:
    166                 return false;
    167             }
    168             return true;
    169         }
    170     };
    171 
    172     /**
    173      * This method animates all other views in the ListView container (not including ignoreView)
    174      * into their final positions. It is called after ignoreView has been removed from the
    175      * adapter, but before layout has been run. The approach here is to figure out where
    176      * everything is now, then allow layout to run, then figure out where everything is after
    177      * layout, and then to run animations between all of those start/end positions.
    178      */
    179     private void animateRemoval(final ListView listview, View viewToRemove) {
    180         int firstVisiblePosition = listview.getFirstVisiblePosition();
    181         for (int i = 0; i < listview.getChildCount(); ++i) {
    182             View child = listview.getChildAt(i);
    183             if (child != viewToRemove) {
    184                 int position = firstVisiblePosition + i;
    185                 long itemId = mAdapter.getItemId(position);
    186                 mItemIdTopMap.put(itemId, child.getTop());
    187             }
    188         }
    189         // Delete the item from the adapter
    190         int position = mListView.getPositionForView(viewToRemove);
    191         mAdapter.remove(mAdapter.getItem(position));
    192 
    193         final ViewTreeObserver observer = listview.getViewTreeObserver();
    194         observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    195             public boolean onPreDraw() {
    196                 observer.removeOnPreDrawListener(this);
    197                 boolean firstAnimation = true;
    198                 int firstVisiblePosition = listview.getFirstVisiblePosition();
    199                 for (int i = 0; i < listview.getChildCount(); ++i) {
    200                     final View child = listview.getChildAt(i);
    201                     int position = firstVisiblePosition + i;
    202                     long itemId = mAdapter.getItemId(position);
    203                     Integer startTop = mItemIdTopMap.get(itemId);
    204                     int top = child.getTop();
    205                     if (startTop != null) {
    206                         if (startTop != top) {
    207                             int delta = startTop - top;
    208                             child.setTranslationY(delta);
    209                             child.animate().setDuration(MOVE_DURATION).translationY(0);
    210                             if (firstAnimation) {
    211                                 child.animate().withEndAction(new Runnable() {
    212                                     public void run() {
    213                                         mBackgroundContainer.hideBackground();
    214                                         mSwiping = false;
    215                                         mListView.setEnabled(true);
    216                                     }
    217                                 });
    218                                 firstAnimation = false;
    219                             }
    220                         }
    221                     } else {
    222                         // Animate new views along with the others. The catch is that they did not
    223                         // exist in the start state, so we must calculate their starting position
    224                         // based on neighboring views.
    225                         int childHeight = child.getHeight() + listview.getDividerHeight();
    226                         startTop = top + (i > 0 ? childHeight : -childHeight);
    227                         int delta = startTop - top;
    228                         child.setTranslationY(delta);
    229                         child.animate().setDuration(MOVE_DURATION).translationY(0);
    230                         if (firstAnimation) {
    231                             child.animate().withEndAction(new Runnable() {
    232                                 public void run() {
    233                                     mBackgroundContainer.hideBackground();
    234                                     mSwiping = false;
    235                                     mListView.setEnabled(true);
    236                                 }
    237                             });
    238                             firstAnimation = false;
    239                         }
    240                     }
    241                 }
    242                 mItemIdTopMap.clear();
    243                 return true;
    244             }
    245         });
    246     }
    247 
    248 }
    249