承接上篇listview 源码解析
以下用第一次向下滑动举例。
由于ListView和GridView都继承自AbsListView当中的了。那么监听触控事件是在onTouchEvent()方法当中进行的,我们就来看一下AbsListView中的这个方法:
case MotionEvent.ACTION_MOVE: {
onTouchMove(ev, vtev);
break;
}
跟进onTouchMove
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
...省略非重点代码
if (mDataChanged) {
// Re-sync everything if data has been changed
// since the scroll operation can query the adapter.
layoutChildren();
}
final int y = (int) ev.getY(pointerIndex);
switch (mTouchMode) {
...省略非重点代码
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}
在此我们发现 if (mDataChanged)就layoutChildren();可以说数据发生改变时就layoutChildren。跟进scrollIfNeeded方法;
private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
...省略非重点代码。。。。。。省略的有点太多了哈哈。
else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
if (y != mLastY) {
final int oldScroll = mScrollY;
final int newScroll = oldScroll - incrementalDeltaY;
int newDirection = y > mLastY ? 1 : -1;
if (mDirection == 0) {
mDirection = newDirection;
}
int overScrollDistance = -incrementalDeltaY;
if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
overScrollDistance = -oldScroll;
incrementalDeltaY += overScrollDistance;
} else {
incrementalDeltaY = 0;
}
if (overScrollDistance != 0) {
overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
0, mOverscrollDistance, true);
final int overscrollMode = getOverScrollMode();
if (overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
!contentFits())) {
if (rawDeltaY > 0) {
mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
(float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
invalidateTopGlow();
} else if (rawDeltaY < 0) {
mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
invalidateBottomGlow();
}
}
}
if (incrementalDeltaY != 0) {
// Coming back to 'real' list scrolling
if (mScrollY != 0) {
mScrollY = 0;
invalidateParentIfNeeded();
}
trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
mTouchMode = TOUCH_MODE_SCROLL;
// We did not scroll the full amount. Treat this essentially like the
// start of a new touch scroll
final int motionPosition = findClosestMotionRow(y);
mMotionCorrection = 0;
View motionView = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
mMotionY = y + scrollOffsetCorrection;
mMotionPosition = motionPosition;
}
mLastY = y + lastYCorrection + scrollOffsetCorrection;
mDirection = newDirection;
}
}
}
mTouchMode在上一个方法中就已经是TOUCH_MODE_OVERSCROLL所以查看其代码块。
其中一旦上下卫衣 if (incrementalDeltaY != 0) 成立
跟进trackMotionScroll方法。此方法即为判断轨迹运动的具体代码。
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
...省略非重点代码 就是给一个向上滑还是向下滑的判断
if (down) {
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
}
} else {
int bottom = getHeight() - incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
bottom -= listPadding.bottom;
}
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom) {
break;
} else {
start = i;
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
}
}
mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
mBlockLayoutRequests = true;
if (count > 0) {
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
// invalidate before moving the children to avoid unnecessary invalidate
// calls to bubble up from the children all the way to the top
if (!awakenScrollBars()) {
invalidate();
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
mRecycler.fullyDetachScrapViews();
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
return false;
}
重点来了,在判断有一个item移除后if(down)的判断中mRecycler.addScrapView(child, position);
此时的scrapView即为被回收的item也就是移除屏幕的item。但偏移量不够的时候不addScrapView
之后if (count > 0) {
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
再之后 offsetChildrenTopAndBottom(incrementalDeltaY);
该方法存在于ViewGroup,跟进。
public void offsetChildrenTopAndBottom(int offset) {
final int count = mChildrenCount;
final View[] children = mChildren;
boolean invalidate = false;
for (int i = 0; i < count; i++) {
final View v = children[i];
v.mTop += offset;
v.mBottom += offset;
if (v.mRenderNode != null) {
invalidate = true;
v.mRenderNode.offsetTopAndBottom(offset);
}
}
if (invalidate) {
invalidateViewProperty(false, false);
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
没别的意思,整体偏移所有的item。
之后回到trackMotionScroll我们发现if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}的判断满足之时就是下一个item添加之日。跟进。
@Override
void fillGap(boolean down) {
final int count = getChildCount();
if (down) {
int paddingTop = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
paddingTop = getListPaddingTop();
}
final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
paddingTop;
fillDown(mFirstPosition + count, startOffset);
correctTooHigh(getChildCount());
} else {
int paddingBottom = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
paddingBottom = getListPaddingBottom();
}
final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
getHeight() - paddingBottom;
fillUp(mFirstPosition - 1, startOffset);
correctTooLow(getChildCount());
}
}
熟悉的老朋友fillDown以及fillUp又回来了代码不再重复。重点再次回到makeAndAddView中
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
此时的ActiveView的返回值是null,因为activeview的值仅仅是第一次layout的显示出来的那几个值。判断中child= null故而再次进入obtainView方法。跟进。
View obtainView(int position, boolean[] isScrap) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
isScrap[0] = false;
// Check whether we have a transient state view. Attempt to re-bind the
// data and discard the view if we fail.
final View transientView = mRecycler.getTransientStateView(position);
if (transientView != null) {
final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
// If the view type hasn't changed, attempt to re-bind the data.
if (params.viewType == mAdapter.getItemViewType(position)) {
final View updatedView = mAdapter.getView(position, transientView, this);
// If we failed to re-bind the data, scrap the obtained view.
if (updatedView != transientView) {
setItemViewLayoutParams(updatedView, position);
mRecycler.addScrapView(updatedView, position);
}
}
isScrap[0] = true;
// Finish the temporary detach started in addScrapView().
transientView.dispatchFinishTemporaryDetach();
return transientView;
}
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
// Failed to re-bind the data, return scrap to the heap.
mRecycler.addScrapView(scrapView, position);
} else {
if (child.isTemporarilyDetached()) {
isScrap[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
} else {
// we set isScrap to "true" only if the view is temporarily detached.
// if the view is fully detached, it is as good as a view created by the
// adapter
isScrap[0] = false;
}
}
}
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
setItemViewLayoutParams(child, position);
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new ListItemAccessibilityDelegate();
}
if (child.getAccessibilityDelegate() == null) {
child.setAccessibilityDelegate(mAccessibilityDelegate);
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return child;
}
神奇的东西来了scrapView!=null所以 此时view的来源来自于mRecycler.addScrapView(scrapView, position);从ScrapVie中复用一个view。
刚才在trackMotionScroll()方法中我们就已经看到了,一旦有任何子View被移出了屏幕,就会将它加入到废弃缓存中,而从obtainView()方法中的逻辑来看,一旦有新的数据需要显示到屏幕上,就会尝试从废弃缓存中获取View。所以它们之间就形成了一个生产者和消费者的模式,那么ListView神奇的地方也就在这里体现出来了,不管你有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据重新利用起来,因而不管我们加载多少数据都不会出现OOM的情况,甚至内存都不会有所增加。
我们可以暂时回顾小结一下,实际上RecycleBin的主要结构就是三个,一个是activeView堆,结构是一个View数组,另一个是scrapView堆,结构是一个
ArrayList数组。还有一个是transientViews,结构是SparseArray,主要通过Id或者position存取。
transientViews的获取会首先通过id获取之后再通过position,这个是撸代码的常识了吧,不解释。
几个结构可以理解为层级不同,activeView比scrapView高一点,当触发了某种瞬态或其他条件机制后,child的view就会从activieView中移到transientViews或者scrapView中进行缓存。
当ListView需要obtainView时,会先从有瞬时态的sparseArray中获取view,当失败时就会去scrapViews中获取view。
当然,这些过程又是和一个boolean变量mDataChanged进行配合的,具体的过程在上面的源码分析中已经解释过了,诸位可以回过去看看。
基本思路是在给子view布局时,如果数据没有发生改变,就使用当前已经存在ActiveViews的view。
至此结束
添加几个被老大提问的疑难杂症看法
1.如果mDataChanged=true怎么办。
mDataChanged=true 肯定是调用了adapter的notifyDataSetChanged,跟进。
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
这里我们可以看到这里使用了Observable的观察者模式,继续跟入。
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
我们发现是其每一个注册过的观察者Observers调用相对应的onChanged方法。接下来让我们在找Observers在哪里。此时我们想起了listview的setAdapter方法
跟入去瞧瞧。
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
lucky 发现一个mDataSetObserver并且mAdapter.registerDataSetObserver(mDataSetObserver);这是将mDataSetObserver的这个观察者注册到了mAdapter中,而mAdapter来自于ListAdapter,而我们常见的BaseAdapter恰恰继承自ListAdapter。观察者模式至此串联成功。但我们继续查找mDataSetObserver的来源AdapterDataSetObserver,在AbsListView中我们发现了
class AdapterDataSetObserver extends AdapterView.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}
然并卵。。查看其AdapterDataSetObserver的父类
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;
}
}
可以看到,Observable是adapter,其数据改变调用notifyDataSetChanged最终通知到了Observers的onChanged方法onChanged中调用了requestLayout
重新布局,自然也调用了ListView的layoutChildren
现在回到listvuew的layoutChildren中
if (dataChanged) {
handleDataChanged();}
调用AbsListView的handleDataChanged
@Override
protected void handleDataChanged() {
int count = mItemCount;
int lastHandledItemCount = mLastHandledItemCount;
mLastHandledItemCount = mItemCount;
if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
confirmCheckedPositionsById();
}
// TODO: In the future we can recycle these views based on stable ID instead.
mRecycler.clearTransientStateViews();
if (count > 0) {
int newPos;
int selectablePos;
// Find the row we are supposed to sync to
if (mNeedSync) {
// Update this first, since setNextSelectedPositionInt inspects it
mNeedSync = false;
mPendingSync = null;
if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
mLayoutMode = LAYOUT_FORCE_BOTTOM;
return;
} else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
if (mForceTranscriptScroll) {
mForceTranscriptScroll = false;
mLayoutMode = LAYOUT_FORCE_BOTTOM;
return;
}
final int childCount = getChildCount();
final int listBottom = getHeight() - getPaddingBottom();
final View lastChild = getChildAt(childCount - 1);
final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
if (mFirstPosition + childCount >= lastHandledItemCount &&
lastBottom <= listBottom) {
mLayoutMode = LAYOUT_FORCE_BOTTOM;
return;
}
// Something new came in and we didn't scroll; give the user a clue that
// there's something new.
awakenScrollBars();
}
switch (mSyncMode) {
case SYNC_SELECTED_POSITION:
if (isInTouchMode()) {
// We saved our state when not in touch mode. (We know this because
// mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
// restore in touch mode. Just leave mSyncPosition as it is (possibly
// adjusting if the available range changed) and return.
mLayoutMode = LAYOUT_SYNC;
mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
return;
} else {
// See if we can find a position in the new data with the same
// id as the old selection. This will change mSyncPosition.
newPos = findSyncPosition();
if (newPos >= 0) {
// Found it. Now verify that new selection is still selectable
selectablePos = lookForSelectablePosition(newPos, true);
if (selectablePos == newPos) {
// Same row id is selected
mSyncPosition = newPos;
if (mSyncHeight == getHeight()) {
// If we are at the same height as when we saved state, try
// to restore the scroll position too.
mLayoutMode = LAYOUT_SYNC;
} else {
// We are not the same height as when the selection was saved, so
// don't try to restore the exact position
mLayoutMode = LAYOUT_SET_SELECTION;
}
// Restore selection
setNextSelectedPositionInt(newPos);
return;
}
}
}
break;
case SYNC_FIRST_POSITION:
// Leave mSyncPosition as it is -- just pin to available range
mLayoutMode = LAYOUT_SYNC;
mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
return;
}
}
...省略非重要代码
}
此时mLayoutMode = LAYOUT_SYNC;再次回到listview的layoutChildren中
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
即便是我的child被detach掉我也可以通过fillSpecific的方式继续在上一次显示的view中继续的显示上一次的布局,此处就是数据更改后的listView刷新重新布局。