天天看點

安卓5.1源碼解析 : ListView解析 從繪制,重新整理機制到Item的回收機制全面講解

最近一直在研究關于 安卓中常用控件的源碼實作,也參考了不少文章,希望通過自己的總結加深一下記憶,我會從一個view的繪制流程去分析這個控件 作為安卓中最常用的控件ListView,我覺很很有必要學習一下Google的大牛是如何實作這種比較複雜的控件,包括ListVIew的繪制流程,ListView的緩存機制,以及封裝思想,對今後自己能早出更好的輪子有所幫助. 注 : 所有的源碼都是來自安卓5.1版本. 本文将從以下角度對安卓中最常用的控件ListView進行分析

  • ListView的構造方法
  • onMeasure
  • onLayout
  • Item的填充
  • Item的布局
  • setAdapter
  • notifyDataSetChanged
  • 以及ListView的回收機制

ListView的構造 我們先從一個類的最開始構造方法開始研究,第二行,ListView在初始化的時候,先執行了 super(context, attrs, defStyleAttr, defStyleRes)方法,ListView的父類是 AbsListView,是以我們先看下父類的初始化究竟做了什麼 publicListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { //初始化AbsListViewsuper(context, attrs, defStyleAttr, defStyleRes); ... }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • ListView 父類 AbsListView的構造

父類方法中調用了 initAbsListView進行ListView的初始化配置,之後就是拿到一些自定義屬性 publicAbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); //初始化設定一些額外屬性 initAbsListView(); ... 拿到自定義屬性省略 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

