Home | History | Annotate | Download | only in listviewdeletion
      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