package org.holoeverywhere.widget; import java.util.ArrayList; import java.util.Collections; import android.database.DataSetObserver; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ExpandableListAdapter; import android.widget.Filter; import android.widget.Filterable; public class ExpandableListConnector extends BaseAdapter implements Filterable { static class GroupMetadata implements Parcelable, Comparable<GroupMetadata> { public static final Parcelable.Creator<GroupMetadata> CREATOR = new Parcelable.Creator<GroupMetadata>() { @Override public GroupMetadata createFromParcel(Parcel in) { GroupMetadata gm = GroupMetadata.obtain( in.readInt(), in.readInt(), in.readInt(), in.readLong()); return gm; } @Override public GroupMetadata[] newArray(int size) { return new GroupMetadata[size]; } }; final static int REFRESH = -1; static GroupMetadata obtain(int flPos, int lastChildFlPos, int gPos, long gId) { GroupMetadata gm = new GroupMetadata(); gm.flPos = flPos; gm.lastChildFlPos = lastChildFlPos; gm.gPos = gPos; gm.gId = gId; return gm; } int flPos; long gId; int gPos; int lastChildFlPos; private GroupMetadata() { } @Override public int compareTo(GroupMetadata another) { if (another == null) { throw new IllegalArgumentException(); } return gPos - another.gPos; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(flPos); dest.writeInt(lastChildFlPos); dest.writeInt(gPos); dest.writeLong(gId); } } protected class MyDataSetObserver extends DataSetObserver { @Override public void onChanged() { refreshExpGroupMetadataList(true, true); notifyDataSetChanged(); } @Override public void onInvalidated() { refreshExpGroupMetadataList(true, true); notifyDataSetInvalidated(); } } static public class PositionMetadata { private static final int MAX_POOL_SIZE = 5; private static ArrayList<PositionMetadata> sPool = new ArrayList<PositionMetadata>(MAX_POOL_SIZE); private static PositionMetadata getRecycledOrCreate() { PositionMetadata pm; synchronized (sPool) { if (sPool.size() > 0) { pm = sPool.remove(0); } else { return new PositionMetadata(); } } pm.resetState(); return pm; } static PositionMetadata obtain(int flatListPos, int type, int groupPos, int childPos, GroupMetadata groupMetadata, int groupInsertIndex) { PositionMetadata pm = getRecycledOrCreate(); pm.position = ExpandableListPosition.obtain(type, groupPos, childPos, flatListPos); pm.groupMetadata = groupMetadata; pm.groupInsertIndex = groupInsertIndex; return pm; } public int groupInsertIndex; public GroupMetadata groupMetadata; public ExpandableListPosition position; private PositionMetadata() { } public boolean isExpanded() { return groupMetadata != null; } public void recycle() { resetState(); synchronized (sPool) { if (sPool.size() < MAX_POOL_SIZE) { sPool.add(this); } } } private void resetState() { if (position != null) { position.recycle(); position = null; } groupMetadata = null; groupInsertIndex = 0; } } private final DataSetObserver mDataSetObserver = new MyDataSetObserver(); private ExpandableListAdapter mExpandableListAdapter; private ArrayList<GroupMetadata> mExpGroupMetadataList; private int mMaxExpGroupCount = Integer.MAX_VALUE; private int mTotalExpChildrenCount; public ExpandableListConnector(ExpandableListAdapter expandableListAdapter) { mExpGroupMetadataList = new ArrayList<GroupMetadata>(); setExpandableListAdapter(expandableListAdapter); } @Override public boolean areAllItemsEnabled() { return mExpandableListAdapter.areAllItemsEnabled(); } boolean collapseGroup(int groupPos) { ExpandableListPosition elGroupPos = ExpandableListPosition.obtain( ExpandableListPosition.GROUP, groupPos, -1, -1); PositionMetadata pm = getFlattenedPos(elGroupPos); elGroupPos.recycle(); if (pm == null) { return false; } boolean retValue = collapseGroup(pm); pm.recycle(); return retValue; } boolean collapseGroup(PositionMetadata posMetadata) { if (posMetadata.groupMetadata == null) { return false; } mExpGroupMetadataList.remove(posMetadata.groupMetadata); refreshExpGroupMetadataList(false, false); notifyDataSetChanged(); mExpandableListAdapter.onGroupCollapsed(posMetadata.groupMetadata.gPos); return true; } boolean expandGroup(int groupPos) { ExpandableListPosition elGroupPos = ExpandableListPosition.obtain( ExpandableListPosition.GROUP, groupPos, -1, -1); PositionMetadata pm = getFlattenedPos(elGroupPos); elGroupPos.recycle(); boolean retValue = expandGroup(pm); pm.recycle(); return retValue; } boolean expandGroup(PositionMetadata posMetadata) { if (posMetadata.position.groupPos < 0) { throw new RuntimeException("Need group"); } if (mMaxExpGroupCount == 0) { return false; } if (posMetadata.groupMetadata != null) { return false; } if (mExpGroupMetadataList.size() >= mMaxExpGroupCount) { GroupMetadata collapsedGm = mExpGroupMetadataList.get(0); int collapsedIndex = mExpGroupMetadataList.indexOf(collapsedGm); collapseGroup(collapsedGm.gPos); if (posMetadata.groupInsertIndex > collapsedIndex) { posMetadata.groupInsertIndex--; } } GroupMetadata expandedGm = GroupMetadata.obtain( GroupMetadata.REFRESH, GroupMetadata.REFRESH, posMetadata.position.groupPos, mExpandableListAdapter.getGroupId(posMetadata.position.groupPos)); mExpGroupMetadataList.add(posMetadata.groupInsertIndex, expandedGm); refreshExpGroupMetadataList(false, false); notifyDataSetChanged(); mExpandableListAdapter.onGroupExpanded(expandedGm.gPos); return true; } int findGroupPosition(long groupIdToMatch, int seedGroupPosition) { int count = mExpandableListAdapter.getGroupCount(); if (count == 0) { return AdapterView.INVALID_POSITION; } if (groupIdToMatch == AdapterView.INVALID_ROW_ID) { return AdapterView.INVALID_POSITION; } seedGroupPosition = Math.max(0, seedGroupPosition); seedGroupPosition = Math.min(count - 1, seedGroupPosition); long endTime = SystemClock.uptimeMillis() + AdapterView.SYNC_MAX_DURATION_MILLIS; long rowId; int first = seedGroupPosition; int last = seedGroupPosition; boolean next = false; boolean hitFirst; boolean hitLast; ExpandableListAdapter adapter = getAdapter(); if (adapter == null) { return AdapterView.INVALID_POSITION; } while (SystemClock.uptimeMillis() <= endTime) { rowId = adapter.getGroupId(seedGroupPosition); if (rowId == groupIdToMatch) { return seedGroupPosition; } hitLast = last == count - 1; hitFirst = first == 0; if (hitLast && hitFirst) { break; } if (hitFirst || next && !hitLast) { last++; seedGroupPosition = last; next = false; } else if (hitLast || !next && !hitFirst) { first--; seedGroupPosition = first; next = true; } } return AdapterView.INVALID_POSITION; } ExpandableListAdapter getAdapter() { return mExpandableListAdapter; } @Override public int getCount() { return mExpandableListAdapter.getGroupCount() + mTotalExpChildrenCount; } ArrayList<GroupMetadata> getExpandedGroupMetadataList() { return mExpGroupMetadataList; } @Override public Filter getFilter() { ExpandableListAdapter adapter = getAdapter(); if (adapter instanceof Filterable) { return ((Filterable) adapter).getFilter(); } else { return null; } } PositionMetadata getFlattenedPos(final ExpandableListPosition pos) { final ArrayList<GroupMetadata> egml = mExpGroupMetadataList; final int numExpGroups = egml.size(); int leftExpGroupIndex = 0; int rightExpGroupIndex = numExpGroups - 1; int midExpGroupIndex = 0; GroupMetadata midExpGm; if (numExpGroups == 0) { return PositionMetadata.obtain(pos.groupPos, pos.type, pos.groupPos, pos.childPos, null, 0); } while (leftExpGroupIndex <= rightExpGroupIndex) { midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex) / 2 + leftExpGroupIndex; midExpGm = egml.get(midExpGroupIndex); if (pos.groupPos > midExpGm.gPos) { leftExpGroupIndex = midExpGroupIndex + 1; } else if (pos.groupPos < midExpGm.gPos) { rightExpGroupIndex = midExpGroupIndex - 1; } else if (pos.groupPos == midExpGm.gPos) { if (pos.type == ExpandableListPosition.GROUP) { return PositionMetadata.obtain(midExpGm.flPos, pos.type, pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex); } else if (pos.type == ExpandableListPosition.CHILD) { return PositionMetadata.obtain(midExpGm.flPos + pos.childPos + 1, pos.type, pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex); } else { return null; } } } if (pos.type != ExpandableListPosition.GROUP) { return null; } if (leftExpGroupIndex > midExpGroupIndex) { final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex - 1); final int flPos = leftExpGm.lastChildFlPos + pos.groupPos - leftExpGm.gPos; return PositionMetadata.obtain(flPos, pos.type, pos.groupPos, pos.childPos, null, leftExpGroupIndex); } else if (rightExpGroupIndex < midExpGroupIndex) { final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex); final int flPos = rightExpGm.flPos - (rightExpGm.gPos - pos.groupPos); return PositionMetadata.obtain(flPos, pos.type, pos.groupPos, pos.childPos, null, rightExpGroupIndex); } else { return null; } } @Override public Object getItem(int flatListPos) { final PositionMetadata posMetadata = getUnflattenedPos(flatListPos); Object retValue; if (posMetadata.position.type == ExpandableListPosition.GROUP) { retValue = mExpandableListAdapter .getGroup(posMetadata.position.groupPos); } else if (posMetadata.position.type == ExpandableListPosition.CHILD) { retValue = mExpandableListAdapter.getChild(posMetadata.position.groupPos, posMetadata.position.childPos); } else { throw new RuntimeException("Flat list position is of unknown type"); } posMetadata.recycle(); return retValue; } @Override public long getItemId(int flatListPos) { final PositionMetadata posMetadata = getUnflattenedPos(flatListPos); final long groupId = mExpandableListAdapter.getGroupId(posMetadata.position.groupPos); long retValue; if (posMetadata.position.type == ExpandableListPosition.GROUP) { retValue = mExpandableListAdapter.getCombinedGroupId(groupId); } else if (posMetadata.position.type == ExpandableListPosition.CHILD) { final long childId = mExpandableListAdapter.getChildId(posMetadata.position.groupPos, posMetadata.position.childPos); retValue = mExpandableListAdapter.getCombinedChildId(groupId, childId); } else { throw new RuntimeException("Flat list position is of unknown type"); } posMetadata.recycle(); return retValue; } @Override public int getItemViewType(int flatListPos) { final PositionMetadata metadata = getUnflattenedPos(flatListPos); final ExpandableListPosition pos = metadata.position; int retValue; if (mExpandableListAdapter instanceof HeterogeneousExpandableList) { HeterogeneousExpandableList adapter = (HeterogeneousExpandableList) mExpandableListAdapter; if (pos.type == ExpandableListPosition.GROUP) { retValue = adapter.getGroupType(pos.groupPos); } else { final int childType = adapter.getChildType(pos.groupPos, pos.childPos); retValue = adapter.getGroupTypeCount() + childType; } } else { if (pos.type == ExpandableListPosition.GROUP) { retValue = 0; } else { retValue = 1; } } metadata.recycle(); return retValue; } PositionMetadata getUnflattenedPos(final int flPos) { final ArrayList<GroupMetadata> egml = mExpGroupMetadataList; final int numExpGroups = egml.size(); int leftExpGroupIndex = 0; int rightExpGroupIndex = numExpGroups - 1; int midExpGroupIndex = 0; GroupMetadata midExpGm; if (numExpGroups == 0) { return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, flPos, -1, null, 0); } while (leftExpGroupIndex <= rightExpGroupIndex) { midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex) / 2 + leftExpGroupIndex; midExpGm = egml.get(midExpGroupIndex); if (flPos > midExpGm.lastChildFlPos) { leftExpGroupIndex = midExpGroupIndex + 1; } else if (flPos < midExpGm.flPos) { rightExpGroupIndex = midExpGroupIndex - 1; } else if (flPos == midExpGm.flPos) { return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, midExpGm.gPos, -1, midExpGm, midExpGroupIndex); } else if (flPos <= midExpGm.lastChildFlPos) { final int childPos = flPos - (midExpGm.flPos + 1); return PositionMetadata.obtain(flPos, ExpandableListPosition.CHILD, midExpGm.gPos, childPos, midExpGm, midExpGroupIndex); } } int insertPosition = 0; int groupPos = 0; if (leftExpGroupIndex > midExpGroupIndex) { final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex - 1); insertPosition = leftExpGroupIndex; groupPos = flPos - leftExpGm.lastChildFlPos + leftExpGm.gPos; } else if (rightExpGroupIndex < midExpGroupIndex) { final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex); insertPosition = rightExpGroupIndex; groupPos = rightExpGm.gPos - (rightExpGm.flPos - flPos); } else { throw new RuntimeException("Unknown state"); } return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, groupPos, -1, null, insertPosition); } @Override public View getView(int flatListPos, View convertView, ViewGroup parent) { final PositionMetadata posMetadata = getUnflattenedPos(flatListPos); View retValue; if (posMetadata.position.type == ExpandableListPosition.GROUP) { retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata.isExpanded(), convertView, parent); } else if (posMetadata.position.type == ExpandableListPosition.CHILD) { final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos; retValue = mExpandableListAdapter.getChildView(posMetadata.position.groupPos, posMetadata.position.childPos, isLastChild, convertView, parent); } else { throw new RuntimeException("Flat list position is of unknown type"); } posMetadata.recycle(); return retValue; } @Override public int getViewTypeCount() { if (mExpandableListAdapter instanceof HeterogeneousExpandableList) { HeterogeneousExpandableList adapter = (HeterogeneousExpandableList) mExpandableListAdapter; return adapter.getGroupTypeCount() + adapter.getChildTypeCount(); } else { return 2; } } @Override public boolean hasStableIds() { return mExpandableListAdapter.hasStableIds(); } @Override public boolean isEmpty() { ExpandableListAdapter adapter = getAdapter(); return adapter != null ? adapter.isEmpty() : true; } @Override public boolean isEnabled(int flatListPos) { final PositionMetadata metadata = getUnflattenedPos(flatListPos); final ExpandableListPosition pos = metadata.position; boolean retValue; if (pos.type == ExpandableListPosition.CHILD) { retValue = mExpandableListAdapter.isChildSelectable(pos.groupPos, pos.childPos); } else { retValue = true; } metadata.recycle(); return retValue; } public boolean isGroupExpanded(int groupPosition) { GroupMetadata groupMetadata; for (int i = mExpGroupMetadataList.size() - 1; i >= 0; i--) { groupMetadata = mExpGroupMetadataList.get(i); if (groupMetadata.gPos == groupPosition) { return true; } } return false; } private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh, boolean syncGroupPositions) { final ArrayList<GroupMetadata> egml = mExpGroupMetadataList; int egmlSize = egml.size(); int curFlPos = 0; mTotalExpChildrenCount = 0; if (syncGroupPositions) { boolean positionsChanged = false; for (int i = egmlSize - 1; i >= 0; i--) { GroupMetadata curGm = egml.get(i); int newGPos = findGroupPosition(curGm.gId, curGm.gPos); if (newGPos != curGm.gPos) { if (newGPos == AdapterView.INVALID_POSITION) { egml.remove(i); egmlSize--; } curGm.gPos = newGPos; if (!positionsChanged) { positionsChanged = true; } } } if (positionsChanged) { Collections.sort(egml); } } int gChildrenCount; int lastGPos = 0; for (int i = 0; i < egmlSize; i++) { GroupMetadata curGm = egml.get(i); if (curGm.lastChildFlPos == GroupMetadata.REFRESH || forceChildrenCountRefresh) { gChildrenCount = mExpandableListAdapter.getChildrenCount(curGm.gPos); } else { gChildrenCount = curGm.lastChildFlPos - curGm.flPos; } mTotalExpChildrenCount += gChildrenCount; curFlPos += curGm.gPos - lastGPos; lastGPos = curGm.gPos; curGm.flPos = curFlPos; curFlPos += gChildrenCount; curGm.lastChildFlPos = curFlPos; } } public void setExpandableListAdapter(ExpandableListAdapter expandableListAdapter) { if (mExpandableListAdapter != null) { mExpandableListAdapter.unregisterDataSetObserver(mDataSetObserver); } mExpandableListAdapter = expandableListAdapter; expandableListAdapter.registerDataSetObserver(mDataSetObserver); } void setExpandedGroupMetadataList(ArrayList<GroupMetadata> expandedGroupMetadataList) { if (expandedGroupMetadataList == null || mExpandableListAdapter == null) { return; } int numGroups = mExpandableListAdapter.getGroupCount(); for (int i = expandedGroupMetadataList.size() - 1; i >= 0; i--) { if (expandedGroupMetadataList.get(i).gPos >= numGroups) { return; } } mExpGroupMetadataList = expandedGroupMetadataList; refreshExpGroupMetadataList(true, false); } public void setMaxExpGroupCount(int maxExpGroupCount) { mMaxExpGroupCount = maxExpGroupCount; } }