天天看点

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

最近一直在研究关于 安卓中常用控件的源码实现,也参考了不少文章,希望通过自己的总结加深一下记忆,我会从一个view的绘制流程去分析这个控件 作为安卓中最常用的控件ListView,我觉很很有必要学习一下Google的大牛是如何实现这种比较复杂的控件,包括ListVIew的绘制流程,ListView的缓存机制,以及封装思想,对今后自己能早出更好的轮子有所帮助. 注 : 所有的源码都是来自安卓5.1版本. 本文将从以下角度对安卓中最常用的控件ListView进行分析

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

ListView的构造 我们先从一个类的最开始构造方法开始研究,第二行,ListView在初始化的时候,先执行了 super(context, attrs, defStyleAttr, defStyleRes)方法,ListView的父类是 AbsListView,所以我们先看下父类的初始化究竟做了什么 publicListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { //初始化AbsListViewsuper(context, attrs, defStyleAttr, defStyleRes); ... }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • ListView 父类 AbsListView的构造

父类方法中调用了 initAbsListView进行ListView的初始化配置,之后就是拿到一些自定义属性 publicAbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); //初始化设置一些额外属性 initAbsListView(); ... 拿到自定义属性省略 }

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

initAbsListView() 这个方法中给ListView设置了一些初始化状态 privatevoidinitAbsListView() { // Setting focusable in touch mode will set the focusable property to true//可点击 setClickable(true); //触摸可获取焦点 setFocusableInTouchMode(true); //可以绘制 setWillNotDraw(false); //对于透明的地方,显示最底层的背景 setAlwaysDrawnWithCacheEnabled(false); //设置是否缓存卷动项 setScrollingCacheEnabled(true); final ViewConfiguration configuration = ViewConfiguration.get(mContext); // 事件处理相关变量初始化 mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); mDensityScale = getContext().getResources().getDisplayMetrics().density; }

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

可以看到在初始化状态之后,通过 a.getDrawable(com. Android .internal.R.styleable.ListView_divider); 拿到了分割线的样式,这就是是我们通过在style文件中复 ListView_divider可以自定义Item分割线的原因.而且还可以通过复写 ListView_overScrollHeader, ListView_overScrollFooter设置头部和底部的drawble文件 publicListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { //初始化AbsListViewsuper(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.ListView, defStyleAttr, defStyleRes); CharSequence[] entries = a.getTextArray( com.android.internal.R.styleable.ListView_entries); if (entries != null) { setAdapter(new ArrayAdapter(context, com.android.internal.R.layout.simple_list_item_1, entries)); } //获取item分割线 drawable 可以自定义final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider); if (d != null) { // If a divider is specified use its intrinsic height for divider height setDivider(d); } //头部样式final Drawable osHeader = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollHeader); if (osHeader != null) { setOverscrollHeader(osHeader); } //脚步样式final Drawable osFooter = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollFooter); if (osFooter != null) { setOverscrollFooter(osFooter); } // Use the height specified, zero being the default// item分割线的高度finalint dividerHeight = a.getDimensionPixelSize( com.android.internal.R.styleable.ListView_dividerHeight, 0); if (dividerHeight != 0) { setDividerHeight(dividerHeight); } mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true); mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true); a.recycle(); }

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

最后总结一下,ListView在构造方法中,就是初始化了一些状态,并且将分割线等样式添加了进来,这就是我们可以通过在sylte.xml复写对应的样式达到修改分割线的原因. onMeasure方法 在onMeasure方法中会根据我们自定义继承BaseAdapter的 adpter.getCount方法拿到所有item的数量,并且通过 View child = obtainView(0, mIsScrap);方法创建view,那么这个view是怎么创建的呢,进去看一下 protectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Sets up mListPadding//设置List 的Paddingsuper.onMeasure(widthMeasureSpec, heightMeasureSpec); ... //step 1 getCount 得到adapter 中的 getCount 这里返回的是data 数据的长度 mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); //循环if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)) { //step 2 getView 创建每个viewfinal View child = obtainView(0, mIsScrap); //测量 子view measureScrapChild(child, 0, widthMeasureSpec); //获取childWidth childHeight childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (recycleOnMeasure() && mRecycler.shouldRecycleViewType( ((LayoutParams) child.getLayoutParams()).viewType)) { mRecycler.addScrapView(child, 0); } } ... 省略 以下是对ListView的测量并赋值给成员变量 }

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

可以看到最终也是调用了 mAdapter.getView(position, scrapView, this);创建child,getView中的参数scrapView 就是被回收的view对象,后面会讲到 View obtainView(int position, boolean[] isScrap) { ... final View scrapView = mRecycler.getScrapView(position); //获取到adapter中返回的convertView;final View child = mAdapter.getView(position, scrapView, this); ... //将getView 中返回的convertView 返回return child; }

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

总结一下.在onMeasure方法中,会通过我们设置进来的mAdpter的getCount方法拿到item的数量,通过getView的方法拿到我们创建的每一个view,当然ListVIew第一次创建的时候并没有mAdapter的存在,只有在setAdapter被我们调用过后才会执行这些方法,也就是说在setAdapter中一定会调用 requestLayout方法重新走一遍流程,这个下面会进行讲解. onLayout方法 通过搜索发现ListView中并没有onLayout方法,那也就是说一定是在他的父类 AbsListView中,我们可以看到它调用了 layoutChildren(),从方法名看应该是对子view进行布局,这个layoutChildren是一个空实现方法,也就是说应该是通过AbsListView的子类 ListVIew和 GridView进行实现 protectedvoidonLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; //拿到view 数量finalint childCount = getChildCount(); if (changed) { for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } // 由子类ListView 和 GridView实现,是核心布局方法代码,也是listview与adapter交互数据// 的主要入口函数 layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; // TODO: Move somewhere sane. This doesn't belong in onLayout().if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } }

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

这个方法比较长,我们具体看重点,这个方法中会判断是否通过adapter进行添加数据的操作,并通过 fillXXX()方法进行对ItemView的填充,并且有两个很重要的对象: 1. View[] mActiveViews:存放的是当前ListView可以使用的待激活的子item view 2. ArrayList:存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view @OverrideprotectedvoidlayoutChildren() { ... finalint firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; // 只有在调用adapter.notifyDatasetChanged()方法一直到layout()布局结束,//dataChanged为true,默认为false,这里如果调用notifyDatasetChanged,就会将Item添加到ReyclerBin当中,这个//ReyclerBin封装了这两个集合用来存放对应的符合条件的item,用来实现复用机制//1.View[] mActiveViews : 存放的是当前ListView可以使用的待激活的子item view//2.ArrayListif (dataChanged) { // dataChanged为true,说明当前listview是有数据的了,把当前所有的item view// 存放到RecycleBin对象的mScrapViews中保存for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { // dataChanged默认为false,第一次执行此方法走这里//将view添加到 activeViews[] 中 recycleBin.fillActiveViews(childCount, firstPosition); } ... switch (mLayoutMode) { ... default: //一般情况下走这里if (childCount == 0) { // 第一次布局的时候,因为还没有setAdapter,没有走mAdpate.getCount方法,所以childCount必然为0if (!mStackFromBottom) { finalint position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); // 从上到上布局listview能显示得下的子view,具体的填充view的方法,下面讲到 sel = fillFromTop(childrenTop); } else { finalint position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { // 非第一次layout,也就说执行了nitifyDatasetChanged方法之后if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } elseif (mFirstPosition < mItemCount) { // 通常情况走这里,fillSpecific()会调用fillUp()和fillDown()布局子view sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } //到这里,ListView中的view就被填充完毕. ... //布局完成之后记录状态 mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; }

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

总结一下,通过onLayout方法,就将item填充到了ListView中 Item的填充与Item的布局 我们刚才讲到,在layoutChidren中有几个以fill开头的方法就是具体的Item的填充方法,

  • fillSpecific()

这个方法中会根据mStackFromBottom参数判断填充方向,通过 fillUp, fillDown进行填充 private View fillSpecific(int position, int top) { boolean tempIsSelected = position == mSelectedPosition; View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected); // Possibly changed again in fillUp if we add rows above this one. mFirstPosition = position; View above; View below; finalint dividerHeight = mDividerHeight; //根据填充方向,如果mStackFromBottom为false,表示从顶部向底部填充,true反之//mStackFromBottom 可以通过 xml文件android:stackFromBottom="false"设置,默认为falseif (!mStackFromBottom) { //具体填充方法 above = fillUp(position - 1, temp.getTop() - dividerHeight); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); //具体填充方法 below = fillDown(position + 1, temp.getBottom() + dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); } } else { below = fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); above = fillUp(position - 1, temp.getTop() - dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); } } if (tempIsSelected) { return temp; } elseif (above != null) { return above; } else { return below; } }

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

以 fillDown()举例 第一次进入nextTop就是padding,也就是最顶部的位置,通过一个while循环,只要nextTop没有超出end(ListView内容高度)就一直 makeAndAddView()创建view,nextTop在循环里会根据Item数量进行循环赋值,只要判断当前这个item的nextTop超出listView,就停止这个循环,通过这种方法就将可见view都填充出来了 //第一次进来pos = 0;// nexttop 是 padding.topprivate View fillDown(int pos, int nextTop) { View selectedView = null; //listView的高度int end = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { //listView的高度-padding值 end -= mListPadding.bottom; } // while循环在listview范围内布局可见数量的子item view// nextTop == mListPadding.top,可认为是listview的mPaddingTop// end == mListPadding.bottom,可认为是listview的mPaddingBottom// nextTop < end说明下一个要装载的item view的getTop()依然可见,那当然要布局到listview中 //这里未进入循环的时候nextTop == list.paddinttop 默认值while (nextTop < end && pos < mItemCount) { // is this the selected item?//布局当前页面可以显示的viewboolean selected = pos == mSelectedPosition; //重点,布局子view的方法 //参数,postion 每个Item的top值 paddingLeft值 是否被选中 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); //这里nextTop = child的top 加上 他的行高 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }

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

boolean selected) listView就是通过这个方法调用 obtainView(position, mIsScrap)mAdapter.getView创建view,然后通过 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);这个方法进行对子view的布局,记住这些方法都在while循环中, //布局当前页面可显示的子viewprivate View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child// This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible//获取到getView的每个view child = obtainView(position, mIsScrap); // This needs to be positioned and measured//布局当前页面可显示的子view,这个方法中对view进行布局 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }

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

boolean selected, boolean recycled) 在这个方法中,通过拿到上面while循环传经来的参数,调用了子child的measure和layout方法进行测量和绘制,到此listView中可见区域的view就被填充出来了. privatevoidsetupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ... //如果需要测量,先测量子viewif (needToMeasure) { int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, mListPadding.left + mListPadding.right, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } //测量 child.measure(childWidthSpec, childHeightSpec); } else { cleanupLayoutState(child); } ... //对子view进行布局 child.layout(childrenLeft, childTop, childRight, childBottom); ... }

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

上面就是ListView中item的填充,下面我们来看看setAdapter中究竟做了什么操作 setAdapter setAdapter中通过 mAdapter.registerDataSetObserver(mDataSetObserver);注册一个 AdapterDataSetObserver订阅者,每当调用 notifyDataSetChange的时候,就会触发 AdapterDataSetObserver的 onChanged的方法,这个是观察者模式,不懂得可以参考下其他文章,这里就不多做赘述,这个方法最终调用requestLayout方法,也就是说我们每次setAdapter之后就会重新布局,这时候mAdapter不为空,就会走刚才所说的绘制流程. @OverridepublicvoidsetAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); //将一些成员变量还原设置为初始默认值 //mLayoutMode = LAYOUT_NORMAL resetList(); // mRecycler的mScrapViews清空并执行listview.removeDetachedView//mScrapViews 存放边界之外的view mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { // 如果listview有headerView或者FooterView则会生成包装adapter,生成一个含有HeaderView 和footerView的adapter mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION;//-1 mOldSelectedRowId = INVALID_ROW_ID;//Long.MIN_VALUE// AbsListView#setAdapter will update choice mode states.//给父亲 adblistView 设置 adaptersuper.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; //调用adapter的getCount 得到条目个数 mItemCount = mAdapter.getCount(); checkFocus(); //注册观察者,这个观察者每当调用notifyDataSetChange的时候就会触发 mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); // 设置listview的数据源类型,并在mRecycler中初始化对应个数的scrapViews list mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position; if (mStackFromBottom) { position = lookForSelectablePosition(mItemCount - 1, false); } else { position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position); setNextSelectedPositionInt(position); if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } // 会调用顶层viewRootImpl.performTraversals(),导致视图重绘,listview刷新 requestLayout(); }

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

notifyDataSetChanged 这个方法在 BaseAdapter中 public void notifyDataSetChanged() { mDataSetObservable.notifyChanged();} 这时候根据观察者模式,会调用订阅者 AdapterDataSetObserver的onChanged方法,上面提到过,最终还是会调用requestLayout进行重新布局

  • onChanged

@OverridepublicvoidonChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); // Detect the case where a cursor that was previously invalidated has// been repopulated with new data.if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } else { rememberSyncState(); } checkFocus(); // 同样,最终调用viewRootImpl.performTraversals(),导致视图重绘,执行listview的 // measure layout 方法等 requestLayout(); }

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

ListView的回收机制 最后我们来看看ListView的复用机制 要想了解这方面,先要从ListView滑动开始看,滚动核心方法 AbsListView的 trackMotionScroll,在这个方法中实现了对ListView,Item的缓存

  • trackMotionScroll

第一步 : 这个方法会先判断我们先在滑动的位置是否已经到最顶部,或者最底部,如果到了边界值,就不能再滑动了 第二步 : 拿手指向上移动,也就是下滑状态来说名:先遍历所有的item,如果发现这个item的底部还在可视范围之内,说明这个item还没有销毁,如果超出,则表示需要被缓存起来,也就是会加入到mRecycler的mScrapViews(超出屏幕的集合)中保存,这个集合之前有说过,专门用来保存超出屏幕的Item.并将划出的view通过 detachViewsFromParent从ListView中detach掉 第三步 : 判断是否有Item滚入了ListView中,如果滚入,调用 fillGap方法进行填充,这个方法中会调用之前说过的 fillDown或者 fillUp方法填充item,并添加到 mActivated(当前屏幕中Item的集合)中,这样就实现了Item的缓存. //incrementalDeltaY 从上一个事件更改deltaY 即上一个deltaY//deltaY Y轴偏移量boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { finalint childCount = getChildCount(); if (childCount == 0) { returntrue; } .... //判断是否在最顶部且手指向下滑动,是的话即不能向下滑动了,表示到顶部了//如果第一个item的positon 是 0 并且 第一个item的top==listPadding.topfinalboolean cannotScrollDown = (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0); //判断是否在最底部且手指向上滑动,是的话即不能向上滑动了,表示到底部了finalboolean cannotScrollUp = (firstPosition + childCount == mItemCount && lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); // listview无法滚动即返回 if (cannotScrollDown || cannotScrollUp) { return incrementalDeltaY != 0; } // incrementalDeltaY<0说明手指是向上滑动的finalboolean down = incrementalDeltaY < 0; ... int start = 0; int count = 0; //手指向上移动if (down) { int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); //底部大于等于 top 说明 还在当前视图范围内if (child.getBottom() >= top) { break; } else { // 最top的子view已经滑出listview,count 就是滑出去的view数 count++; //拿到position int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any// system-managed transient state. child.clearAccessibilityFocus(); // 将最顶部滑出的子view 加入到mRecycler的mScrapViews中保存 mRecycler.addScrapView(child, position); } } } } else { ...向下移动 省略 和上面逻辑相同 } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { // 将上面滑出的子view 从listview中detach掉 detachViewsFromParent(start, count); mRecycler.removeSkippedScrap(); } // invalidate before moving the children to avoid unnecessary invalidate// calls to bubble up from the children all the way to the topif (!awakenScrollBars()) { invalidate(); } //核心滚动便宜代码,根据incrementalDeltaY同步偏移所有的子view offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } // 根据条件判断是否填充滑动进入listview的子viewfinalint absIncrementalDeltaY = Math.abs(incrementalDeltaY); //if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { //滚动过程判断需要加载填充滑动进的子view的处理部分 fillGap(down); } if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { finalint childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectedPosition, getChildAt(childIndex)); } } elseif (mSelectorPosition != INVALID_POSITION) { finalint childIndex = mSelectorPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(INVALID_POSITION, getChildAt(childIndex)); } } else { mSelectorRect.setEmpty(); } mBlockLayoutRequests = false; invokeOnItemScrollListener(); returnfalse; }

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

以上就是对ListView绘制流程以及其中的观察者模式等等,后面我也会对ReyclerView 以及ViewPager进行源码分析,希望通过这种方式,更加深刻的了解各种View的实现,对以后自定义控件的编写提供更好的思想.