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

来源:互联网 发布:淘宝抢红包软件真假 编辑:程序博客网 时间:2024/05/16 09:39

最近一直在研究关于安卓中常用控件的源码实现,也参考了不少文章,希望通过自己的总结加深一下记忆,我会从一个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,所以我们先看下父类的初始化究竟做了什么

    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