A base adapter that handles boilerplate and view reuse.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.animation.Animator; | |
import android.animation.AnimatorListenerAdapter; | |
import android.animation.ValueAnimator; | |
import android.content.Context; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.AbsListView; | |
import android.widget.BaseAdapter; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* @author jaynewstrom | |
* Created on 2/4/14. | |
* | |
* A base adapter that handles boilerplate and view reuse. | |
* | |
* Other useful goodies: | |
* - Animated deleting. | |
* - Refresh views data without calling notifyDataSetChanged. | |
*/ | |
public abstract class ListAdapter<D, V extends View> extends BaseAdapter { | |
private final Context context; | |
private final LayoutInflater layoutInflater; | |
private List<D> data; | |
private int cachedListSize; | |
/** | |
* Create the adapter with an empty list. | |
* Useful for when data is loaded from the network, and you need to initialize the list adapter. | |
*/ | |
public ListAdapter(Context context) { | |
this(context, new ArrayList<D>()); | |
} | |
/** | |
* Create the adapter with an initial list. | |
*/ | |
public ListAdapter(Context context, List<D> dataToSet) { | |
this.context = context; | |
this.layoutInflater = LayoutInflater.from(this.context); | |
setList(dataToSet); | |
} | |
public void add(D item) { | |
data.add(item); | |
cachedListSize = data.size(); | |
notifyDataSetChanged(); | |
} | |
public void add(List<D> moreData) { | |
data.addAll(moreData); | |
cachedListSize = data.size(); | |
notifyDataSetChanged(); | |
} | |
public void insert(D item, int index) { | |
data.add(index, item); | |
cachedListSize = data.size(); | |
notifyDataSetChanged(); | |
} | |
public void delete(D item) { | |
if (data.remove(item)) { | |
cachedListSize = data.size(); | |
notifyDataSetChanged(); | |
} | |
} | |
public void delete(List<D> dataToDelete) { | |
data.removeAll(dataToDelete); | |
cachedListSize = data.size(); | |
notifyDataSetChanged(); | |
} | |
public int indexOf(D item) { | |
return data.indexOf(item); | |
} | |
/** | |
* returns null if the list is empty, otherwise returns the last item in the list | |
*/ | |
public D last() { | |
if (cachedListSize >= 1) { | |
return getItem(cachedListSize - 1); | |
} else { | |
return null; | |
} | |
} | |
public void setList(List<D> dataToSet) { | |
data = new ArrayList<D>(dataToSet); | |
cachedListSize = data.size(); | |
notifyDataSetChanged(); | |
} | |
public Iterable<D> getData() { | |
return data; | |
} | |
public ArrayList<D> getDataCopy() { | |
return new ArrayList<D>(data); | |
} | |
/** | |
* rebinds the view if it is on screen, otherwise it's a no op | |
*/ | |
public void reBindView(D item, AbsListView listView) { | |
int index = indexOf(item); | |
if (index >= 0) { | |
// do the animation on the visible views | |
int firstVisible = listView.getFirstVisiblePosition(); | |
int lastVisible = listView.getLastVisiblePosition(); | |
if (index >= firstVisible && index <= lastVisible) { | |
@SuppressWarnings("unchecked") V view = (V) listView.getChildAt(index - firstVisible); | |
bindDataToView(item, view); | |
} | |
} | |
} | |
/** | |
* if the view is not found: | |
* - no op | |
* if the view is on screen: | |
* - animates the view off screen | |
* - removes the item from the adapter. | |
* if the view is off screen: | |
* - removes the item from the adapter. | |
*/ | |
public void deleteView(final D item, AbsListView listView) { | |
deleteView(item, listView, 0); | |
} | |
/** | |
* This is useful for being called onActivityResult. | |
* | |
* if the view is not found: | |
* - no op | |
* if the view is on screen: | |
* - animates the view off screen, with a small delay | |
* - removes the item from the adapter. | |
* if the view is off screen: | |
* - removes the item from the adapter. | |
*/ | |
public void deleteViewWithSmallDelay(final D item, AbsListView listView) { | |
deleteView(item, listView, 300); | |
} | |
@SuppressWarnings("ConstantConditions") | |
private void deleteView(final D item, AbsListView listView, int delay) { | |
int index = indexOf(item); | |
if (index >= 0) { | |
// do the animation on the visible views | |
int firstVisible = listView.getFirstVisiblePosition(); | |
int lastVisible = listView.getLastVisiblePosition(); | |
if (index >= firstVisible && index <= lastVisible) { | |
final View view = listView.getChildAt(index - firstVisible); | |
final ViewGroup.LayoutParams lp = view.getLayoutParams(); | |
final int originalHeight = lp.height; | |
final int currentHeight = view.getHeight(); | |
ValueAnimator animator = ValueAnimator.ofInt(currentHeight, 1); | |
animator.addListener(new AnimatorListenerAdapter() { | |
@Override | |
public void onAnimationEnd(Animator animation) { | |
delete(item); | |
// Reset view presentation | |
view.setAlpha(1f); | |
view.setTranslationX(0); | |
lp.height = originalHeight; | |
view.setLayoutParams(lp); | |
} | |
}); | |
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator valueAnimator) { | |
lp.height = (Integer) valueAnimator.getAnimatedValue(); | |
view.setLayoutParams(lp); | |
} | |
}); | |
animator.setStartDelay(delay); | |
animator.start(); | |
} else { | |
delete(item); | |
} | |
} | |
} | |
/** | |
* Replace an item with it's upgraded version, | |
* rebinding the new contents without calling notifyDataSetChanged. | |
*/ | |
public void replace(D item, AbsListView listView) { | |
int index = indexOf(item); | |
if (index >= 0) { | |
data.remove(index); | |
data.add(index, item); | |
reBindView(item, listView); | |
} | |
} | |
protected Context getContext() { | |
return context; | |
} | |
@Override public int getCount() { | |
return cachedListSize; | |
} | |
@Override public D getItem(int position) { | |
return data.get(position); | |
} | |
@Override public long getItemId(int position) { | |
return 0; | |
} | |
@Override public V getView(int position, View convertView, ViewGroup parent) { | |
@SuppressWarnings("unchecked") V view = (V) convertView; | |
if (view == null) { | |
view = createView(layoutInflater); | |
} | |
bindDataToView(getItem(position), view); | |
return view; | |
} | |
/** | |
* Create a view to be reused. | |
*/ | |
public abstract V createView(LayoutInflater layoutInflater); | |
/** | |
* Bind the data to the view. | |
*/ | |
public abstract void bindDataToView(D data, V view); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment