package cn.rongcloud.im.ui.adapter; import android.content.Context; import android.view.View; import android.view.ViewGroup; import java.util.List; import io.rong.imkit.widget.adapter.BaseAdapter; public abstract class CompositeAdapter<T> extends BaseAdapter<T> { private static final int ITEM_VIEW_BASE_TYPE = 1; private static final String fTag = "CompositeAdapter"; public static class Partition<T> { boolean showIfEmpty; boolean hasHeader; List<T> list; int idColumnIndex; int count; public Partition(boolean showIfEmpty, boolean hasHeader, List<T> list) { this.showIfEmpty = showIfEmpty; this.hasHeader = hasHeader; this.list = list; } /** * True if the directory should be shown even if no contacts are found. */ public boolean getShowIfEmpty() { return showIfEmpty; } public boolean getHasHeader() { return hasHeader; } public List<T> getList() { return list; } } public final Context mContext; private Partition<T>[] mPartitions; private int mSize = 0; private int mCount = 0; private boolean mCacheValid = true; private boolean mNotificationsEnabled = true; private boolean mNotificationNeeded; public CompositeAdapter(Context context) { mContext = context; } public Context getContext() { return mContext; } /** * Registers a partition. The cursor for that partition can be set later. * Partitions should be added in the order they are supposed to appear in * the list. */ public void addPartition(boolean showIfEmpty, boolean hasHeader, List<T> list, int size) { addPartition(new Partition<T>(showIfEmpty, hasHeader, list), size); } @SuppressWarnings("unchecked") public void addPartition(Partition<T> partition, int size) { if (mPartitions == null || mPartitions.length != size) { mPartitions = new Partition[size]; mSize = 0; } mPartitions[mSize++] = partition; // Log.e(fTag, "create Partition -------------- mPartitions = " + mPartitions); invalidate(); notifyDataSetChanged(); } protected Partition<T>[] getPartitions() { return mPartitions; } public void removePartition(int partitionIndex) { System.arraycopy(mPartitions, partitionIndex + 1, mPartitions, partitionIndex, mSize - partitionIndex - 1); mSize--; invalidate(); notifyDataSetChanged(); } /** * Removes cursors for all partitions. */ public void clearPartitions() { if (mSize == 0) { return; } for (int i = 0; i < mSize; i++) { List<T> list = mPartitions[i].list; if (list != null) { list.clear(); mPartitions[i].list = list = null; } } mSize = 0; invalidate(); notifyDataSetChanged(); } public void setHasHeader(int partitionIndex, boolean flag) { mPartitions[partitionIndex].hasHeader = flag; invalidate(); } public void setShowIfEmpty(int partitionIndex, boolean flag) { mPartitions[partitionIndex].showIfEmpty = flag; invalidate(); } public Partition<T> getPartition(int partitionIndex) { if (partitionIndex >= mSize) { throw new ArrayIndexOutOfBoundsException(partitionIndex); } return mPartitions[partitionIndex]; } protected void invalidate() { mCacheValid = false; } public int getPartitionCount() { return mSize; } protected void ensureCacheValid() { if (mCacheValid) { return; } mCount = 0; for (int i = 0; i < mSize; i++) { int count = mPartitions[i].list != null ? mPartitions[i].list.size() : 0; if (mPartitions[i].hasHeader) { if (count != 0 || mPartitions[i].showIfEmpty) { count++; } } mPartitions[i].count = count; mCount += count; } mCacheValid = true; } /** * Returns true if the specified partition was configured to have a header. */ public boolean hasHeader(int partition) { return mPartitions[partition].hasHeader; } /** * Returns the total number of list items in all partitions. */ public int getCount() { ensureCacheValid(); return mCount; } /** * Returns the cursor for the given partition */ public List<T> getData(int partition) { return mPartitions[partition].list; } /** * Changes the cursor for an individual partition. */ public void changeCursor(int partition, List<T> data) { mPartitions[partition].list = data; invalidate(); notifyDataSetChanged(); } /** * Returns true if the specified partition has no cursor or an empty cursor. */ public boolean isPartitionEmpty(int partition) { return mPartitions[partition].list == null || mPartitions[partition].list.size() == 0; } /** * Given a list position, returns the index of the corresponding partition. */ public int getPartitionForPosition(int position) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { return i; } start = end; } return -1; } /** * Given a list position, return the offset of the corresponding item in its * partition. The header, if any, will have offset -1. */ public int getOffsetInPartition(int position) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { int offset = position - start; if (mPartitions[i].hasHeader) { offset--; } return offset; } start = end; } return -1; } /** * Returns the first list position for the specified partition. */ public int getPositionForPartition(int partition) { ensureCacheValid(); int position = 0; for (int i = 0; i < partition; i++) { position += mPartitions[i].count; } return position; } @Override public int getViewTypeCount() { return getItemViewTypeCount() + 1; } /** * Returns the overall number of item view types across all partitions. An * implementation of this method needs to ensure that the returned count is * consistent with the values returned by {@link #getItemViewType(int, int)} * . */ public int getItemViewTypeCount() { return 2; } /** * Returns the view type for the list item at the specified position in the * specified partition. */ protected int getItemViewType(int partition, int position) { return ITEM_VIEW_BASE_TYPE; } @Override public int getItemViewType(int position) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { int offset = position - start; if (mPartitions[i].hasHeader && offset == 0) { // return IGNORE_ITEM_VIEW_TYPE; return ITEM_VIEW_BASE_TYPE + 1; } return getItemViewType(i, position); } start = end; } throw new ArrayIndexOutOfBoundsException(position); } public View getView(int position, View convertView, ViewGroup parent) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { int offset = position - start; if (mPartitions[i].hasHeader) { offset--; } View view; if (offset == -1) { view = getHeaderView(i, mPartitions[i].list, convertView, parent); } else { view = getView(i, mPartitions[i].list, offset, convertView, parent); } if (view == null) { throw new NullPointerException("View should not be null, partition: " + i + " position: " + offset); } return view; } start = end; } throw new ArrayIndexOutOfBoundsException(position); } /** * Returns the header view for the specified partition, creating one if * needed. */ protected View getHeaderView(int partition, List<T> data, View convertView, ViewGroup parent) { View view = convertView != null ? convertView : newHeaderView(mContext, partition, data, parent); bindHeaderView(view, partition, data); return view; } /** * Creates the header view for the specified partition. */ protected View newHeaderView(Context context, int partition, List<T> data, ViewGroup parent) { return null; } /** * Binds the header view for the specified partition. */ protected void bindHeaderView(View view, int partition, List<T> data) { } /** * Returns an item view for the specified partition, creating one if needed. */ protected View getView(int partition, List<T> data, int position, View convertView, ViewGroup parent) { View view; if (convertView != null) { view = convertView; } else { view = newView(mContext, partition, data, position, parent); } bindView(view, partition, data, position); return view; } /** * Creates an item view for the specified partition and position. Position * corresponds directly to the current cursor position. */ protected abstract View newView(Context context, int partition, List<T> data, int position, ViewGroup parent); /** * Binds an item view for the specified partition and position. Position * corresponds directly to the current cursor position. */ protected abstract void bindView(View v, int partition, List<T> data, int position); /** * Returns a pre-positioned cursor for the specified list position. */ public T getItem(int position) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { int offset = position - start; if (mPartitions[i].hasHeader) { offset--; } if (offset == -1) { return null; } return mPartitions[i].list.get(offset); } start = end; } return null; } /** * Returns the item ID for the specified list position. */ public long getItemId(int position) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { int offset = position - start; if (mPartitions[i].hasHeader) { offset--; } if (offset == -1) { return 0; } if (mPartitions[i].idColumnIndex == -1) { return 0; } if (mPartitions[i].list == null || mPartitions[i].list.size() <= 0) { return 0; } return position; } start = end; } return 0; } /** * Returns false if any partition has a header. */ @Override public boolean areAllItemsEnabled() { for (int i = 0; i < mSize; i++) { if (mPartitions[i].hasHeader) { return false; } } return true; } /** * Returns true for all items except headers. */ @Override public boolean isEnabled(int position) { ensureCacheValid(); int start = 0; for (int i = 0; i < mSize; i++) { int end = start + mPartitions[i].count; if (position >= start && position < end) { int offset = position - start; if (mPartitions[i].hasHeader && offset == 0) { return false; } else { return isEnabled(i, offset); } } start = end; } return false; } /** * Returns true if the item at the specified offset of the specified * partition is selectable and clickable. */ protected boolean isEnabled(int partition, int position) { return true; } /** * Enable or disable data change notifications. It may be a good idea to * disable notifications before making changes to several partitions at * once. */ public void setNotificationsEnabled(boolean flag) { mNotificationsEnabled = flag; if (flag && mNotificationNeeded) { notifyDataSetChanged(); } } @Override public void notifyDataSetChanged() { if (mNotificationsEnabled) { mNotificationNeeded = false; super.notifyDataSetChanged(); } else { mNotificationNeeded = true; } } }