郭林大神的博客 http://blog.csdn.net/guolin_blog/article/details/44996879
下面這篇博客對(duì)listView的觸摸事件講的比較細(xì)
http://www.lxweimin.com/p/7f95297b6271
注:本文是以API 25的代碼為準(zhǔn)
使用ListView的時(shí)候,還有一個(gè)東西不得不提就是BaseAdapter,這有這兩個(gè)東西同時(shí)使用的時(shí)候,才能顯示出列表.下面我們就一起來(lái)看看ListView和BaseAdapter
BaseAdapter及其父親和爺爺:
從圖中我們可以看出BaseAdapter實(shí)現(xiàn)了ListAdapter和SpinnerAdapter,而ListAdapter和SpinnerAdapter又同時(shí)繼承了Adapter接口,下面我們就一起看看這個(gè)幾個(gè)類里面都干了些什么.
Adapter:官方文檔描述->Adapter是AdapterView和數(shù)據(jù)之間的一個(gè)橋梁,Adapter提供對(duì)數(shù)據(jù)項(xiàng)的訪問(wèn),也負(fù)責(zé)填充view.
Adapter中提供了getCount(),getView(),registerDataSetObserver()等最基本的方法.
ListAdapter:官方文檔描述->ListAdapter是Adapter 的擴(kuò)展,ListAdapter和ListView配合可以顯示任何的數(shù)據(jù)
ListAdapter中相較于Adapter只擴(kuò)展了兩個(gè)方法areAllItemsEnabled()和isEnabled(position),areAllItemsEnabled()是設(shè)置所有的items都可用(點(diǎn)擊或者選中)或者不可用(不可點(diǎn)擊或者不可選中)(由返回值來(lái)判斷),isEnabled(position)是設(shè)置指定位置的item是否可以點(diǎn)擊或者選中(由返回值來(lái)判斷)
SpinnerAdapter:官方文檔描述->SpinnerAdapter是Adapter的擴(kuò)展,Spinner一般顯示兩種視圖,一種是Spinner本身的視圖,一種是在他按下以后顯示的列表視圖.
SpinnerAdapter中相較于Adapter中只擴(kuò)展了一個(gè)方法getDropDownView(int position,View convertView,ViewGroup parent)該方法和Adapter中的getView()方法效果基本是一致的,是用于創(chuàng)建顯示在Spinner中的ui的,一般是供AbsSpinner調(diào)用. BaseAdapter中對(duì)該方法的實(shí)現(xiàn)就是直接返回getView()方法
BaseAdapter:官方文檔描述->實(shí)現(xiàn)了Adapter的普通基類,可用于listView(因?yàn)閷?shí)現(xiàn)了listAdapter)和Spanner(因?yàn)閷?shí)現(xiàn)了SpannerAdapter)
BaseAdapter實(shí)現(xiàn)了Adapter ,ListAdapter,Spanner中大部分的方法比如notifyDataSetChanged(),registerDataSetObserver(),unregisterDataSetObserver().BaseAdpter中只有一個(gè)成員變量就是mDataSetObservable
它是DataSetObservable的實(shí)例
private final DataSetObservable mDataSetObservable = new DataSetObservable();
其中registerDataSetObserver()(設(shè)置數(shù)據(jù)變化監(jiān)聽(tīng))就是將傳過(guò)來(lái)的DataSetObserver設(shè)置給了DataSetObserveable.
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
registerDataSetObserver()這個(gè)方法回到我們給listView設(shè)置adapter的時(shí)候調(diào)用
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
********
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
}
unregisterDataSetObserver()(取消數(shù)據(jù)變化監(jiān)聽(tīng))也是調(diào)用DataSetObserveable的取消監(jiān)聽(tīng)的方法
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
notifyDataSetChanged()這是我們經(jīng)常使用的方法,是用來(lái)進(jìn)行刷新列表的他其實(shí)也是調(diào)用DataSetObserveable中的notifyChanged()方法來(lái)出發(fā)數(shù)據(jù)變化的
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
從上面這三個(gè)方法分析得出,其實(shí)DataSetObservable才是真正監(jiān)聽(tīng)數(shù)據(jù)變化的類
再說(shuō)ListView之前我們先說(shuō)說(shuō)ListView中非常重要的緩存機(jī)制RecycleBin
RecycleBin
這里的RecycleBin代碼并不全,我只是把最主要的幾個(gè)方法提了出來(lái)。那么我們先來(lái)對(duì)這幾個(gè)方法進(jìn)行簡(jiǎn)單解讀,這對(duì)后面分析ListView的工作原理將會(huì)有很大的幫助。
private View[] mActiveViews = new View[0];-->mActiveViews 中保存的是從第一個(gè)可見(jiàn)的item開(kāi)始的連續(xù)的視圖范圍,當(dāng)布局結(jié)束時(shí)mActiveViews中的view將會(huì)轉(zhuǎn)移到mScrapViews中
private ArrayList<View>[] mScrapViews;-->mScrapViews中保存的是廢棄的view,比如說(shuō)是移出屏幕的view
private ArrayList<View> mCurrentScrap;-->和mScrapViews一樣也是用來(lái)保存廢棄view的
fillActiveViews() 這個(gè)方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)表示要存儲(chǔ)的view的數(shù)量,第二個(gè)參數(shù)表示ListView中第一個(gè)可見(jiàn)元素的position值。RecycleBin當(dāng)中使用mActiveViews這個(gè)數(shù)組來(lái)存儲(chǔ)View,調(diào)用這個(gè)方法后就會(huì)根據(jù)傳入的參數(shù)來(lái)將ListView中的指定元素存儲(chǔ)到mActiveViews數(shù)組當(dāng)中。
getActiveView() 這個(gè)方法和fillActiveViews()是對(duì)應(yīng)的,用于從mActiveViews數(shù)組當(dāng)中獲取數(shù)據(jù)。該方法接收一個(gè)position參數(shù),表示元素在ListView當(dāng)中的位置,方法內(nèi)部會(huì)自動(dòng)將position值轉(zhuǎn)換成mActiveViews數(shù)組對(duì)應(yīng)的下標(biāo)值。需要注意的是,mActiveViews當(dāng)中所存儲(chǔ)的View,一旦被獲取了之后就會(huì)從mActiveViews當(dāng)中移除,下次獲取同樣位置的View將會(huì)返回null,也就是說(shuō)mActiveViews不能被重復(fù)利用。
addScrapView() 用于將一個(gè)廢棄的View進(jìn)行緩存,該方法接收一個(gè)View參數(shù),當(dāng)有某個(gè)View確定要廢棄掉的時(shí)候(比如滾動(dòng)出了屏幕),就應(yīng)該調(diào)用這個(gè)方法來(lái)對(duì)View進(jìn)行緩存,RecycleBin當(dāng)中使用mScrapViews和mCurrentScrap這兩個(gè)List來(lái)存儲(chǔ)廢棄View。
getScrapView 用于從廢棄緩存中取出一個(gè)View,這些廢棄緩存中的View是沒(méi)有順序可言的,因此getScrapView()方法中的算法也非常簡(jiǎn)單,就是直接從mCurrentScrap當(dāng)中獲取尾部的一個(gè)scrap view進(jìn)行返回。
setViewTypeCount() 我們都知道Adapter當(dāng)中可以重寫(xiě)一個(gè)getViewTypeCount()來(lái)表示ListView中有幾種類型的數(shù)據(jù)項(xiàng),而setViewTypeCount()方法的作用就是為每種類型的數(shù)據(jù)項(xiàng)都單獨(dú)啟用一個(gè)RecycleBin緩存機(jī)制。實(shí)際上,- - - getViewTypeCount()方法通常情況下使用的并不是很多,所以我們只要知道RecycleBin當(dāng)中有這樣一個(gè)功能就行了。
ListView
從上圖可以清晰的看出ListView的繼承體系下面我們就對(duì)這些類做一些大致的了解
AbsListView官方文檔描述->它是可用于實(shí)現(xiàn)虛擬化列表的基類,這個(gè)列表沒(méi)有特殊的空間定義.例如這個(gè)類的子類可以以網(wǎng)格,輪播,堆疊等方式顯示列表內(nèi)容.-->所以我們?nèi)绻远x列表就可以繼承該類
AdapterView官方文檔描述->AdapterView就是其子View由Adapter決定的View.
下面是對(duì)ListView源碼的解讀
setAdapter():這個(gè)方法的作用是綁定listView所需的數(shù)據(jù)
-
調(diào)用這個(gè)方法中首先是將之前給adapter設(shè)置的監(jiān)聽(tīng)解除掉然后也將listView進(jìn)行重置也將之前緩存的view進(jìn)行清除
//解除數(shù)據(jù)監(jiān)聽(tīng)
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
//listView進(jìn)行重置
resetList();
//緩沖池進(jìn)行清除
mRecycler.clear();//重置成員變量 mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID;
-
如果該listView中包含頭布局或者是腳布局那么就會(huì)把該adapter包裝成HeaderViewListAdapter否則不會(huì)包裝
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {//如果有頭布局或者腳布局則進(jìn)行包裝 mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter); } else {不包裝 mAdapter = adapter; }
-
調(diào)用父類的setAdapter方法,也是做了一些數(shù)據(jù)還原,和初始化
// AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter);
-
當(dāng)mAdapter不為null的時(shí)候?qū)istView中的變量進(jìn)行初始化
if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); //設(shè)置數(shù)據(jù)監(jiān)聽(tīng) mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); //這里給緩存給負(fù)責(zé)緩存的對(duì)象設(shè)置一共要生成幾種緩存的view 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(); }
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount())這里給緩存給負(fù)責(zé)緩存的對(duì)象設(shè)置一共要生成幾種緩存的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;
}-
mAdapter為null的情況下也做了一下操作
else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); }
-
最后請(qǐng)求重繪
requestLayout();
所以setAdapter()中主要是做數(shù)據(jù)的重置和初始化工作,在這里添加了數(shù)據(jù)監(jiān)聽(tīng),也初始化了item的緩沖池
在setAdapter()方法的最后它調(diào)用了requestLayout()方法,那么我們就來(lái)看看onLayout()中是怎么擺放item的,但是在這之前我們還是很有必要看一下onMeasure()方法,看看listView是如何測(cè)量自己的子View的
onMeasure()
listView自己實(shí)現(xiàn)了onMeasure()方法
1.在onMeasure()中的第一句代碼就是調(diào)用了父類的onMeasure()方法也就是AbsListView的onMeasure()方法,父類的onMeasure()主要是設(shè)置了listView的Padding
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
2.當(dāng)listView中有item而且他的寬或者高德MeasureSpec mode 是 UNSPECIFIED的時(shí)候
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
//當(dāng)listView中有item而且他的寬或者高德MeasureSpec mode 是 UNSPECIFIED的時(shí)候
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
//去獲取第一個(gè)childView
final View child = obtainView(0, mIsScrap);
3.其中調(diào)用了obtainView(int position, boolean[] outMetadata)這個(gè)方法去獲取childView,這個(gè)方法中的第一個(gè)if判斷是對(duì) "transient state view"的判斷一般一般不會(huì)進(jìn)到這個(gè)if語(yǔ)句中 --->現(xiàn)在還不知道這個(gè)transient state view具體是什么東西0.0
// 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);
}
}
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
transientView.dispatchFinishTemporaryDetach();
return transientView;
}
4.下面這段代碼就是一般真正的從復(fù)用view的代碼.首先他先從recycleBin中取出scrapView然后傳給adapter的getView(),getView()也會(huì)返回一個(gè)child,如果scrapView不為null則說(shuō)明緩沖池中有view,然后再進(jìn)行判斷scrapViwe和child是否相同,如果不相同就調(diào)用 mRecycler.addScrapView(scrapView, position);將scrapView再添加到緩沖池中
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()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
5.到這里obtainView()就完成了產(chǎn)出一個(gè)child的任務(wù),下面的代碼就是給child進(jìn)行設(shè)置一些東西,比如"DrawingCacheBackgroundColor","LayoutParams"等
6.到最后就是直接返回這個(gè)來(lái)之不易的child
.....
return child;
7.obtainView()方法走完以后他返回了一個(gè)child,下面會(huì)調(diào)用measureScrapChild()這個(gè)方法測(cè)量child
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
//上面注釋的意思就是 按照父親的MeasureSpec來(lái)測(cè)量可以獲取到child的最小寬度和高度
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
8.child測(cè)量完了以后然后將child添加到緩存池中
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
9.下來(lái)的代碼就是調(diào)用setMeasureDimension(widthSize, heightSize)將listView的寬高進(jìn)行保存
ListView的onMeasure()總結(jié):
**_看完代碼以后我們知道在onMeasure()方法的時(shí)候已經(jīng)把position為0的child造出來(lái)了,也進(jìn)行了測(cè)量并加入到了緩沖池RecycleBin中.但是值得注意的是在ListView的父類的寬或者搞的mode是MeasureSpec.UNSPECIFIED這里L(fēng)istView的高只會(huì)是一個(gè)item的高 (這也就是scallView嵌套listView出現(xiàn)問(wèn)題的原因)_**
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
******
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
******
解決的方法也就很簡(jiǎn)單將重寫(xiě)listView的onMeasure()方法就行了,就是將父類的 mode從 MeasureSpec.UNSPECIFIED改為MeasureSpec.AT_MOST因?yàn)楫?dāng)父類的mode的是MeasureSpec.AT_MOST的時(shí)候listView會(huì)循環(huán)獲取child的高并想加
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
onMeasure()說(shuō)完了下面我們來(lái)看看onLayout()
onLayout()
ListView中沒(méi)有實(shí)現(xiàn)onLayout()方法,但是復(fù)寫(xiě)了父類onLayout()中調(diào)用的一個(gè)方法layoutChildren()方法.后面我們就一起看看ListView中的layoutChildren()是怎么實(shí)現(xiàn)的,但是在看layoutChildren()之前我們先看看AbsListView中的onLayout()都干了什么.
onLayout()>AbsListView-->其實(shí)在這個(gè)方法中也沒(méi)有做什么處理就是判斷了一下當(dāng)change改變的時(shí)候的情況,主要還是在layoutChildren()方法中,下面我們就看看這個(gè)方法
layoutChildren()>ListView這個(gè)方法也是挺長(zhǎng)的,大概有300多行.
1.layoutChildren中首先做的一個(gè)操作就是避免在短時(shí)間內(nèi)重復(fù)調(diào)用layoutChildren()該方法
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (blockLayoutRequests) {
return;
}
mBlockLayoutRequests = true;
*****
//最后又將mBlockLayoutRequests置為false
finally {
*****
if (!blockLayoutRequests) {
mBlockLayoutRequests = false;
}
}
2.調(diào)用了父類的layoutChildren()方法,其實(shí)是一個(gè)空方法
try {
super.layoutChildren();
****
3.調(diào)用了invalidate()方法來(lái)出發(fā)draw()方法,這里不太明白為什么這里要調(diào)用invalidate(),難道是要一遍擺放一邊?
invalidate();
4.是在對(duì)adapter為null的情況進(jìn)行了判斷,當(dāng)adapter為null的時(shí)候,會(huì)清空列表然后直接返回
if (mAdapter == null) {
resetList();
invokeOnItemScrollListener();
return;
}
5.在不同的layoutMode情況下對(duì)第一個(gè)item,選中的item進(jìn)行緩存
// Remember stuff we will need down below
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
index = mNextSelectedPosition - mFirstPosition;
if (index >= 0 && index < childCount) {
newSel = getChildAt(index);
}
break;
case LAYOUT_FORCE_TOP:
case LAYOUT_FORCE_BOTTOM:
case LAYOUT_SPECIFIC:
case LAYOUT_SYNC:
break;
case LAYOUT_MOVE_SELECTION:
default:
// Remember the previously selected view
index = mSelectedPosition - mFirstPosition;
if (index >= 0 && index < childCount) {
oldSel = getChildAt(index);
}
// Remember the previous first child
oldFirst = getChildAt(0);
if (mNextSelectedPosition >= 0) {
delta = mNextSelectedPosition - mSelectedPosition;
}
// Caution: newSel might be null
newSel = getChildAt(index + delta);
}
6.當(dāng)數(shù)據(jù)變化時(shí) ,則進(jìn)行同步數(shù)據(jù)
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
7.對(duì)空item或者錯(cuò)誤的item數(shù)進(jìn)行判斷和操作,當(dāng)空item的時(shí)候也會(huì)清空列表,當(dāng)錯(cuò)誤的item數(shù)使會(huì)拋出異常
if (mItemCount == 0) {
resetList();
invokeOnItemScrollListener();
return;
} else if (mItemCount != mAdapter.getCount()) {
throw new IllegalStateException("The content of the adapter has changed but "
+ "ListView did not receive a notification. Make sure the content of "
+ "your adapter is not modified from a background thread, but only from "
+ "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
+ "when its content changes. [in ListView(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}
8.同步選中的數(shù)據(jù)
setSelectedPositionInt(mNextSelectedPosition);
9.對(duì)child是否具有輔助功能進(jìn)行判斷,如果有則進(jìn)入輔助功能的邏輯
// Remember which child, if any, had accessibility focus. This must
// occur before recycling any views, since that will clear
// accessibility focus.
final ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
if (focusHost != null) {
final View focusChild = getAccessibilityFocusedChild(focusHost);
if (focusChild != null) {
if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
|| focusChild.hasTransientState() || mAdapterHasStableIds) {
// The views won't be changing, so try to maintain
// focus on the current host and virtual view.
accessibilityFocusLayoutRestoreView = focusHost;
accessibilityFocusLayoutRestoreNode = viewRootImpl
.getAccessibilityFocusedVirtualView();
}
// If all else fails, maintain focus at the same
// position.
accessibilityFocusPosition = getPositionForView(focusChild);
}
}
}
10.對(duì)焦點(diǎn)進(jìn)行處理
final View focusedChild = getFocusedChild();
if (focusedChild != null) {
// TODO: in some cases focusedChild.getParent() == null
// We can remember the focused view to restore after re-layout
// if the data hasn't changed, or if the focused position is a
// header or footer.
if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
|| focusedChild.hasTransientState() || mAdapterHasStableIds) {
focusLayoutRestoreDirectChild = focusedChild;
// Remember the specific view that had focus.
focusLayoutRestoreView = findFocus();
if (focusLayoutRestoreView != null) {
// Tell it we are going to mess with it.
focusLayoutRestoreView.dispatchStartTemporaryDetach();
}
}
requestFocus();
}
11.將所有的item都加入到緩存池中
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
12.清除緩存池中舊的view
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
13.對(duì)mLayoutMode進(jìn)行判斷然后選擇填充listView的方法,一般的話都會(huì)執(zhí)行default.
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
***********
default:
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
14.在default中當(dāng)childCount為0的時(shí)候會(huì)執(zhí)行fillFromTop()方法,去從上倒下的填充listView
//Fills the list from top to bottom, starting with mFirstPosition
private View fillFromTop(int nextTop) {
mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
if (mFirstPosition < 0) {
mFirstPosition = 0;
}
return fillDown(mFirstPosition, nextTop);
}
15.在fillFromTop()方法中其實(shí)也就調(diào)用了一個(gè)方法fillDown(),這個(gè)方法中就調(diào)用了makeAndAddView()方法去填充 一屏 的listView
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
16.fillFromTop()中調(diào)用了makeAndAddView()方法去創(chuàng)建一個(gè)View并將這個(gè)View添加到listView中.在makeAndAddView()方法中首先回去RecycleBin中拿緩存的View如果這個(gè)view不為null,則進(jìn)行使用并返回,如果是null就會(huì)調(diào)用obtainView()去創(chuàng)建一個(gè)新的view,使用并返回
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;
}
17.obtainView()view就將adapter和listView聯(lián)系了起來(lái),在obtainView()中首先回去RecycleBin中獲取一個(gè)廢棄的View 然后傳到adapter中的getView()方法中,這里這個(gè)廢棄的View很有可能的null.
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()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
18.調(diào)用mRecycler.getActiveView()或者obtainView()產(chǎn)生了view以后調(diào)用setupChild()方法,其中setupChild()方法中會(huì)調(diào)用addViewInLayout()或者attachViewToParent()將這個(gè)view添加到listView中attachViewToParent()比addViewInLayout()的效率高很多
19.刷新RecycleBin中mActiveViews中的緩存-->其實(shí)是將mActiveViews中的緩存挪到了mScrapViews
// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();
20.移除沒(méi)有用的header和footer
// remove any header/footer that has been temp detached and not re-attached
removeUnusedFixedViews(mHeaderViewInfos);
removeUnusedFixedViews(mFooterViewInfos);
21.找到選中的位置然后對(duì)選中的位置進(jìn)行一系列到操作
onInterceptTouchEvent()
下面我們一起來(lái)看看listView滑動(dòng)過(guò)程中的操作,那么就要從onInterceptTouchEvent()這個(gè)方法開(kāi)始了.onInterceptTouchEvent()->listView中沒(méi)有重寫(xiě)這個(gè)方法,那么我們就在AbsListView中看看吧.
1.MotionEvent.ACTION_DOWN中當(dāng)listView在慣性滑動(dòng)或者滑動(dòng)狀態(tài)是就要攔截
if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
mMotionCorrection = 0;
return true;
}
2.獲取觸摸位置對(duì)應(yīng)的item的position,這個(gè)方法在listView中實(shí)現(xiàn)了
int motionPosition = findMotionRow(y);
3.當(dāng)listView處于fling狀態(tài)而且觸摸的位置在listView上則進(jìn)行記錄,觸摸的X坐標(biāo),觸摸的Y坐標(biāo),觸摸位置對(duì)應(yīng)的item的position,和TouchMode;
if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
// User clicked on an actual view (and was not stopping a fling).
// Remember where the motion event started
v = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = v.getTop();//觸摸位置對(duì)應(yīng)的item的top值
mMotionX = x;//觸摸的X坐標(biāo)
mMotionY = y;//觸摸的Y坐標(biāo)
mMotionPosition = motionPosition;//觸摸位置對(duì)應(yīng)的item的position
mTouchMode = TOUCH_MODE_DOWN;//將TouchMode設(shè)置為T(mén)OUCH_MODE_DOWN
clearScrollingCache();
}
4.當(dāng)TouchMode為T(mén)OUCH_MODE_FLING的時(shí)候攔截
if (touchMode == TOUCH_MODE_FLING) {
return true;
}
5.MotionEvent.ACTION_MOVE中判斷是否攔截,是onInterceptTouchEvent()的核心
if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
return true;
}
6.所以我們來(lái)看一下startScrollIfNeeded()這個(gè)方法
// 這個(gè)方法在onInterceptTouchEvent的move事件中調(diào)用,在onTouchEvent()的onTouchMove()方法
// 中開(kāi)始時(shí)候也會(huì)調(diào)用
private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
// Check if we have moved far enough that it looks more like a
// scroll than a tap
// 得到當(dāng)前事件的y值與down事件時(shí)候設(shè)置的值的差值
final int deltaY = y - mMotionY;
final int distance = Math.abs(deltaY);
// mScrollY!=0即overscroll為true ,核心為distance > mTouchSlop即攔截事件自己處理
// mTouchSlop在構(gòu)造函數(shù)中初始化并賦值了
final boolean overscroll = mScrollY != 0;
if ((overscroll || distance > mTouchSlop) &&
(getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
createScrollingCache();
if (overscroll) {
mTouchMode = TOUCH_MODE_OVERSCROLL;
mMotionCorrection = 0;
} else {
// 設(shè)置觸摸模式為T(mén)OUCH_MODE_SCROLL,在onTouchEvent()用到
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
}
// 取消子view的長(zhǎng)按監(jiān)聽(tīng)觸發(fā)
removeCallbacks(mPendingCheckForLongPress);
setPressed(false);
final View motionView = getChildAt(mMotionPosition - mFirstPosition);
// listview攔截了事件本身處理,所以恢復(fù)可能設(shè)置子view的press狀態(tài)
if (motionView != null) {
motionView.setPressed(false);
}
// 通知ScrollState狀態(tài)變化回調(diào)
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
// Time to start stealing events! Once we've stolen them, don't let anyone
// steal from us
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
// 作用如名,如果滿足條件,滾動(dòng)listview
scrollIfNeeded(x, y, vtev);
return true;
}
return false;
}
7.MotionEvent.ACTION_CANCEL或者M(jìn)otionEvent.ACTION_UP中則是將TouchMode恢復(fù)成默認(rèn)狀態(tài),然后回收VelocityTracker相關(guān)資源
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
mTouchMode = TOUCH_MODE_REST;//將TouchMode恢復(fù)成默認(rèn)狀態(tài)
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();//回收VelocityTracker相關(guān)資源
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
stopNestedScroll();
break;
}
onTouchEvent()
onTouchEvent()->listView中沒(méi)有重寫(xiě)onTouchEvent方法,那么我們就在AbsListView中看看吧 ,因?yàn)閛nTouchEvent()中的邏輯很多 所以我們就主要
看看Down事件(onTouchDown())和Move事件(onTouchMove())
onTouchDown()中首先會(huì)判斷TouchMode如果是fling狀態(tài),就讓他停下來(lái),然后記錄手指點(diǎn)擊的位置和TouchMode等
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;
}
當(dāng)TouchMode不是fling狀態(tài)的話,首先會(huì)記錄手指的落點(diǎn),然后計(jì)算到落點(diǎn)對(duì)應(yīng)的item的position
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int motionPosition = pointToPosition(x, y);
當(dāng)數(shù)據(jù)沒(méi)有變化,我們就要重新判斷TouchMode,如果是TOUCH_MODE_FLING的話就要將TouchMode改為T(mén)OUCH_MODE_SCROLL并且馬上中斷fling,然后通過(guò)findMotionRow()這個(gè)方法重新找一下手指落點(diǎn)對(duì)應(yīng)的item的position
if (mTouchMode == TOUCH_MODE_FLING) {
// Stopped a fling. It is a scroll.
createScrollingCache();
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = 0;
motionPosition = findMotionRow(y);
mFlingRunnable.flywheelTouch();//中斷fling
}
當(dāng)點(diǎn)擊的item是可用的那么又要改變TouchMode
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());
}
各種情況下獲取到了手指落點(diǎn)對(duì)應(yīng)的position也就是motionPosition,這里對(duì)motionPosition進(jìn)行判斷如果大于等于零,那么就獲取到這個(gè)item,并記錄他的top值
if (motionPosition >= 0) {
// Remember where the motion event started
final View v = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = v.getTop();
}
下來(lái)在對(duì)落點(diǎn)的坐標(biāo)和motionPostion進(jìn)行保存
mMotionX = x;
mMotionY = y;
mMotionPosition = motionPosition;
mLastY = Integer.MIN_VALUE;
最后判斷TouchMode是TOUCH_MODE_DOWN,而且motionPosition不是默認(rèn)值并且ListView接受這個(gè)down時(shí)間那么就取消其他的操作(后面看看mPendingCheckForTap這個(gè)runnable是干啥的)
if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
&& performButtonActionOnTouchDown(ev)) {
removeCallbacks(mPendingCheckForTap);
}
onTouchMove()中如果數(shù)據(jù)改變了就要重新排列item
if (mDataChanged) {
// Re-sync everything if data has been changed
// since the scroll operation can query the adapter.
layoutChildren();
}
在mode時(shí)間中還有一個(gè)switch語(yǔ)句,當(dāng)TouchMode為T(mén)OUCH_MODE_DOWN,TOUCH_MODE_TAP,TOUCH_MODE_DONE_WAITING等沒(méi)有動(dòng)的狀態(tài)是會(huì)去判斷是否可以滾動(dòng)
,核心判斷調(diào)條件為down事件y與down事件mMotionY值的差值絕對(duì)值是否大于mTouchSlop
if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
break;
}
下面會(huì)判斷點(diǎn)擊的區(qū)域是否在ListView內(nèi) 如果不是則取消操作
// 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]);
}
如果已經(jīng)是TOUCH_MODE_SCROLL和TOUCH_MODE_OVERSCROLL這種滾動(dòng)狀態(tài),則執(zhí)行scrollIfNeeded()方法判斷是否要進(jìn)行滾動(dòng).
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
scrollifNeeded()中主要負(fù)責(zé)滾動(dòng)的方法就是trackMotionScroll()
trackMotionScroll()這個(gè)方法接收兩個(gè)參數(shù),deltaY表示從手指按下時(shí)的位置到當(dāng)前手指位置的距離,incrementalDeltaY則表示據(jù)上次觸發(fā)event事件手指在Y方向上位置的改變量,那么其實(shí)我們就可以通過(guò)incrementalDeltaY的正負(fù)值情況來(lái)判斷用戶是向上還是向下滑動(dòng)的了.在滾動(dòng)的過(guò)程中使用fillGap()方法進(jìn)行item的填充
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;
}
//incrementalDeltaY表示據(jù)上次觸發(fā)event事件手指在Y方向上位置的改變量
final boolean down = incrementalDeltaY < 0;//用來(lái)判斷是向上滑動(dòng)還是向下滑動(dòng),true的話就是向下滑動(dòng),false就是向上滑動(dòng)
final boolean inTouchMode = isInTouchMode();
if (inTouchMode) {
hideSelector();
}
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0;
int count = 0;
if (down) {//down為true則向下滑動(dòng),false則為向上滑動(dòng)
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) {//說(shuō)明child還在屏幕內(nèi)
break;
} else {//說(shuō)明child已經(jīng)挪到了屏幕外
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
//將item重置
child.clearAccessibilityFocus();
//將child放進(jìn)RecycleBin的回收池內(nèi)
mRecycler.addScrapView(child, position);
}
}
}
} else {//邏輯和向下移動(dòng)一樣只不過(guò)是向上移動(dòng)
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) {
//將回收掉的item和listView切斷關(guān)系
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();
}
//這個(gè)方法的作用是讓ListView中所有的子View都按照傳入的參數(shù)值進(jìn)行相應(yīng)的偏移,
//這樣就實(shí)現(xiàn)了隨著手指的拖動(dòng),ListView的內(nèi)容也會(huì)隨著滾動(dòng)的效果。
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;
}
onTouchUp()中主要處理了filing狀態(tài),和狀態(tài)重置
而filing狀態(tài)的主要代碼是在FlingRunnable中我們來(lái)看看FlingRunnable的start()方法.