#####我先解釋下,listView隨着滑動的複用邏輯!算法
首先:攔截先不用說;下面的文章會進行說明,直接說listView重寫的onTouchEvent()事件!數組
當手指觸摸move的時候,緩存
listView 最終會走 trackMotionScroll(int deltaY, int incrementalDeltaY)這一方法; incrementalDeltaY就是兩次移動的y軸座標差值! 對 incrementalDeltaY進行判斷 若是 incrementalDeltaY <0 則是下滑, 不然則是上滑!這個方法將進行判斷,bash
#####加進scrapView中,app
不管是上滑仍是下滑都會把移出屏幕的view(不是徹底移除,當前view移除屏幕就算)都會加入scrapView中 而且detachViewsFromParent(start, count);對加入緩存的view detach掉FromParent!ide
而下面則繼續:佈局
####listView中的全部view進行平移 offsetChildrenTopAndBottom(incrementalDeltaY); 對listView中的全部view進行平移post
而後當某一view完全滑出屏幕的時候 也就是這個判斷 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); } 進行view的添加; 而這面就進行了複用的邏輯; fillGap(down);這個方法裏面都處理好了!性能
####當你要展現最下面的view的時候,會拿你最上面剛剛徹底移除屏幕的view,動畫
####當你要展現最上面的view的時候,會拿你最下面剛剛徹底移除屏幕的view,
這樣就完美的用到了複用,並且剛剛移除屏幕的view,瞬間就會被用到! 這就是簡單的複用邏輯,從scrapView拿出view以後會被置爲null的(原集合)!
final View scrap = scrapViews.remove(size - 1);
clearScrapForRebind(scrap);
return scrap
複製代碼
參考此篇文章:blog.csdn.net/guolin_blog…
RecycleBin機制 其實仍是RecycleBin這個對象
private RecyclerListener mRecyclerListener;
/**
* The position of the first view stored in mActiveViews.
*/
private int mFirstActivePosition;
/**
* Views that were on screen at the start of layout. This array is populated at the start of
* layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
* Views in mActiveViews represent a contiguous range of Views, with position of the first
* view store in mFirstActivePosition.
*/
//佈局開始時屏幕顯示的view,這個數組會在佈局開始時填充,佈局結束後全部view被移至mScrapViews。(也包括是當前屏幕所展現的活躍view)
private View[] mActiveViews = new View[0];
/**
* Unsorted views that can be used by the adapter as a convert view.
*/
//ListView中全部的廢棄緩存。這是一個數組,每一種佈局類型的view都有一個本身的arraylist緩存
private ArrayList<View>[] mScrapViews;
//listView中不一樣item佈局的佈局總數
private int mViewTypeCount;
//跟mScrapViews的區別是,mScrapViews是個隊列數組,ArrayList<View>[]類型,數組長度爲mViewTypeCount,而默認ViewTypeCount = 1的狀況下mCurrentScrap=mScrapViews[0]。
private ArrayList<View> mCurrentScrap;
// 被跳過的,不能複用的view集合。view type小於0或者處理transient狀態的view不能被複用
private ArrayList<View> mSkippedScrap;
//處於transient狀態的view集合,處於transient狀態的view不能被複用,如view的動畫正在播放,
// transient是瞬時、短暫的意思
private SparseArray<View> mTransientStateViews;
//若是adapter的hasStableIds方法返回true,處於過分狀態的view保存到這裏。
//由於須要保存view的position,並且處於過分狀態的view通常不多,
//知道是保存轉換狀態view的集合就行,itemId做爲key(其實就是view的position)
private LongSparseArray<view> mTransientStateViewsById
public void setViewTypeCount(int viewTypeCount) {
........
}
public void markChildrenDirty() {
if (mViewTypeCount == 1) {
.......
} else {
......
}
if (mTransientStateViews != null) {
......
}
if (mTransientStateViewsById != null) {
.....
}
}
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}
/**
* Clears the scrap heap.
*/
void clear() {
.......
}
/**
* Fill ActiveViews with all of the children of the AbsListView.
*
* @param childCount The minimum number of views mActiveViews should hold
* @param firstActivePosition The position of the first view that will be stored in
* mActiveViews
*/
void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
//noinspection MismatchedReadAndWriteOfArray
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. // However, we will NOT place them into scrap views. activeViews[i] = child; // Remember the position so that setupChild() doesn't reset state.
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*
* @param position The position to look up in mActiveViews
* @return The view if it is found, null otherwise
*/
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
View getTransientStateView(int position) {
.......
return null;
}
/**
* Dumps and fully detaches any currently saved views with transient
* state.
*/
void clearTransientStateViews() {
.......
}
/**
* @return A view from the ScrapViews collection. These are unordered.
*/
View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
/**
* Puts a view into the list of scrap views.
* <p>
* If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent
*/
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { // Can't recycle. If it's not a header or footer, which have // special handling and should be ignored, then skip the scrap // heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at
// their old positions.
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<>();
}
mTransientStateViews.put(position, scrap);
} else {
// Otherwise, we'll have to remove the view and start over. clearScrapForRebind(scrap); getSkippedScrap().add(scrap); } } else { clearScrapForRebind(scrap); if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } } private ArrayList<View> getSkippedScrap() { if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<>(); } return mSkippedScrap; } /** * Finish the removal of any views that skipped the scrap heap. */ void removeSkippedScrap() { ....... } /** * Move all views remaining in mActiveViews to mScrapViews. */ void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null; final boolean multipleScraps = mViewTypeCount > 1; ArrayList<View> scrapViews = mCurrentScrap; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) victim.getLayoutParams(); final int whichScrap = lp.viewType; activeViews[i] = null; if (victim.hasTransientState()) { // Store views with transient state for later use. victim.dispatchStartTemporaryDetach(); if (mAdapter != null && mAdapterHasStableIds) { if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<View>(); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<View>(); } mTransientStateViews.put(mFirstActivePosition + i, victim); } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // The data has changed, we can't keep this view.
removeDetachedView(victim, false);
}
} else if (!shouldRecycleViewType(whichScrap)) {
// Discard non-recyclable views except headers/footers.
if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
removeDetachedView(victim, false);
}
} else {
// Store everything else on the appropriate scrap heap.
if (multipleScraps) {
scrapViews = mScrapViews[whichScrap];
}
lp.scrappedFromPosition = mFirstActivePosition + i;
removeDetachedView(victim, false);
scrapViews.add(victim);
if (hasListener) {
mRecyclerListener.onMovedToScrapHeap(victim);
}
}
}
}
pruneScrapViews();
}
/**
* At the end of a layout pass, all temp detached views should either be re-attached or
* completely detached. This method ensures that any remaining view in the scrap list is
* fully detached.
*/
void fullyDetachScrapViews() {
.......
}
/**
* Makes sure that the size of mScrapViews does not exceed the size of
* mActiveViews, which can happen if an adapter does not recycle its
* views. Removes cached transient state views that no longer have
* transient state.
*/
private void pruneScrapViews() {
.......
}
/**
* Updates the cache color hint of all known views.
*
* @param color The new cache color hint.
*/
void setCacheColorHint(int color) {
.......
// Just in case this is called during a layout pass
final View[] activeViews = mActiveViews;
final int count = activeViews.length;
for (int i = 0; i < count; ++i) {
final View victim = activeViews[i];
if (victim != null) {
victim.setDrawingCacheBackgroundColor(color);
}
}
}
private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
final int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position or ID.
// Traverse backwards to find the most recently used scrap view
for (int i = size - 1; i >= 0; i--) {
final View view = scrapViews.get(i);
final AbsListView.LayoutParams params =
(AbsListView.LayoutParams) view.getLayoutParams();
if (mAdapterHasStableIds) {
final long id = mAdapter.getItemId(position);
if (id == params.itemId) {
return scrapViews.remove(i);
}
} else if (params.scrappedFromPosition == position) {
final View scrap = scrapViews.remove(i);
clearScrapForRebind(scrap);
return scrap;
}
}
final View scrap = scrapViews.remove(size - 1);
clearScrapForRebind(scrap);
return scrap;
} else {
return null;
}
}
}
複製代碼
void fillActiveViews(int childCount, int firstActivePosition)
}
複製代碼
這個方法接收兩個參數,第一個參數表示要存儲的view的數量,第二個參數表示ListView中第一個可見元素的position值。RecycleBin當中使用mActiveViews這個數組來存儲View,調用這個方法後就會根據傳入的參數來將ListView中的指定元素存儲到mActiveViews數組當中。
View getActiveView(int position) {
}
複製代碼
這個方法和fillActiveViews()是對應的,用於從mActiveViews數組當中獲取數據。該方法接收一個position參數,表示元素在ListView當中的位置,方法內部會自動將position值轉換成mActiveViews數組對應的下標值。須要注意的是,mActiveViews當中所存儲的View,一旦被獲取了以後就會從mActiveViews當中移除,下次獲取一樣位置的View將會返回null,也就是說mActiveViews不能被重複利用。
void addScrapView(View scrap, int position) {
}
複製代碼
用於將一個廢棄的View進行緩存,該方法接收一個View參數,當有某個View肯定要廢棄掉的時候(好比滾動出了屏幕),就應該調用這個方法來對View進行緩存,RecycleBin當中使用mScrapViews和mCurrentScrap這兩個List來存儲廢棄View。
View getScrapView(int position) {
}
複製代碼
用於從廢棄緩存中取出一個View,這些廢棄緩存中的View是沒有順序可言的,所以getScrapView()方法中的算法也很是簡單,就是直接從mCurrentScrap當中獲取尾部的一個scrap view進行返回。
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
複製代碼
咱們都知道Adapter當中能夠重寫一個getViewTypeCount()來表示ListView中有幾種類型的數據項,而setViewTypeCount()方法的做用就是爲每種類型的數據項都單獨啓用一個RecycleBin緩存機制。實際上,getViewTypeCount()方法一般狀況下使用的並非不少,因此咱們只要知道RecycleBin當中有這樣一個功能就好了。
##那麼下面咱們看是怎麼複用view的 ####滑動加載更多數據 因爲滑動部分的機制是屬於通用型的,即ListView和GridView都會使用一樣的機制,所以這部分代碼就確定是寫在AbsListView當中的了。那麼監聽觸控事件是在onTouchEvent()方法當中進行的,咱們就來看一下AbsListView中的這個方法:
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 = 0; } vtev.offsetLocation(0, 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 >= 0) { // 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 = 0; mActivePointerId = id; mMotionX = x; mMotionY = y; final int motionPosition = pointToPosition(x, y); if (motionPosition >= 0) { // 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; } private void onTouchDown(MotionEvent ev) { mHasPerformedLongPress = false; mActivePointerId = ev.getPointerId(0); hideSelector(); if (mTouchMode == TOUCH_MODE_OVERFLING) { // Stopped the fling. It is a scroll. mFlingRunnable.endFling(); if (mPositionScroller != null) { mPositionScroller.stop(); } mTouchMode = TOUCH_MODE_OVERSCROLL; mMotionX = (int) ev.getX(); mMotionY = (int) ev.getY(); mLastY = mMotionY; mMotionCorrection = 0; mDirection = 0; } else { final int x = (int) ev.getX(); final int y = (int) ev.getY(); int motionPosition = pointToPosition(x, y); if (!mDataChanged) { if (mTouchMode == TOUCH_MODE_FLING) { // Stopped a fling. It is a scroll. createScrollingCache(); mTouchMode = TOUCH_MODE_SCROLL; mMotionCorrection = 0; motionPosition = findMotionRow(y); mFlingRunnable.flywheelTouch(); } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) { // User clicked on an actual view (and was not stopping a // fling). It might be a click or a scroll. Assume it is a // click until proven otherwise. mTouchMode = TOUCH_MODE_DOWN; // FIXME Debounce if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = ev.getX(); mPendingCheckForTap.y = ev.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } } if (motionPosition >= 0) { // Remember where the motion event started final View v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); } mMotionX = x; mMotionY = y; mMotionPosition = motionPosition; mLastY = Integer.MIN_VALUE; } if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION && performButtonActionOnTouchDown(ev)) { removeCallbacks(mPendingCheckForTap); } } 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 == -1) { pointerIndex = 0; 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[0] = x; point[1] = y; transformPointToViewLocal(point, motionView); motionView.drawableHotspotChanged(point[0], point[1]); } break; case TOUCH_MODE_SCROLL: case TOUCH_MODE_OVERSCROLL: scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev); break; } } private void onTouchUp(MotionEvent ev) { switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: final int motionPosition = mMotionPosition; final View child = getChildAt(motionPosition - mFirstPosition); if (child != null) { if (mTouchMode != TOUCH_MODE_DOWN) { child.setPressed(false); } final float x = ev.getX(); final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right; if (inList && !child.hasExplicitFocusable()) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } final AbsListView.PerformClick performClick = mPerformClick; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); mResurrectToPosition = motionPosition; if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress); mLayoutMode = LAYOUT_NORMAL; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { mTouchMode = TOUCH_MODE_TAP; setSelectedPositionInt(mMotionPosition); layoutChildren(); child.setPressed(true); positionSelector(mMotionPosition, child); setPressed(true); if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } mSelector.setHotspot(x, ev.getY()); } if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); } mTouchModeReset = new Runnable() { @Override public void run() { mTouchModeReset = null; mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) { performClick.run(); } } }; postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; updateSelectorState(); } return; } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { performClick.run(); } } } mTouchMode = TOUCH_MODE_REST; updateSelectorState(); break; case TOUCH_MODE_SCROLL: final int childCount = getChildCount(); if (childCount > 0) { final int firstChildTop = getChildAt(0).getTop(); final int lastChildBottom = getChildAt(childCount - 1).getBottom(); final int contentTop = mListPadding.top; final int contentBottom = getHeight() - mListPadding.bottom; if (mFirstPosition == 0 && firstChildTop >= contentTop && mFirstPosition + childCount < mItemCount && lastChildBottom <= getHeight() - contentBottom) { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocity = (int) (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale); // Fling if we have enough velocity and we aren't at a boundary.
// Since we can potentially overfling more than we can overscroll, don't // allow the weird behavior where you can scroll to a boundary then // fling further. boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity; if (flingVelocity && !((mFirstPosition == 0 && firstChildTop == contentTop - mOverscrollDistance) || (mFirstPosition + childCount == mItemCount && lastChildBottom == contentBottom + mOverscrollDistance))) { if (!dispatchNestedPreFling(0, -initialVelocity)) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); mFlingRunnable.start(-initialVelocity); dispatchNestedFling(0, -initialVelocity, true); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); if (mFlingRunnable != null) { mFlingRunnable.endFling(); } if (mPositionScroller != null) { mPositionScroller.stop(); } if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) { dispatchNestedFling(0, -initialVelocity, false); } } } } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } break; case TOUCH_MODE_OVERSCROLL: if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); if (Math.abs(initialVelocity) > mMinimumVelocity) { mFlingRunnable.startOverfling(-initialVelocity); } else { mFlingRunnable.startSpringback(); } break; } setPressed(false); if (mEdgeGlowTop != null) { mEdgeGlowTop.onRelease(); mEdgeGlowBottom.onRelease(); } // Need to redraw since we probably aren't drawing the selector anymore
invalidate();
removeCallbacks(mPendingCheckForLongPress);
recycleVelocityTracker();
mActivePointerId = INVALID_POINTER;
if (PROFILE_SCROLLING) {
if (mScrollProfilingStarted) {
Debug.stopMethodTracing();
mScrollProfilingStarted = false;
}
}
if (mScrollStrictSpan != null) {
mScrollStrictSpan.finish();
mScrollStrictSpan = null;
}
}
複製代碼
這樣,由於方法比較多,那咱們只看
case MotionEvent.ACTION_MOVE: {
onTouchMove(ev, vtev);
break;
}
複製代碼
就就是move, 能夠看到,ACTION_MOVE這個case裏面又嵌套了一個switch語句,是根據當前的TouchMode來選擇的。那這裏我能夠直接告訴你們,當手指在屏幕上滑動時,TouchMode是等於TOUCH_MODE_SCROLL這個值的, 爲何TouchMode的值是TOUCH_MODE_SCROLL呢? 這面就得追朔到
public boolean onInterceptTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_MOVE: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
final int y = (int) ev.getY(pointerIndex);
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
return true;
}
break;
}
break;
}
}
複製代碼
會經過startScrollIfNeeded((int) ev.getX(pointerIndex), y, null))這個方法進行賦值TouchMode TOUCH_MODE_SCROLL; 這樣就會走到
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 == -1) {
pointerIndex = 0;
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[0] = x;
point[1] = y;
transformPointToViewLocal(point, motionView);
motionView.drawableHotspotChanged(point[0], point[1]);
}
break;
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}
複製代碼
這裏面的 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);這一方法;
private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
int rawDeltaY = y - mMotionY;
int scrollOffsetCorrection = 0;
int scrollConsumedCorrection = 0;
if (mLastY == Integer.MIN_VALUE) {
rawDeltaY -= mMotionCorrection;
}
if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
mScrollConsumed, mScrollOffset)) {
rawDeltaY += mScrollConsumed[1];
scrollOffsetCorrection = -mScrollOffset[1];
scrollConsumedCorrection = mScrollConsumed[1];
if (vtev != null) {
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
}
final int deltaY = rawDeltaY;
int incrementalDeltaY =
mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
int lastYCorrection = 0;
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) == 0 &&
Math.abs(rawDeltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
final int motionIndex;
if (mMotionPosition >= 0) {
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() / 2; } int motionViewPrevTop = 0; 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 != 0) {
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(0, overscroll - incrementalDeltaY, 0, overscroll,
mScrollOffset)) {
lastYCorrection -= mScrollOffset[1];
if (vtev != null) {
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
} else {
final boolean atOverscrollEdge = overScrollBy(0, overscroll,
0, mScrollY, 0, 0, 0, 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 = 0; // Reset when entering overscroll.
mTouchMode = TOUCH_MODE_OVERSCROLL;
}
if (incrementalDeltaY > 0) {
mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
(float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
invalidateTopGlow();
} else if (incrementalDeltaY < 0) {
mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
1.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 ? 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;
}
}
}
複製代碼
而後在走向下面的方法
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
return true;
}
final int firstTop = getChildAt(0).getTop();
final int lastBottom = getChildAt(childCount - 1).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 = 0; int effectivePaddingBottom = 0; 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 < 0) { deltaY = Math.max(-(height - 1), deltaY); } else { deltaY = Math.min(height - 1, deltaY); } if (incrementalDeltaY < 0) { incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); } else { incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } final int firstPosition = mFirstPosition; // Update our guesses for where the first and last views are if (firstPosition == 0) { mFirstPositionDistanceGuess = firstTop - listPadding.top; } else { mFirstPositionDistanceGuess += incrementalDeltaY; } if (firstPosition + childCount == mItemCount) { mLastPositionDistanceGuess = lastBottom + listPadding.bottom; } else { mLastPositionDistanceGuess += incrementalDeltaY; } final boolean cannotScrollDown = (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0); final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); if (cannotScrollDown || cannotScrollUp) { return incrementalDeltaY != 0; } final boolean down = incrementalDeltaY < 0; final boolean inTouchMode = isInTouchMode(); if (inTouchMode) { hideSelector(); } final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount(); int start = 0; int count = 0; 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(); boolean selectorOnScreen = false; if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectedPosition, getChildAt(childIndex)); selectorOnScreen = true; } } else if (mSelectorPosition != INVALID_POSITION) { final int childIndex = mSelectorPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectorPosition, getChildAt(childIndex)); selectorOnScreen = true; } } if (!selectorOnScreen) { mSelectorRect.setEmpty(); } mBlockLayoutRequests = false; invokeOnItemScrollListener(); return false; } 複製代碼
這個方法接收兩個參數,deltaY表示從手指按下時的位置到當前手指位置的距離,incrementalDeltaY則表示據上次觸發event事件手指在Y方向上位置的改變量,那麼其實咱們就能夠經過incrementalDeltaY的正負值狀況來判斷用戶是向上仍是向下滑動的了。
如看代碼:
if (incrementalDeltaY < 0) {
incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
} else {
incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
}
複製代碼
,若是incrementalDeltaY小於0,說明是向下滑動,不然就是向上滑動。
final boolean down = incrementalDeltaY < 0;
複製代碼
進行判斷: 下面將會進行一個邊界值檢測的過程,能夠看到,從上面判斷開始,當ListView向下滑動的時候,就會進入一個for循環當中,從上往下依次獲取子View,第47行當中,若是該子View的bottom值已經小於top值了,就說明這個子View已經移出屏幕了,因此會調用RecycleBin的addScrapView()方法將這個View加入到廢棄緩存當中,並將count計數器加1,計數器用於記錄有多少個子View被移出了屏幕。那麼若是是ListView向上滑動的話,其實過程是基本相同的,只不過變成了從下往上依次獲取子View,而後判斷該子View的top值是否是大於bottom值了,若是大於的話說明子View已經移出了屏幕,一樣把它加入到廢棄緩存中,並將計數器加1。
接下來在
if (count > 0) {
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
複製代碼
,會根據當前計數器的值來進行一個detach操做,它的做用就是把全部移出屏幕的子View所有detach掉,在ListView的概念當中,全部看不到的View就沒有必要爲它進行保存,由於屏幕外還有成百上千條數據等着顯示呢,一個好的回收策略才能保證ListView的高性能和高效率。
offsetChildrenTopAndBottom(incrementalDeltaY);
複製代碼
這個方法,並將incrementalDeltaY做爲參數傳入,這個方法的做用是讓ListView中全部的子View都按照傳入的參數值進行相應的偏移,這樣就實現了隨着手指的拖動,ListView的內容也會隨着滾動的效果。
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
複製代碼
而後在進行判斷,若是ListView中最後一個View的底部已經移入了屏幕,或者ListView中第一個View的頂部移入了屏幕,就會調用fillGap()方法,那麼所以咱們就能夠猜出fillGap()方法是用來加載屏幕外數據的,進入到這個方法中瞧一瞧,以下所示:
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());
}
}
複製代碼
down參數用於表示ListView是向下滑動仍是向上滑動的,能夠看到,若是是向下滑動的話就會調用fillDown()方法,而若是是向上滑動的話就會調用fillUp()方法。那麼這兩個方法咱們都已經很是熟悉了,內部都是經過一個循環來去對ListView進行填充,因此這兩個方法咱們就不看了,可是填充ListView會經過調用makeAndAddView()方法來完成,又是makeAndAddView()方法,但此次的邏輯再次不一樣了,因此咱們仍是回到這個方法看一看:
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
// Try to use an existing view for this position.
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs // to be positioned like a scrap view. setupChild(activeView, position, y, flow, childrenLeft, selected, true); return activeView; } } // Make a new view for this position, or convert an unused view if // possible. final View child = obtainView(position, mIsScrap); // This needs to be positioned and measured. setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; } 複製代碼
無論怎麼說,這裏首先仍然是會嘗試調用RecycleBin的getActiveView()方法來獲取子佈局,只不過確定是獲取不到的了,由於在第二次Layout過程當中咱們已經從mActiveViews中獲取過了數據,而根據RecycleBin的機制,mActiveViews是不可以重複利用的,所以這裏返回的值確定是null。 因此接下來應該會走
final View child = obtainView(position, mIsScrap);
複製代碼
obtainView(position, mIsScrap)方法中
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
複製代碼
View obtainView(int position, boolean[] outMetadata) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
outMetadata[0] = false;
final View transientView = mRecycler.getTransientStateView(position);
if (transientView != null) {
final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
if (params.viewType == mAdapter.getItemViewType(position)) {
final View updatedView = mAdapter.getView(position, transientView, this);
if (updatedView != transientView) {
setItemViewLayoutParams(updatedView, position);
mRecycler.addScrapView(updatedView, position);
}
}
outMetadata[0] = true;
transientView.dispatchFinishTemporaryDetach();
return transientView;
}
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
return child;
}
複製代碼
這裏在調用RecyleBin的getScrapView()方法來嘗試從廢棄緩存中獲取一個View,那麼廢棄緩存有沒有View呢?固然有,由於剛纔在trackMotionScroll()方法中咱們就已經看到了,一旦有任何子View被移出了屏幕,就會將它加入到廢棄緩存中,而從obtainView()方法中的邏輯來看,一旦有新的數據須要顯示到屏幕上,就會嘗試從廢棄緩存中獲取View。因此它們之間就造成了一個生產者和消費者的模式,那麼ListView神奇的地方也就在這裏體現出來了,無論你有任意多條數據須要顯示,ListView中的子View其實來來回回就那麼幾個,移出屏幕的子View會很快被移入屏幕的數據從新利用起來,於是無論咱們加載多少數據都不會出現OOM的狀況,甚至內存都不會有所增長。
那麼另外還有一點是須要你們留意的,這裏獲取到了一個scrapView,而後咱們將它做爲第二個參數傳入到了Adapter的getView()方法當中。那麼第二個參數是什麼意思呢?咱們再次看一下一個簡單的getView()方法示例:
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
} else {
view = convertView;
}
return view;
}
複製代碼
第二個參數就是咱們最熟悉的convertView呀,難怪平時咱們在寫getView()方法是要判斷一下convertView是否是等於null,若是等於null才調用inflate()方法來加載佈局,不等於null就能夠直接利用convertView,由於convertView就是咱們以前用過的View,只不過被移出屏幕後進入到了廢棄緩存中,如今又從新拿出來使用而已。而後咱們只須要把convertView中的數據更新成當前位置上應該顯示的數據,那麼看起來就好像是全新加載出來的一個佈局同樣,這背後的道理你是否是已經徹底搞明白了?
以後的代碼又都是咱們熟悉的流程了,從緩存中拿到子View以後再調用setupChild()方法將它從新attach到ListView當中,由於緩存中的View也是以前從ListView中detach掉的,這部分代碼就再也不重複進行分析了。