initAbsListView() 這個方法中給ListView設定了一些初始化狀态 privatevoidinitAbsListView() { // Setting focusable in touch mode will set the focusable property to true//可點選 setClickable(true); //觸摸可擷取焦點 setFocusableInTouchMode(true); //可以繪制 setWillNotDraw(false); //對于透明的地方,顯示最底層的背景 setAlwaysDrawnWithCacheEnabled(false); //設定是否緩存卷動項 setScrollingCacheEnabled(true); final ViewConfiguration configuration = ViewConfiguration.get(mContext); // 事件處理相關變量初始化 mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); mDensityScale = getContext().getResources().getDisplayMetrics().density; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 回到ListView的構造方法

可以看到在初始化狀态之後,通過 a.getDrawable(com. Android .internal.R.styleable.ListView_divider); 拿到了分割線的樣式,這就是是我們通過在style檔案中複 ListView_divider可以自定義Item分割線的原因.而且還可以通過複寫 ListView_overScrollHeader, ListView_overScrollFooter設定頭部和底部的drawble檔案 publicListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { //初始化AbsListViewsuper(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.ListView, defStyleAttr, defStyleRes); CharSequence[] entries = a.getTextArray( com.android.internal.R.styleable.ListView_entries); if (entries != null) { setAdapter(new ArrayAdapter(context, com.android.internal.R.layout.simple_list_item_1, entries)); } //擷取item分割線 drawable 可以自定義final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider); if (d != null) { // If a divider is specified use its intrinsic height for divider height setDivider(d); } //頭部樣式final Drawable osHeader = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollHeader); if (osHeader != null) { setOverscrollHeader(osHeader); } //腳步樣式final Drawable osFooter = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollFooter); if (osFooter != null) { setOverscrollFooter(osFooter); } // Use the height specified, zero being the default// item分割線的高度finalint dividerHeight = a.getDimensionPixelSize( com.android.internal.R.styleable.ListView_dividerHeight, 0); if (dividerHeight != 0) { setDividerHeight(dividerHeight); } mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true); mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true); a.recycle(); }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

最後總結一下,ListView在構造方法中,就是初始化了一些狀态,并且将分割線等樣式添加了進來,這就是我們可以通過在sylte.xml複寫對應的樣式達到修改分割線的原因. onMeasure方法 在onMeasure方法中會根據我們自定義繼承BaseAdapter的 adpter.getCount方法拿到所有item的數量,并且通過 View child = obtainView(0, mIsScrap);方法建立view,那麼這個view是怎麼建立的呢,進去看一下 protectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Sets up mListPadding//設定List 的Paddingsuper.onMeasure(widthMeasureSpec, heightMeasureSpec); ... //step 1 getCount 得到adapter 中的 getCount 這裡傳回的是data 資料的長度 mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); //循環if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)) { //step 2 getView 建立每個viewfinal View child = obtainView(0, mIsScrap); //測量 子view measureScrapChild(child, 0, widthMeasureSpec); //擷取childWidth childHeight childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (recycleOnMeasure() && mRecycler.shouldRecycleViewType( ((LayoutParams) child.getLayoutParams()).viewType)) { mRecycler.addScrapView(child, 0); } } ... 省略 以下是對ListView的測量并指派給成員變量 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • obtainView

可以看到最終也是調用了 mAdapter.getView(position, scrapView, this);建立child,getView中的參數scrapView 就是被回收的view對象,後面會講到 View obtainView(int position, boolean[] isScrap) { ... final View scrapView = mRecycler.getScrapView(position); //擷取到adapter中傳回的convertView;final View child = mAdapter.getView(position, scrapView, this); ... //将getView 中傳回的convertView 傳回return child; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

總結一下.在onMeasure方法中,會通過我們設定進來的mAdpter的getCount方法拿到item的數量,通過getView的方法拿到我們建立的每一個view,當然ListVIew第一次建立的時候并沒有mAdapter的存在,隻有在setAdapter被我們調用過後才會執行這些方法,也就是說在setAdapter中一定會調用 requestLayout方法重新走一遍流程,這個下面會進行講解. onLayout方法 通過搜尋發現ListView中并沒有onLayout方法,那也就是說一定是在他的父類 AbsListView中,我們可以看到它調用了 layoutChildren(),從方法名看應該是對子view進行布局,這個layoutChildren是一個空實作方法,也就是說應該是通過AbsListView的子類 ListVIew和 GridView進行實作 protectedvoidonLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; //拿到view 數量finalint childCount = getChildCount(); if (changed) { for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } // 由子類ListView 和 GridView實作,是核心布局方法代碼,也是listview與adapter互動資料// 的主要入口函數 layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; // TODO: Move somewhere sane. This doesn't belong in onLayout().if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • listView.layoutChildren()

這個方法比較長,我們具體看重點,這個方法中會判斷是否通過adapter進行添加資料的操作,并通過 fillXXX()方法進行對ItemView的填充,并且有兩個很重要的對象: 1. View[] mActiveViews:存放的是目前ListView可以使用的待激活的子item view 2. ArrayList:存放的是在ListView滑動過程中滑出螢幕來回收以便下次利用的子item view @OverrideprotectedvoidlayoutChildren() { ... finalint firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; // 隻有在調用adapter.notifyDatasetChanged()方法一直到layout()布局結束,//dataChanged為true,預設為false,這裡如果調用notifyDatasetChanged,就會将Item添加到ReyclerBin當中,這個//ReyclerBin封裝了這兩個集合用來存放對應的符合條件的item,用來實作複用機制//1.View[] mActiveViews : 存放的是目前ListView可以使用的待激活的子item view//2.ArrayListif (dataChanged) { // dataChanged為true,說明目前listview是有資料的了,把目前所有的item view// 存放到RecycleBin對象的mScrapViews中儲存for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { // dataChanged預設為false,第一次執行此方法走這裡//将view添加到 activeViews[] 中 recycleBin.fillActiveViews(childCount, firstPosition); } ... switch (mLayoutMode) { ... default: //一般情況下走這裡if (childCount == 0) { // 第一次布局的時候,因為還沒有setAdapter,沒有走mAdpate.getCount方法,是以childCount必然為0if (!mStackFromBottom) { finalint position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); // 從上到上布局listview能顯示得下的子view,具體的填充view的方法,下面講到 sel = fillFromTop(childrenTop); } else { finalint position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { // 非第一次layout,也就說執行了nitifyDatasetChanged方法之後if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } elseif (mFirstPosition < mItemCount) { // 通常情況走這裡,fillSpecific()會調用fillUp()和fillDown()布局子view sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } //到這裡,ListView中的view就被填充完畢. ... //布局完成之後記錄狀态 mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

總結一下,通過onLayout方法,就将item填充到了ListView中 Item的填充與Item的布局 我們剛才講到,在layoutChidren中有幾個以fill開頭的方法就是具體的Item的填充方法,

  • fillSpecific()

這個方法中會根據mStackFromBottom參數判斷填充方向,通過 fillUp, fillDown進行填充 private View fillSpecific(int position, int top) { boolean tempIsSelected = position == mSelectedPosition; View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected); // Possibly changed again in fillUp if we add rows above this one. mFirstPosition = position; View above; View below; finalint dividerHeight = mDividerHeight; //根據填充方向,如果mStackFromBottom為false,表示從頂部向底部填充,true反之//mStackFromBottom 可以通過 xml檔案android:stackFromBottom="false"設定,預設為falseif (!mStackFromBottom) { //具體填充方法 above = fillUp(position - 1, temp.getTop() - dividerHeight); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); //具體填充方法 below = fillDown(position + 1, temp.getBottom() + dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); } } else { below = fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); above = fillUp(position - 1, temp.getTop() - dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); } } if (tempIsSelected) { return temp; } elseif (above != null) { return above; } else { return below; } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • fillDown

以 fillDown()舉例 第一次進入nextTop就是padding,也就是最頂部的位置,通過一個while循環,隻要nextTop沒有超出end(ListView内容高度)就一直 makeAndAddView()建立view,nextTop在循環裡會根據Item數量進行循環指派,隻要判斷目前這個item的nextTop超出listView,就停止這個循環,通過這種方法就将可見view都填充出來了 //第一次進來pos = 0;// nexttop 是 padding.topprivate View fillDown(int pos, int nextTop) { View selectedView = null; //listView的高度int end = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { //listView的高度-padding值 end -= mListPadding.bottom; } // while循環在listview範圍内布局可見數量的子item view// nextTop == mListPadding.top,可認為是listview的mPaddingTop// end == mListPadding.bottom,可認為是listview的mPaddingBottom// nextTop < end說明下一個要裝載的item view的getTop()依然可見,那當然要布局到listview中 //這裡未進入循環的時候nextTop == list.paddinttop 預設值while (nextTop < end && pos < mItemCount) { // is this the selected item?//布局目前頁面可以顯示的viewboolean selected = pos == mSelectedPosition; //重點,布局子view的方法 //參數,postion 每個Item的top值 paddingLeft值 是否被選中 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); //這裡nextTop = child的top 加上 他的行高 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • makeAndAddView(int position, int y, boolean flow, int childrenLeft,

boolean selected) listView就是通過這個方法調用 obtainView(position, mIsScrap)mAdapter.getView建立view,然後通過 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);這個方法進行對子view的布局,記住這些方法都在while循環中, //布局目前頁面可顯示的子viewprivate View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child// This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible//擷取到getView的每個view child = obtainView(position, mIsScrap); // This needs to be positioned and measured//布局目前頁面可顯示的子view,這個方法中對view進行布局 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,

boolean selected, boolean recycled) 在這個方法中,通過拿到上面while循環傳經來的參數,調用了子child的measure和layout方法進行測量和繪制,到此listView中可見區域的view就被填充出來了. privatevoidsetupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ... //如果需要測量,先測量子viewif (needToMeasure) { int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, mListPadding.left + mListPadding.right, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } //測量 child.measure(childWidthSpec, childHeightSpec); } else { cleanupLayoutState(child); } ... //對子view進行布局 child.layout(childrenLeft, childTop, childRight, childBottom); ... }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

上面就是ListView中item的填充,下面我們來看看setAdapter中究竟做了什麼操作 setAdapter setAdapter中通過 mAdapter.registerDataSetObserver(mDataSetObserver);注冊一個 AdapterDataSetObserver訂閱者,每當調用 notifyDataSetChange的時候,就會觸發 AdapterDataSetObserver的 onChanged的方法,這個是觀察者模式,不懂得可以參考下其他文章,這裡就不多做贅述,這個方法最終調用requestLayout方法,也就是說我們每次setAdapter之後就會重新布局,這時候mAdapter不為空,就會走剛才所說的繪制流程. @OverridepublicvoidsetAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); //将一些成員變量還原設定為初始預設值 //mLayoutMode = LAYOUT_NORMAL resetList(); // mRecycler的mScrapViews清空并執行listview.removeDetachedView//mScrapViews 存放邊界之外的view mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { // 如果listview有headerView或者FooterView則會生成包裝adapter,生成一個含有HeaderView 和footerView的adapter mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION;//-1 mOldSelectedRowId = INVALID_ROW_ID;//Long.MIN_VALUE// AbsListView#setAdapter will update choice mode states.//給父親 adblistView 設定 adaptersuper.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; //調用adapter的getCount 得到條目個數 mItemCount = mAdapter.getCount(); checkFocus(); //注冊觀察者,這個觀察者每當調用notifyDataSetChange的時候就會觸發 mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); // 設定listview的資料源類型,并在mRecycler中初始化對應個數的scrapViews list mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position; if (mStackFromBottom) { position = lookForSelectablePosition(mItemCount - 1, false); } else { position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position); setNextSelectedPositionInt(position); if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } // 會調用頂層viewRootImpl.performTraversals(),導緻視圖重繪,listview重新整理 requestLayout(); }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

