本篇文章是接着上篇文章繼續分析
上篇文章位址
接下來主要讨論ListView 由使用者改變行為,分一下幾個方面
(5) Listview 的滑動過程
(6)setAdapter 原理
(7)notifyDataSetChanged 原理
(5) Listview 的滑動過程
Listview 和 GirdView 滑動事件的處理是在父類AbsListView完成對view銷毀緩存和複用的, 滑動是Listview 實作資料展示一個重要的功能。我們現來看onTouchEvent方法,同樣也是對重要的方法進行解析
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled()) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return isClickable() || isLongClickable();
}
if (mPositionScroller != null) {
mPositionScroller.stop();
}
if (mIsDetaching || !isAttachedToWindow()) {
// Something isn't right.
// Since we rely on being attached to get data set change notifications,
// don't risk doing anything where we might try to resync and find things
// in a bogus state.
return false;
}
startNestedScroll(SCROLL_AXIS_VERTICAL);
if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
return true;
}
initVelocityTrackerIfNotExists();
final MotionEvent vtev = MotionEvent.obtain(ev);
final int actionMasked = ev.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
mNestedYOffset = ;
}
vtev.offsetLocation(, mNestedYOffset);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
onTouchDown(ev);
break;
}
case MotionEvent.ACTION_MOVE: {
onTouchMove(ev, vtev);
break;
}
case MotionEvent.ACTION_UP: {
onTouchUp(ev);
break;
}
case MotionEvent.ACTION_CANCEL: {
onTouchCancel();
break;
}
case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
final int x = mMotionX;
final int y = mMotionY;
final int motionPosition = pointToPosition(x, y);
if (motionPosition >= ) {
// Remember where the motion event started
final View child = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = child.getTop();
mMotionPosition = motionPosition;
}
mLastY = y;
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
// New pointers take over dragging duties
final int index = ev.getActionIndex();
final int id = ev.getPointerId(index);
final int x = (int) ev.getX(index);
final int y = (int) ev.getY(index);
mMotionCorrection = ;
mActivePointerId = id;
mMotionX = x;
mMotionY = y;
final int motionPosition = pointToPosition(x, y);
if (motionPosition >= ) {
// Remember where the motion event started
final View child = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = child.getTop();
mMotionPosition = motionPosition;
}
mLastY = y;
break;
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
當ListView在滾動時,直接進入onTouchMove 方法
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
if (mHasPerformedLongPress) {
// Consume all move events following a successful long press.
return;
}
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -) {
pointerIndex = ;
mActivePointerId = ev.getPointerId(pointerIndex);
}
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_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
// Check if we have moved far enough that it looks more like a
// scroll than a tap. If so, we'll enter scrolling mode.
if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
break;
}
// Otherwise, check containment within list bounds. If we're
// outside bounds, cancel any active presses.
final View motionView = getChildAt(mMotionPosition - mFirstPosition);
final float x = ev.getX(pointerIndex);
if (!pointInView(x, y, mTouchSlop)) {
setPressed(false);
if (motionView != null) {
motionView.setPressed(false);
}
removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
mPendingCheckForTap : mPendingCheckForLongPress);
mTouchMode = TOUCH_MODE_DONE_WAITING;
updateSelectorState();
} else if (motionView != null) {
// Still within bounds, update the hotspot.
final float[] point = mTmpPoint;
point[] = x;
point[] = y;
transformPointToViewLocal(point, motionView);
motionView.drawableHotspotChanged(point[], point[]);
}
break;
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}
當手指在螢幕上滑動時,TouchMode是等于TOUCH_MODE_SCROLL這個值的,至于為什麼是這個值,大家可以自己查源碼,這裡主要解析滾動的源碼解析, 這裡根據touchMode會直接進入scrollIfNeeded方法
private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
int rawDeltaY = y - mMotionY;
int scrollOffsetCorrection = ;
int scrollConsumedCorrection = ;
if (mLastY == Integer.MIN_VALUE) {
rawDeltaY -= mMotionCorrection;
}
if (dispatchNestedPreScroll(, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
mScrollConsumed, mScrollOffset)) {
rawDeltaY += mScrollConsumed[];
scrollOffsetCorrection = -mScrollOffset[];
scrollConsumedCorrection = mScrollConsumed[];
if (vtev != null) {
vtev.offsetLocation(, mScrollOffset[]);
mNestedYOffset += mScrollOffset[];
}
}
final int deltaY = rawDeltaY;
int incrementalDeltaY =
mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
int lastYCorrection = ;
if (mTouchMode == TOUCH_MODE_SCROLL) {
if (PROFILE_SCROLLING) {
if (!mScrollProfilingStarted) {
Debug.startMethodTracing("AbsListViewScroll");
mScrollProfilingStarted = true;
}
}
if (mScrollStrictSpan == null) {
// If it's non-null, we're already in a scroll.
mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
}
if (y != mLastY) {
// We may be here after stopping a fling and continuing to scroll.
// If so, we haven't disallowed intercepting touch events yet.
// Make sure that we do so in case we're in a parent that can intercept.
if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == &&
Math.abs(rawDeltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
final int motionIndex;
if (mMotionPosition >= ) {
motionIndex = mMotionPosition - mFirstPosition;
} else {
// If we don't have a motion position that we can reliably track,
// pick something in the middle to make a best guess at things below.
motionIndex = getChildCount() / ;
}
int motionViewPrevTop = ;
View motionView = this.getChildAt(motionIndex);
if (motionView != null) {
motionViewPrevTop = motionView.getTop();
}
// No need to do all this work if we're not going to move anyway
boolean atEdge = false;
if (incrementalDeltaY != ) {
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}
// Check to see if we have bumped into the scroll limit
motionView = this.getChildAt(motionIndex);
if (motionView != null) {
// Check if the top of the motion view is where it is
// supposed to be
final int motionViewRealTop = motionView.getTop();
if (atEdge) {
// Apply overscroll
int overscroll = -incrementalDeltaY -
(motionViewRealTop - motionViewPrevTop);
if (dispatchNestedScroll(, overscroll - incrementalDeltaY, , overscroll,
mScrollOffset)) {
lastYCorrection -= mScrollOffset[];
if (vtev != null) {
vtev.offsetLocation(, mScrollOffset[]);
mNestedYOffset += mScrollOffset[];
}
} else {
final boolean atOverscrollEdge = overScrollBy(, overscroll,
, mScrollY, , , , mOverscrollDistance, true);
if (atOverscrollEdge && mVelocityTracker != null) {
// Don't allow overfling if we're at the edge
mVelocityTracker.clear();
}
final int overscrollMode = getOverScrollMode();
if (overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
!contentFits())) {
if (!atOverscrollEdge) {
mDirection = ; // Reset when entering overscroll.
mTouchMode = TOUCH_MODE_OVERSCROLL;
}
if (incrementalDeltaY > ) {
mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
(float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
invalidateTopGlow();
} else if (incrementalDeltaY < ) {
mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
invalidateBottomGlow();
}
}
}
}
mMotionY = y + lastYCorrection + scrollOffsetCorrection;
}
mLastY = y + lastYCorrection + scrollOffsetCorrection;
}
} else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
if (y != mLastY) {
final int oldScroll = mScrollY;
final int newScroll = oldScroll - incrementalDeltaY;
int newDirection = y > mLastY ? : -;
if (mDirection == ) {
mDirection = newDirection;
}
int overScrollDistance = -incrementalDeltaY;
if ((newScroll < && oldScroll >= ) || (newScroll > && oldScroll <= )) {
overScrollDistance = -oldScroll;
incrementalDeltaY += overScrollDistance;
} else {
incrementalDeltaY = ;
}
if (overScrollDistance != ) {
overScrollBy(, overScrollDistance, , mScrollY, , ,
, mOverscrollDistance, true);
final int overscrollMode = getOverScrollMode();
if (overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
!contentFits())) {
if (rawDeltaY > ) {
mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
(float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
invalidateTopGlow();
} else if (rawDeltaY < ) {
mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
invalidateBottomGlow();
}
}
}
if (incrementalDeltaY != ) {
// Coming back to 'real' list scrolling
if (mScrollY != ) {
mScrollY = ;
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 = ;
View motionView = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = motionView != null ? motionView.getTop() : ;
mMotionY = y + scrollOffsetCorrection;
mMotionPosition = motionPosition;
}
mLastY = y + lastYCorrection + scrollOffsetCorrection;
mDirection = newDirection;
}
}
}
這個方法很長,需要處理螢幕上大大小小的事件,這裡我們需要研究的是trackMotionScroll方法,我們手指在螢幕上又一點的滑動都會觸發這個方法。
可以從代碼的19行看出,incrementalDeltaY 表示觸發事件在Y軸上的偏移量,我們會根據他的正負表示向上還是向下滑動,deltaY表示從手指按下到目前手指位置的距離。
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == ) {
return true;
}
final int firstTop = getChildAt().getTop();
final int lastBottom = getChildAt(childCount - ).getBottom();
final Rect listPadding = mListPadding;
// "effective padding" In this case is the amount of padding that affects
// how much space should not be filled by items. If we don't clip to padding
// there is no effective padding.
int effectivePaddingTop = ;
int effectivePaddingBottom = ;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
effectivePaddingTop = listPadding.top;
effectivePaddingBottom = listPadding.bottom;
}
// FIXME account for grid vertical spacing too?
final int spaceAbove = effectivePaddingTop - firstTop;
final int end = getHeight() - effectivePaddingBottom;
final int spaceBelow = lastBottom - end;
final int height = getHeight() - mPaddingBottom - mPaddingTop;
if (deltaY < ) {
deltaY = Math.max(-(height - ), deltaY);
} else {
deltaY = Math.min(height - , deltaY);
}
if (incrementalDeltaY < ) {
incrementalDeltaY = Math.max(-(height - ), incrementalDeltaY);
} else {
incrementalDeltaY = Math.min(height - , incrementalDeltaY);
}
final int firstPosition = mFirstPosition;
// Update our guesses for where the first and last views are
if (firstPosition == ) {
mFirstPositionDistanceGuess = firstTop - listPadding.top;
} else {
mFirstPositionDistanceGuess += incrementalDeltaY;
}
if (firstPosition + childCount == mItemCount) {
mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
} else {
mLastPositionDistanceGuess += incrementalDeltaY;
}
final boolean cannotScrollDown = (firstPosition == &&
firstTop >= listPadding.top && incrementalDeltaY >= );
final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= );
if (cannotScrollDown || cannotScrollUp) {
return incrementalDeltaY != ;
}
final boolean down = incrementalDeltaY < ;
final boolean inTouchMode = isInTouchMode();
if (inTouchMode) {
hideSelector();
}
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = ;
int count = ;
if (down) {
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = ; 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 - ; i >= ; 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 > ) {
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 >= && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
return false;
}
如果incrementalDeltaY < 0, 表示向下滑動, down = true; 進入一個for循環,一旦child.getBottom() < top,表示 view滑出螢幕就會調用addScrapView,将view添加到mScrapViews數組中; 同樣如果 down = false,一樣的處理,進行view的緩存; 之後會調用detachViewsFromParent對 view 資源進行釋放,因為螢幕外還有很多view等着展示,已經不展示的資料就可以直接釋放了;
之後根據下面這段代碼判斷
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
如果第一個ListView的第一個View的頂部移入螢幕,或者最後一個View的底部移入螢幕,就會調用fillGap 方法,因為可以判斷fillGap方法是用來加載螢幕外資料加載到螢幕上的。現在我們來看下fillGap方法到底藏了什麼武器
AbsListView 的 fillGap方法是個抽象類,應該在ListView 和GridView 中分别實作的
abstract void fillGap(boolean down);
ListView 的fillGap方法:
@Override
void fillGap(boolean down) {
final int count = getChildCount();
if (down) {
int paddingTop = ;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
paddingTop = getListPaddingTop();
}
final int startOffset = count > ? getChildAt(count - ).getBottom() + mDividerHeight :
paddingTop;
fillDown(mFirstPosition + count, startOffset);
correctTooHigh(getChildCount());
} else {
int paddingBottom = ;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
paddingBottom = getListPaddingBottom();
}
final int startOffset = count > ? getChildAt().getTop() - mDividerHeight :
getHeight() - paddingBottom;
fillUp(mFirstPosition - , startOffset);
correctTooLow(getChildCount());
}
}
fillGap方法 根據 使用者滑動的方向分别使用 fillDown 和fillUp 處理,從上篇文章分析我們知道fillDown 方法使用一個循環調用主makeAndAddView獲得view 會在 adapter 的getView 方法中判斷是重新LayoutInflater.from(this).inflate() 布局 還是複用convertView。
到這裡我們就分析完 ListView 在滑動的時候是怎麼處理資料的了,對于即将要滑出螢幕的資料使用RecycleBin的addScrapView方法緩存,并且調用detachViewsFromParent将螢幕外的view detach掉, 對于螢幕上的view根據fillGap方法調用fillDown或者fillUp進而調用makeAndAddView,再調用obtinView擷取view,最後使用setupChild 将 view 通過attachViewToParent或者addViewInLayout 方法 添加到螢幕上。
(6) setAdapter 原理
@Override
public void setAdapter(ListAdapter adapter) {
// 當ListView之前綁定過adapter資訊時,在這裡會清除原有Adapter和資料集觀察者等資訊,
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > || mFooterViewInfos.size() > ) {
mAdapter = wrapHeaderListAdapterInternal(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 - , false);
} else {
position = lookForSelectablePosition(, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == ) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
// 重繪
requestLayout();
}
ListView的setAdapter方法,首先會清楚之前adapter的相關設定,之後重新設定資料觀察者,之後再調用requestLayout() 重新進行繪制過程。
(7)notifyDataSetChanged 原理
adapter的notifyDataSetChanged 會先調用onRestoreInstanceState進行資料恢複,再 調到requestLayout方法進行重繪。setAdapter 沒有目前狀态的儲存,直接進行requestLayout 進行重繪。
我們先進入
BaseAdapter 中的 notifyDataSetChanged
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
mDataSetObservable 的 notifyChanged
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() - ; i >= ; i--) {
mObservers.get(i).onChanged();
}
}
}
mObservers 的onChangde 方法是個空方法。
public void onChanged() {
// Do nothing
}
要想漲到onChanged方法的具體實作,這樣從setAdapter 方法開始追朔。
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > || mFooterViewInfos.size() > ) {
mAdapter = wrapHeaderListAdapterInternal(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();
// 調用Adapter 注冊 觀察者
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - , false);
} else {
position = lookForSelectablePosition(, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == ) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
AdapterDataSetObserver 通過mAdapter.registerDataSetObserver(mDataSetObserver); 将觀察者添加到mObservers集合中,自然,onChanged 方法是在 AdapterDataSetObserver 的。
AdapterDataSetObserver 是 AdapterView 的内部類
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 == && mItemCount > ) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
...
}
到這裡,我們就找到了onChanged 方法的真身了,從代碼個 AdapterView.this.onRestoreInstanceState(mInstanceState); 在開始先判斷是否有狀态儲存,先恢複狀态,再requestLayout();進行重繪
目前為止,對ListView的源碼有了基本的認識。如果有那些地方需要讨論,希望大家指正。哈哈