安卓5.1源码解析 : ListView解析 从绘制,刷新机制到Item的回收机制全面讲解

最近一直在研究关于安卓中常用控件的源码实现,也参考了不少文章,希望通过自己的总结加深一下记忆,我会从一个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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值