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(); } } } }