文章目录
- 资料
- 参数介绍
- 闭合
- ExpandableListView#collapseGroup
- ExpandableListConnector#collapseGroup
- ExpandableListConnector#collapseGroup
- ExpandableListConnector#refreshExpGroupMetadataList
- 展开
- ExpandableListView#expandGroup
- ExpandableConnecter#getFlattenedPos
- ExpandableListConnector#expandGroup
- 显示
- ExpandableListConnector#getView
- ExpandableListConnector#getUnflattenedPos
- 观察者
- ExpandableListConnector#constructor
- ExpandableListConnector#MyDataSetObserver#onChange
- AdapterView#AdapterDataSetObserver
资料
Android开发——ListView的复用机制源码解析
参数介绍
mExpGroupMetadataList : 所有展开的Group
闭合
ExpandableListView#collapseGroup
public boolean collapseGroup(int groupPos) {
boolean retValue = mConnector.collapseGroup(groupPos);
if (mOnGroupCollapseListener != null) {
mOnGroupCollapseListener.onGroupCollapse(groupPos);
}
return retValue;
}
ExpandableListConnector#collapseGroup
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;
}
ExpandableListConnector#collapseGroup
boolean collapseGroup(PositionMetadata posMetadata) {
/*
* Collapsing requires removal from mExpGroupMetadataList
*/
/*
* If it is null, it must be already collapsed. This group metadata
* object should have been set from the search that returned the
* position metadata object.
*/
if (posMetadata.groupMetadata == null) return false;
// Remove the group from the list of expanded groups
// 应该是保存着所有展开的的Group
mExpGroupMetadataList.remove(posMetadata.groupMetadata);
// Refresh the metadata
refreshExpGroupMetadataList(false, false);
// Notify of change
notifyDataSetChanged();
// Give the callback
mExpandableListAdapter.onGroupCollapsed(posMetadata.groupMetadata.gPos);
return true;
}
ExpandableListConnector#refreshExpGroupMetadataList
private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh,
boolean syncGroupPositions) {
final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
int egmlSize = egml.size();
int curFlPos = 0;
/* Update child count as we go through */
mTotalExpChildrenCount = 0;
if (syncGroupPositions) {
// We need to check whether any groups have moved positions
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) {
// Doh, just remove it from the list of expanded groups
egml.remove(i);
egmlSize--;
}
curGm.gPos = newGPos;
if (!positionsChanged) positionsChanged = true;
}
}
if (positionsChanged) {
// At least one group changed positions, so re-sort
Collections.sort(egml);
}
}
int gChildrenCount;
int lastGPos = 0;
for (int i = 0; i < egmlSize; i++) {
/* Store in local variable since we'll access freq */
GroupMetadata curGm = egml.get(i);
/*
* Get the number of children, try to refrain from calling
* another class's method unless we have to (so do a subtraction)
*/
if ((curGm.lastChildFlPos == GroupMetadata.REFRESH) || forceChildrenCountRefresh) {
gChildrenCount = mExpandableListAdapter.getChildrenCount(curGm.gPos);
} else {
/* Num children for this group is its last child's fl pos minus
* the group's fl pos
*/
gChildrenCount = curGm.lastChildFlPos - curGm.flPos;
}
/* Update */
mTotalExpChildrenCount += gChildrenCount;
/*
* This skips the collapsed groups and increments the flat list
* position (for subsequent exp groups) by accounting for the collapsed
* groups
*/
curFlPos += (curGm.gPos - lastGPos);
lastGPos = curGm.gPos;
/* Update the flat list positions, and the current flat list pos */
curGm.flPos = curFlPos;
curFlPos += gChildrenCount;
curGm.lastChildFlPos = curFlPos;
}
}
展开
ExpandableListView#expandGroup
- 从ExpandableListPosition.obtain获取
- 调用mConnecter
public boolean expandGroup(int groupPos, boolean animate) {
ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
ExpandableListPosition.GROUP, groupPos, -1, -1);
PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
elGroupPos.recycle();
boolean retValue = mConnector.expandGroup(pm);
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(groupPos);
}
if (animate) {
final int groupFlatPos = pm.position.flatListPos;
final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
shiftedGroupPosition);
}
pm.recycle();
return retValue;
}
ExpandableConnecter#getFlattenedPos
PositionMetadata getFlattenedPos(final ExpandableListPosition pos) {
final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
final int numExpGroups = egml.size();
/* Binary search variables */
int leftExpGroupIndex = 0;
int rightExpGroupIndex = numExpGroups - 1;
int midExpGroupIndex = 0;
GroupMetadata midExpGm;
if (numExpGroups == 0) {
/*
* There aren't any expanded groups, so flPos must be a group and
* its flPos will be the same as its group pos. The
* insert position is 0 (since the list is empty).
*/
return PositionMetadata.obtain(pos.groupPos, pos.type,
pos.groupPos, pos.childPos, null, 0);
}
/*
* Binary search over the expanded groups to find either the exact
* expanded group (if we're looking for a group) or the group that
* contains the child we're looking for.
*/
while (leftExpGroupIndex <= rightExpGroupIndex) {
midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex)/2 + leftExpGroupIndex;
midExpGm = egml.get(midExpGroupIndex);
if (pos.groupPos > midExpGm.gPos) {
/*
* It's after the current middle group, so search right
*/
leftExpGroupIndex = midExpGroupIndex + 1;
} else if (pos.groupPos < midExpGm.gPos) {
/*
* It's before the current middle group, so search left
*/
rightExpGroupIndex = midExpGroupIndex - 1;
} else if (pos.groupPos == midExpGm.gPos) {
/*
* It's this middle group, exact hit
*/
if (pos.type == ExpandableListPosition.GROUP) {
/* If it's a group, give them this matched group's flPos */
return PositionMetadata.obtain(midExpGm.flPos, pos.type,
pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex);
} else if (pos.type == ExpandableListPosition.CHILD) {
/* If it's a child, calculate the flat list pos */
return PositionMetadata.obtain(midExpGm.flPos + pos.childPos
+ 1, pos.type, pos.groupPos, pos.childPos,
midExpGm, midExpGroupIndex);
} else {
return null;
}
}
}
/*
* If we've reached here, it means there was no match in the expanded
* groups, so it must be a collapsed group that they're search for
*/
if (pos.type != ExpandableListPosition.GROUP) {
/* If it isn't a group, return null */
return null;
}
/*
* To figure out exact insertion and prior group positions, we need to
* determine how we broke out of the binary search. We backtrack to see
* this.
*/
if (leftExpGroupIndex > midExpGroupIndex) {
/*
* This would occur in the first conditional, so the flat list
* insertion position is after the left group.
*
* The leftGroupPos is one more than it should be (from the binary
* search loop) so we subtract 1 to get the actual left group. Since
* the insertion point is AFTER the left group, we keep this +1
* value as the insertion point
*/
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) {
/*
* This would occur in the second conditional, so the flat list
* insertion position is before the right group. Also, the
* rightGroupPos is one less than it should be (from binary search
* loop), so we increment to it.
*/
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;
}
}
ExpandableListConnector#expandGroup
boolean expandGroup(PositionMetadata posMetadata) {
/*
* Expanding requires insertion into the mExpGroupMetadataList
*/
if (posMetadata.position.groupPos < 0) {
// TODO clean exit
throw new RuntimeException("Need group");
}
if (mMaxExpGroupCount == 0) return false;
// Check to see if it's already expanded
if (posMetadata.groupMetadata != null) return false;
/* Restrict number of expanded groups to mMaxExpGroupCount */
if (mExpGroupMetadataList.size() >= mMaxExpGroupCount) {
/* Collapse a group */
// TODO: Collapse something not on the screen instead of the first one?
// TODO: Could write overloaded function to take GroupMetadata to collapse
GroupMetadata collapsedGm = mExpGroupMetadataList.get(0);
int collapsedIndex = mExpGroupMetadataList.indexOf(collapsedGm);
collapseGroup(collapsedGm.gPos);
/* Decrement index if it is after the group we removed */
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);
// Refresh the metadata
refreshExpGroupMetadataList(false, false);
// Notify of change
notifyDataSetChanged();
// Give the callback
mExpandableListAdapter.onGroupExpanded(expandedGm.gPos);
return true;
}
显示
ExpandableListConnector#getView
posMetadata.position.type 保存的是child还是group
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 {
// TODO: clean exit
throw new RuntimeException("Flat list position is of unknown type");
}
posMetadata.recycle();
return retValue;
}
ExpandableListConnector#getUnflattenedPos
- 如果没有展开的,则直接返回位置
- 用到了二分查找
PositionMetadata getUnflattenedPos(final int flPos) {
/* Keep locally since frequent use */
final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
final int numExpGroups = egml.size();
/* Binary search variables */
int leftExpGroupIndex = 0;
int rightExpGroupIndex = numExpGroups - 1;
int midExpGroupIndex = 0;
GroupMetadata midExpGm;
if (numExpGroups == 0) {
/*
* There aren't any expanded groups (hence no visible children
* either), so flPos must be a group and its group pos will be the
* same as its flPos
*/
//如果没有展开的,则直接返回位置
return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, flPos,
-1, null, 0);
}
/*
* Binary search over the expanded groups to find either the exact
* expanded group (if we're looking for a group) or the group that
* contains the child we're looking for. If we are looking for a
* collapsed group, we will not have a direct match here, but we will
* find the expanded group just before the group we're searching for (so
* then we can calculate the group position of the group we're searching
* for). If there isn't an expanded group prior to the group being
* searched for, then the group being searched for's group position is
* the same as the flat list position (since there are no children before
* it, and all groups before it are collapsed).
*/
// 二分查找
while (leftExpGroupIndex <= rightExpGroupIndex) {
midExpGroupIndex =
(rightExpGroupIndex - leftExpGroupIndex) / 2
+ leftExpGroupIndex;
midExpGm = egml.get(midExpGroupIndex);
if (flPos > midExpGm.lastChildFlPos) {
/*
* The flat list position is after the current middle group's
* last child's flat list position, so search right
*/
leftExpGroupIndex = midExpGroupIndex + 1;
} else if (flPos < midExpGm.flPos) {
/*
* The flat list position is before the current middle group's
* flat list position, so search left
*/
rightExpGroupIndex = midExpGroupIndex - 1;
} else if (flPos == midExpGm.flPos) {
/*
* The flat list position is this middle group's flat list
* position, so we've found an exact hit
*/
return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP,
midExpGm.gPos, -1, midExpGm, midExpGroupIndex);
} else if (flPos <= midExpGm.lastChildFlPos
/* && flPos > midGm.flPos as deduced from previous
* conditions */) {
/* The flat list position is a child of the middle group */
/*
* Subtract the first child's flat list position from the
* specified flat list pos to get the child's position within
* the group
*/
final int childPos = flPos - (midExpGm.flPos + 1);
return PositionMetadata.obtain(flPos, ExpandableListPosition.CHILD,
midExpGm.gPos, childPos, midExpGm, midExpGroupIndex);
}
}
/*
* If we've reached here, it means the flat list position must be a
* group that is not expanded, since otherwise we would have hit it
* in the above search.
*/
/**
* If we are to expand this group later, where would it go in the
* mExpGroupMetadataList ?
*/
int insertPosition = 0;
/** What is its group position in the list of all groups? */
int groupPos = 0;
/*
* To figure out exact insertion and prior group positions, we need to
* determine how we broke out of the binary search. We backtrack
* to see this.
*/
if (leftExpGroupIndex > midExpGroupIndex) {
/*
* This would occur in the first conditional, so the flat list
* insertion position is after the left group. Also, the
* leftGroupPos is one more than it should be (since that broke out
* of our binary search), so we decrement it.
*/
final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex-1);
insertPosition = leftExpGroupIndex;
/*
* Sums the number of groups between the prior exp group and this
* one, and then adds it to the prior group's group pos
*/
groupPos =
(flPos - leftExpGm.lastChildFlPos) + leftExpGm.gPos;
} else if (rightExpGroupIndex < midExpGroupIndex) {
/*
* This would occur in the second conditional, so the flat list
* insertion position is before the right group. Also, the
* rightGroupPos is one less than it should be, so increment it.
*/
final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex);
insertPosition = rightExpGroupIndex;
/*
* Subtracts this group's flat list pos from the group after's flat
* list position to find out how many groups are in between the two
* groups. Then, subtracts that number from the group after's group
* pos to get this group's pos.
*/
groupPos = rightExpGm.gPos - (rightExpGm.flPos - flPos);
} else {
// TODO: clean exit
throw new RuntimeException("Unknown state");
}
return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, groupPos, -1,
null, insertPosition);
}
观察者
public abstract class BaseExpandableListAdapter implements ExpandableListAdapter,
HeterogeneousExpandableList {
private final DataSetObservable mDataSetObservable = ne`在这里插入代码片`w DataSetObservable();
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* @see DataSetObservable#notifyInvalidated()
*/
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
/**
* @see DataSetObservable#notifyChanged()
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
ExpandableListConnector#constructor
/**
* Constructs the connector
*/
public ExpandableListConnector(ExpandableListAdapter expandableListAdapter) {
mExpGroupMetadataList = new ArrayList<GroupMetadata>();
setExpandableListAdapter(expandableListAdapter);
}
public void setExpandableListAdapter(ExpandableListAdapter expandableListAdapter) {
if (mExpandableListAdapter != null) {
mExpandableListAdapter.unregisterDataSetObserver(mDataSetObserver);
}
mExpandableListAdapter = expandableListAdapter;
expandableListAdapter.registerDataSetObserver(mDataSetObserver);
}
ExpandableListConnector#MyDataSetObserver#onChange
protected class MyDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
refreshExpGroupMetadataList(true, true);
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
refreshExpGroupMetadataList(true, true); // 重点是重新布局了
notifyDataSetInvalidated(); // 他下面可能还有,也可能没有
}
}
AdapterView#AdapterDataSetObserver
在这里调用了requestLayout
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
@Override
public void onInvalidated() {
mDataChanged = true;
if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
}
// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
checkFocus();
requestLayout();
}
public void clearSavedState() {
mInstanceState = null;
}
}