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