notifyDataSetChanged 這個方法在 BaseAdapter中 public void notifyDataSetChanged() { mDataSetObservable.notifyChanged();} 這時候根據觀察者模式,會調用訂閱者 AdapterDataSetObserver的onChanged方法,上面提到過,最終還是會調用requestLayout進行重新布局

  • onChanged

@OverridepublicvoidonChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); // Detect the case where a cursor that was previously invalidated has// been repopulated with new data.if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } else { rememberSyncState(); } checkFocus(); // 同樣,最終調用viewRootImpl.performTraversals(),導緻視圖重繪,執行listview的 // measure layout 方法等 requestLayout(); }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

ListView的回收機制 最後我們來看看ListView的複用機制 要想了解這方面,先要從ListView滑動開始看,滾動核心方法 AbsListView的 trackMotionScroll,在這個方法中實作了對ListView,Item的緩存

  • trackMotionScroll

第一步 : 這個方法會先判斷我們先在滑動的位置是否已經到最頂部,或者最底部,如果到了邊界值,就不能再滑動了 第二步 : 拿手指向上移動,也就是下滑狀态來說名:先周遊所有的item,如果發現這個item的底部還在可視範圍之内,說明這個item還沒有銷毀,如果超出,則表示需要被緩存起來,也就是會加入到mRecycler的mScrapViews(超出螢幕的集合)中儲存,這個集合之前有說過,專門用來儲存超出螢幕的Item.并将劃出的view通過 detachViewsFromParent從ListView中detach掉 第三步 : 判斷是否有Item滾入了ListView中,如果滾入,調用 fillGap方法進行填充,這個方法中會調用之前說過的 fillDown或者 fillUp方法填充item,并添加到 mActivated(目前螢幕中Item的集合)中,這樣就實作了Item的緩存. //incrementalDeltaY 從上一個事件更改deltaY 即上一個deltaY//deltaY Y軸偏移量boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { finalint childCount = getChildCount(); if (childCount == 0) { returntrue; } .... //判斷是否在最頂部且手指向下滑動,是的話即不能向下滑動了,表示到頂部了//如果第一個item的positon 是 0 并且 第一個item的top==listPadding.topfinalboolean cannotScrollDown = (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0); //判斷是否在最底部且手指向上滑動,是的話即不能向上滑動了,表示到底部了finalboolean cannotScrollUp = (firstPosition + childCount == mItemCount && lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); // listview無法滾動即傳回 if (cannotScrollDown || cannotScrollUp) { return incrementalDeltaY != 0; } // incrementalDeltaY<0說明手指是向上滑動的finalboolean down = incrementalDeltaY < 0; ... 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); //底部大于等于 top 說明 還在目前視圖範圍内if (child.getBottom() >= top) { break; } else { // 最top的子view已經滑出listview,count 就是滑出去的view數 count++; //拿到position 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(); // 将最頂部滑出的子view 加入到mRecycler的mScrapViews中儲存 mRecycler.addScrapView(child, position); } } } } else { ...向下移動 省略 和上面邏輯相同 } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { // 将上面滑出的子view 從listview中detach掉 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 topif (!awakenScrollBars()) { invalidate(); } //核心滾動便宜代碼,根據incrementalDeltaY同步偏移所有的子view offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } // 根據條件判斷是否填充滑動進入listview的子viewfinalint absIncrementalDeltaY = Math.abs(incrementalDeltaY); //if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { //滾動過程判斷需要加載填充滑動進的子view的處理部分 fillGap(down); } if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { finalint childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectedPosition, getChildAt(childIndex)); } } elseif (mSelectorPosition != INVALID_POSITION) { finalint childIndex = mSelectorPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(INVALID_POSITION, getChildAt(childIndex)); } } else { mSelectorRect.setEmpty(); } mBlockLayoutRequests = false; invokeOnItemScrollListener(); returnfalse; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
安卓5.1源碼解析 : ListView解析 從繪制,重新整理機制到Item的回收機制全面講解
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

以上就是對ListView繪制流程以及其中的觀察者模式等等,後面我也會對ReyclerView 以及ViewPager進行源碼分析,希望通過這種方式,更加深刻的了解各種View的實作,對以後自定義控件的編寫提供更好的思想.