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

yangxi_001發表於2017-08-01

最近一直在研究關於安卓中常用控制元件的原始碼實現,也參考了不少文章,希望通過自己的總結加深一下記憶,我會從一個view的繪製流程去分析這個控制元件

作為安卓中最常用的控制元件ListView,我覺很很有必要學習一下Google的大牛是如何實現這種比較複雜的控制元件,包括ListVIew的繪製流程,ListView的快取機制,以及封裝思想,對今後自己能早出更好的輪子有所幫助.

注 : 所有的原始碼都是來自安卓5.1版本.

本文將從以下角度對安卓中最常用的控制元件ListView進行分析

ListView的構造

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

    public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        //初始化AbsListView
        super(context, attrs, defStyleAttr, defStyleRes);

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

父類方法中呼叫了initAbsListView進行ListView的初始化配置,之後就是拿到一些自定義屬性

    public AbsListView(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設定了一些初始化狀態

    private void initAbsListView() {
        // 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檔案

    public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        //初始化AbsListView
        super(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<CharSequence>(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分割線的高度
        final int 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是怎麼建立的呢,進去看一下

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        //設定List 的Padding
        super.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 建立每個view
            final 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的子類ListVIewGridView進行實現

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        mInLayout = true;
        //拿到view 數量
        final int 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<View>[] mScrapViews:存放的是在ListView滑動過程中滑出螢幕來回收以便下次利用的子item view

         @Override
    protected void layoutChildren() {
        ...

        final int firstPosition = mFirstPosition;
        final RecycleBin recycleBin = mRecycler;
        // 只有在呼叫adapter.notifyDatasetChanged()方法一直到layout()佈局結束,
        //dataChanged為true,預設為false,這裡如果呼叫notifyDatasetChanged,就會將Item新增到ReyclerBin當中,這個
        //ReyclerBin封裝了這兩個集合用來存放對應的符合條件的item,用來實現複用機制
        //1.View[] mActiveViews : 存放的是當前ListView可以使用的待啟用的子item view
        //2.ArrayList<View>[] mScrapViews : 存放的是在ListView滑動過程中滑出螢幕來回收以便下次利用的子item view
        if (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必然為0
                    if (!mStackFromBottom) {
                        final int position = lookForSelectablePosition(0, true);
                        setSelectedPositionInt(position);
                        // 從上到上佈局listview能顯示得下的子view,具體的填充view的方法,下面講到
                        sel = fillFromTop(childrenTop);
                    } else {
                        final int 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());
                    } else if (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;

        final int dividerHeight = mDividerHeight;
        //根據填充方向,如果mStackFromBottom為false,表示從頂部向底部填充,true反之
        //mStackFromBottom 可以通過 xml檔案android:stackFromBottom="false"設定,預設為false
        if (!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;
        } else if (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.top
    private 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?
            //佈局當前頁面可以顯示的view
            boolean 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迴圈中,

    //佈局當前頁面可顯示的子view
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned

                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        //獲取到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就被填充出來了.

    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean recycled) {
        ...
        //如果需要測量,先測量子view
        if (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的時候,就會觸發AdapterDataSetObserveronChanged的方法,這個是觀察者模式,不懂得可以參考下其他文章,這裡就不多做贅述,這個方法最終呼叫requestLayout方法,也就是說我們每次setAdapter之後就會重新佈局,這時候mAdapter不為空,就會走剛才所說的繪製流程.

    @Override
    public void setAdapter(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 設定 adapter
        super.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
    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

        // Detect the case where a cursor that was previously invalidated has
        // been repopulated with new data.
        if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                && mOldItemCount == 0 && mItemCount > 0) {
            AdapterView.this.onRestoreInstanceState(mInstanceState);
            mInstanceState = null;
        } else {
            rememberSyncState();
        }
        checkFocus();
        // 同樣,最終呼叫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滑動開始看,滾動核心方法AbsListViewtrackMotionScroll,在這個方法中實現了對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) {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return true;
        }
        ....

        //判斷是否在最頂部且手指向下滑動,是的話即不能向下滑動了,表示到頂部了
        //如果第一個item的positon 是 0 並且 第一個item的top==listPadding.top
        final boolean cannotScrollDown = (firstPosition == 0 &&
                firstTop >= listPadding.top && incrementalDeltaY >= 0);
        //判斷是否在最底部且手指向上滑動,是的話即不能向上滑動了,表示到底部了
        final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
                lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);

        // listview無法滾動即返回    
        if (cannotScrollDown || cannotScrollUp) {
            return incrementalDeltaY != 0;
        }
        // incrementalDeltaY<0說明手指是向上滑動的
        final boolean 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 top
        if (!awakenScrollBars()) {
           invalidate();
        }
        //核心滾動便宜程式碼,根據incrementalDeltaY同步偏移所有的子view
        offsetChildrenTopAndBottom(incrementalDeltaY);

        if (down) {
            mFirstPosition += count;
        }
        // 根據條件判斷是否填充滑動進入listview的子view
        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
        //
        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            //滾動過程判斷需要載入填充滑動進的子view的處理部分
            fillGap(down);
        }

        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;
    }
  • 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
  • 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的實現,對以後自定義控制元件的編寫提供更好的思想.

轉自:http://blog.csdn.net/hfyd_/article/details/53768690?_t_t_t=0.8988156646955758

相關文章