blob: d3538231c01c48678cb9599d9a95666192bb80b7 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.listviewdeletion;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ListView;
/**
* This example shows how animating ListView views can lead to artifacts if those views are
* recycled before you animate them.
*
* Watch the associated video for this demo on the DevBytes channel of developer.android.com
* or on YouTube at https://www.youtube.com/watch?v=NewCSg2JKLk.
*/
public class ListViewDeletion extends Activity {
final ArrayList<View> mCheckedViews = new ArrayList<View>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view_deletion);
final Button deleteButton = (Button) findViewById(R.id.deleteButton);
final CheckBox usePositionsCB = (CheckBox) findViewById(R.id.usePositionsCB);
final ListView listview = (ListView) findViewById(R.id.listview);
final ArrayList<String> cheeseList = new ArrayList<String>();
for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) {
cheeseList.add(Cheeses.sCheeseStrings[i]);
}
final StableArrayAdapter adapter = new StableArrayAdapter(this,
android.R.layout.simple_list_item_multiple_choice, cheeseList);
listview.setAdapter(adapter);
listview.setItemsCanFocus(false);
listview.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
// Clicking the delete button fades out the currently selected views
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SparseBooleanArray checkedItems = listview.getCheckedItemPositions();
int numCheckedItems = checkedItems.size();
for (int i = numCheckedItems - 1; i >= 0; --i) {
if (!checkedItems.valueAt(i)) {
continue;
}
int position = checkedItems.keyAt(i);
final String item = adapter.getItem(position);
if (!usePositionsCB.isChecked()) {
// Remove the actual data after the time period that we're going to run
// the fading animation
v.postDelayed(new Runnable() {
@Override
public void run() {
adapter.remove(item);
}
}, 300);
} else {
// This is the correct way to do this: first wee whether the item is
// actually visible, and don't bother animating it if it's not.
// Next, get the view associated with the item at the time of deletion
// (not some old view chosen when the item was clicked).
mCheckedViews.clear();
int positionOnScreen = position - listview.getFirstVisiblePosition();
if (positionOnScreen >= 0 &&
positionOnScreen < listview.getChildCount()) {
final View view = listview.getChildAt(positionOnScreen);
// All set to fade this view out. Using ViewPropertyAnimator accounts
// for possible recycling of views during the animation itself
// (see the ListViewAnimations example for more on this).
view.animate().alpha(0).withEndAction(new Runnable() {
@Override
public void run() {
view.setAlpha(1);
adapter.remove(item);
}
});
} else {
// Not animating the view, but don't delete it yet to avoid making the
// list shift due to offscreen deletions
v.postDelayed(new Runnable() {
@Override
public void run() {
adapter.remove(item);
}
}, 300);
}
}
}
// THIS IS THE WRONG WAY TO DO THIS
// We're basing our decision of the views to be animated based on outdated
// information at selection time. Then we're going ahead and running an animation
// on those views even when the selected items might not even be in view (in which
// case we'll probably be mistakenly fading out something else that is on the
// screen and is re-using that recycled view).
for (int i = 0; i < mCheckedViews.size(); ++i) {
final View checkedView = mCheckedViews.get(i);
checkedView.animate().alpha(0).withEndAction(new Runnable() {
@Override
public void run() {
checkedView.setAlpha(1);
}
});
}
mCheckedViews.clear();
adapter.notifyDataSetChanged();
}
});
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
boolean checked = listview.isItemChecked(position);
if (checked) {
mCheckedViews.add(view);
} else {
mCheckedViews.remove(view);
}
}
});
}
private class StableArrayAdapter extends ArrayAdapter<String> {
HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
public StableArrayAdapter(Context context, int textViewResourceId,
List<String> objects) {
super(context, textViewResourceId, objects);
for (int i = 0; i < objects.size(); ++i) {
mIdMap.put(objects.get(i), i);
}
}
@Override
public long getItemId(int position) {
String item = getItem(position);
return mIdMap.get(item);
}
@Override
public boolean hasStableIds() {
return true;
}
}
}