package net.tasksnow.view.reuse;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author D056943
* @since 19:51:34 - 18.12.2012
* @project cFoldersDemo
*/
public class GenericListAdapter extends BaseAdapter implements Filterable {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private List<ListItem> mObjects = new ArrayList<ListItem>();
/**
* Lock used to modify the content of {@link #mObjects}. Any write operation
* performed on the array should be synchronized on this lock. This lock is also
* used by the filter (see {@link #getFilter()} to make a synchronized copy of
* the original array of data.
*/
private final Object mLock = new Object();
/**
* Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever {@link #mObjects} is modified.
*/
private boolean mNotifyOnChange = true;
private final Context mContext;
// A copy of the original mObjects array, initialized from and then used instead as soon as
// the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.
private ArrayList<ListItem> mOriginalValues;
private GenericFilter mFilter;
private final int diffItems;
// ===========================================================
// Constructors
// ===========================================================
public GenericListAdapter(Context context, int diffItems) {
this.diffItems = diffItems;
this.mContext = context;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public int getItemViewType(int position) {
return this.getItem(position).getType();
}
@Override
public int getViewTypeCount() {
return this.diffItems;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return this.getItem(position).getListItemView(convertView, this.getContext());
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return this.getItem(position).isEnabled();
}
/**
* {@inheritDoc}
*/
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
mNotifyOnChange = true;
}
/**
* {@inheritDoc}
*/
@Override
public int getCount() {
return mObjects.size();
}
/**
* {@inheritDoc}
*/
@Override
public ListItem getItem(int position) {
return mObjects.get(position); //TODO java.lang.RuntimeException: native typeface cannot be made
}
/**
* {@inheritDoc}
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* {@inheritDoc}
*/
@Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new GenericFilter();
}
return mFilter;
}
// ===========================================================
// Methods
// ===========================================================
public void refill(Collection<? extends ListItem> entrieList) {
this.clear();
this.addAll(entrieList);
}
/**
* Adds the specified object at the end of the array.
*
* @param object
* The object to add at the end of the array.
*/
public void add(ListItem object) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.add(object);
}
else {
mObjects.add(object);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Adds the specified Collection at the end of the array.
*
* @param collection
* The Collection to add at the end of the array.
*/
public void addAll(Collection<? extends ListItem> collection) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.addAll(collection);
}
else {
mObjects.addAll(collection);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Adds the specified items at the end of the array.
*
* @param items
* The items to add at the end of the array.
*/
public void addAll(ListItem... items) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.addAll(mOriginalValues, items);
}
else {
Collections.addAll(mObjects, items);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Inserts the specified object at the specified index in the array.
*
* @param object
* The object to insert into the array.
* @param index
* The index at which the object must be inserted.
*/
public void insert(ListItem object, int index) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.add(index, object);
}
else {
mObjects.add(index, object);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Removes the specified object from the array.
*
* @param object
* The object to remove.
*/
public void remove(ListItem object) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.remove(object);
}
else {
mObjects.remove(object);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Removes the specified object from the array.
*
* @param object
* The object to remove.
*/
public void remove(int position) {
synchronized (mLock) {
if (mOriginalValues != null) {
if (mOriginalValues.size() > position)
mOriginalValues.remove(position);
}
else {
if (mObjects.size() > position)
mObjects.remove(position);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Remove all elements from the list.
*/
public void clear() {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.clear();
}
else {
mObjects.clear();
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
/**
* Sorts the content of this adapter using the specified comparator.
*
* @param comparator
* The comparator used to sort the objects contained
* in this adapter.
*/
public void sort(Comparator<? super ListItem> comparator) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.sort(mOriginalValues, comparator);
}
else {
Collections.sort(mObjects, comparator);
}
}
if (mNotifyOnChange)
notifyDataSetChanged();
}
// ===========================================================
// Getter & Setter
// ===========================================================
/**
* Control whether methods that change the list ({@link #add}, {@link #insert}, {@link #remove}, {@link #clear}) automatically call
* {@link #notifyDataSetChanged}. If set to false, caller must
* manually call notifyDataSetChanged() to have the changes
* reflected in the attached view.
*
* The default is true, and calling notifyDataSetChanged()
* resets the flag to true.
*
* @param notifyOnChange
* if true, modifications to the list will
* automatically call {@link #notifyDataSetChanged}
*/
public void setNotifyOnChange(boolean notifyOnChange) {
mNotifyOnChange = notifyOnChange;
}
/**
* Returns the context associated with this array adapter. The context is used
* to create views from the resource passed to the constructor.
*
* @return The Context associated with this adapter.
*/
public Context getContext() {
return mContext;
}
/**
* Returns the position of the specified item in the array.
*
* @param item
* The item to retrieve the position of.
*
* @return The position of the specified item.
*/
public int getPosition(ListItem item) {
return mObjects.indexOf(item);
}
public List<ListItem> getObjectList() {
return mObjects;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public interface ListItem {
public boolean filter(CharSequence constraint);
public int getType();
public View getListItemView(View convertView, Context context);
public boolean isEnabled();
}
/**
* <p>
* An array filter constrains the content of the array adapter with a prefix. Each item that does not start with the supplied prefix is
* removed from the list.
* </p>
*/
private class GenericFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We implement here the filter logic
if (mOriginalValues == null) {
synchronized (mLock) {
mOriginalValues = new ArrayList<ListItem>(mObjects);
}
}
if (constraint == null || constraint.length() == 0) {
ArrayList<ListItem> list;
synchronized (mLock) {
list = new ArrayList<ListItem>(mOriginalValues);
}
results.values = list;
results.count = list.size();
}
else {
ArrayList<ListItem> values;
synchronized (mLock) {
values = new ArrayList<ListItem>(mOriginalValues);
}
final ArrayList<ListItem> newValues = new ArrayList<ListItem>();
for (ListItem item : values) {
if (item.filter(constraint)) {
newValues.add(item);
}
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
mObjects = (List<ListItem>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
}
else {
mObjects = new ArrayList<ListItem>();
notifyDataSetInvalidated();
}
}
}
}