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.listviewdeletion; 18 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.List; 22 23 import android.app.Activity; 24 import android.content.Context; 25 import android.os.Bundle; 26 import android.util.SparseBooleanArray; 27 import android.view.View; 28 import android.widget.AdapterView; 29 import android.widget.ArrayAdapter; 30 import android.widget.Button; 31 import android.widget.CheckBox; 32 import android.widget.ListView; 33 34 /** 35 * This example shows how animating ListView views can lead to artifacts if those views are 36 * recycled before you animate them. 37 * 38 * Watch the associated video for this demo on the DevBytes channel of developer.android.com 39 * or on YouTube at https://www.youtube.com/watch?v=NewCSg2JKLk. 40 */ 41 public class ListViewDeletion extends Activity { 42 43 final ArrayList<View> mCheckedViews = new ArrayList<View>(); 44 45 @Override 46 protected void onCreate(Bundle savedInstanceState) { 47 super.onCreate(savedInstanceState); 48 setContentView(R.layout.activity_list_view_deletion); 49 50 final Button deleteButton = (Button) findViewById(R.id.deleteButton); 51 final CheckBox usePositionsCB = (CheckBox) findViewById(R.id.usePositionsCB); 52 final ListView listview = (ListView) findViewById(R.id.listview); 53 final ArrayList<String> cheeseList = new ArrayList<String>(); 54 for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) { 55 cheeseList.add(Cheeses.sCheeseStrings[i]); 56 } 57 final StableArrayAdapter adapter = new StableArrayAdapter(this, 58 android.R.layout.simple_list_item_multiple_choice, cheeseList); 59 listview.setAdapter(adapter); 60 listview.setItemsCanFocus(false); 61 listview.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 62 63 // Clicking the delete button fades out the currently selected views 64 deleteButton.setOnClickListener(new View.OnClickListener() { 65 @Override 66 public void onClick(View v) { 67 SparseBooleanArray checkedItems = listview.getCheckedItemPositions(); 68 int numCheckedItems = checkedItems.size(); 69 for (int i = numCheckedItems - 1; i >= 0; --i) { 70 if (!checkedItems.valueAt(i)) { 71 continue; 72 } 73 int position = checkedItems.keyAt(i); 74 final String item = adapter.getItem(position); 75 if (!usePositionsCB.isChecked()) { 76 // Remove the actual data after the time period that we're going to run 77 // the fading animation 78 v.postDelayed(new Runnable() { 79 @Override 80 public void run() { 81 adapter.remove(item); 82 } 83 }, 300); 84 } else { 85 // This is the correct way to do this: first wee whether the item is 86 // actually visible, and don't bother animating it if it's not. 87 // Next, get the view associated with the item at the time of deletion 88 // (not some old view chosen when the item was clicked). 89 mCheckedViews.clear(); 90 int positionOnScreen = position - listview.getFirstVisiblePosition(); 91 if (positionOnScreen >= 0 && 92 positionOnScreen < listview.getChildCount()) { 93 final View view = listview.getChildAt(positionOnScreen); 94 // All set to fade this view out. Using ViewPropertyAnimator accounts 95 // for possible recycling of views during the animation itself 96 // (see the ListViewAnimations example for more on this). 97 view.animate().alpha(0).withEndAction(new Runnable() { 98 @Override 99 public void run() { 100 view.setAlpha(1); 101 adapter.remove(item); 102 } 103 }); 104 } else { 105 // Not animating the view, but don't delete it yet to avoid making the 106 // list shift due to offscreen deletions 107 v.postDelayed(new Runnable() { 108 @Override 109 public void run() { 110 adapter.remove(item); 111 } 112 }, 300); 113 } 114 } 115 } 116 // THIS IS THE WRONG WAY TO DO THIS 117 // We're basing our decision of the views to be animated based on outdated 118 // information at selection time. Then we're going ahead and running an animation 119 // on those views even when the selected items might not even be in view (in which 120 // case we'll probably be mistakenly fading out something else that is on the 121 // screen and is re-using that recycled view). 122 for (int i = 0; i < mCheckedViews.size(); ++i) { 123 final View checkedView = mCheckedViews.get(i); 124 checkedView.animate().alpha(0).withEndAction(new Runnable() { 125 @Override 126 public void run() { 127 checkedView.setAlpha(1); 128 } 129 }); 130 } 131 mCheckedViews.clear(); 132 adapter.notifyDataSetChanged(); 133 } 134 }); 135 136 listview.setOnItemClickListener(new AdapterView.OnItemClickListener() { 137 138 @Override 139 public void onItemClick(AdapterView<?> parent, final View view, int position, long id) { 140 boolean checked = listview.isItemChecked(position); 141 if (checked) { 142 mCheckedViews.add(view); 143 } else { 144 mCheckedViews.remove(view); 145 } 146 } 147 }); 148 } 149 150 private class StableArrayAdapter extends ArrayAdapter<String> { 151 152 HashMap<String, Integer> mIdMap = new HashMap<String, Integer>(); 153 154 public StableArrayAdapter(Context context, int textViewResourceId, 155 List<String> objects) { 156 super(context, textViewResourceId, objects); 157 for (int i = 0; i < objects.size(); ++i) { 158 mIdMap.put(objects.get(i), i); 159 } 160 } 161 162 @Override 163 public long getItemId(int position) { 164 String item = getItem(position); 165 return mIdMap.get(item); 166 } 167 168 @Override 169 public boolean hasStableIds() { 170 return true; 171 } 172 173 } 174 175 } 176