ListView 作为 Android 开发中最常用的组件之一,最近刚好有时间,看了源码,记录一下。
ListView 的继承结构
一. 需要解决的问题
. ListView 本质上也是一个 ViewGroup, 它的 onMeasure(), onLayout() 过程, onDraw() 过程?
. 它是如何配合 Adaper 实现数据的展示的?
. ListView 是如何构造 ItemView 的循环使用,而不会造成 OOM 的?
二. 重要的类
1. RecycleBin
RecycleBin 是分析 ListView 重要的类,它是 AbsListView 的一个内部类,它的几个方法,承担着 ListView 对 ItemView 循环使用的功能。
主要的成员变量:
. View[] mActiveViews; 以数据的形式存储在屏幕上展示的 View.
. ArrayList
主要的方法:
. 填充 ActiveView
1 | void fillActiveViews(int childCount, int firstActivePosition) { |
. 根据指定的位置,从 mActiveViews 中获取相应的 View。 如果找到了,返回,并同时从 mActiveViews 中移除。
1 | View getActiveView(int position) { |
.根据指定的位置,从 mScrapViews 中获取相应的 scropView
1 | View getScrapView(int position) { |
. 把 View 放入回收的 mScrapViews 中缓存起来
1 | void addScrapView(View scrap, int position) { |
2. HeaderViewListAdapter
有 header view 时,进行包装的 ListAdapter.
3. DataSetObservable
通知数据产生了变化,(这里使用了观察者模式,待分析)
三. OnLayout 过程
是 AbsListView#onLayout(…) 方法
调用 layoutChildren(), 抽象方法,由子类实现
ListView#layoutChildren()
1 | ListView#layoutChildren() { |
dataChanged 由 mDataChanged 赋值。mDataChanged 是 AdapterView 的成员变量,初始值是 false, 在 Adapter#notifyDataSetChanged() 是才会变成 true, 这个在后面有介绍。当第一次 Layout 的时候时,ListView 还没有 ChildView, 所以 childCount 是 0.
由于 childCount 为 0, recycleBin.fillActiveViews 里面不起作用,跳出循环了。
mStackFromBottom 是否从底部开始填充。 fillFromTop 是从 ListView 的顶部开始填充,fillUp 是从 ListView 的底部开始填充。
它们只是方向不同而已。
fillFromTop 又会调动 fillDown(), fillDown 方法里面会循环,直到所有 child 的高度高于平面的高度,说明已经填满屏幕了,则跳出循环。
1 | private View fillDown(int pos, int nextTop) { |
makaAndAaddView 获取 View, 并将它存放入 child 的 list 中
1 | /** |
当第一次 layout 时, mRecycler.getActiveView 返回的是空的。
最终会是调用 obtainView 方法。obtainView 方法是在 ListView 的父类 AbsListView 的方法
AbsListView#obtainView(…)
1 | /** |
会调用从 RecyclerBin#getScrapView(…) 尝试从缓存中取出废弃的 View, 但是第一次 layout 的时候,返回来是空的。
重点是 Adapter.getView(…) 方法,当我们继承 BaseAdapter 的时候,要重写它的 getView 方法。
下面我们看看例子
1 | public View getView(int position, View convertView, ViewGroup parent) { |
我们看到 converView 就是从缓存中取出的 scrapView, 当 scrapView 是空的时候,所以我们要创建一个 View。
另外参数 ViewGroup parent, 其实就是 ListView.
我们回到 makeAndAddView 方法中, 在 obtainView 中获取 child, 然后调用 setupChild 方法,将 child 添加进 ListView 中。
ListView#setupChild(…)
1 | private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, |
我们可以看到最终是通过 attachViewToParent 或者 addViewInLayout 方法,将 child 添加进 ListView 中。
当第二次 layout 的时候,就是调用 RecyclerBin 的一些方法时有一些改变,但是整个流程的变化不大。
四. draw 绘制过程
ListView 的绘制在 ListView#dispatchDraw(…) 中。这个方法比较简单,就是绘制分割线和分发 childView 自己的绘制而已。
五. 滑动的过程
滑动开始是 AbsListView#onTouchEvent(…) 然后会调用 AbsListView#onTouchMove()
1 | private void onTouchMove(MotionEvent ev, MotionEvent vtev) { |
AbsListView#scrollIfNeeded(…)
1 | private void scrollIfNeeded(int x, int y, MotionEvent vtev) { |
scrllIfNeeded(…) 方法又会调用 trackMotionScroll 方法
AbsListView#trackMotionScroll
1 | boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { |
在 trackMotionScroll 方法里面,前段,我们看到,无论是向上滑,还是向下滑,只要超出屏幕外,就会调用 RecyclerBin#addScrapView(…),进行回收。同时也会调用 fillGap 进行填充。
fillGap 是抽象方法,由子类实现
ListView#fillGap(…)
1 | void fillGap(boolean down) { |
在 fillGrap 里面调用了 fillDown 或者 fillUp, 对 ListView 填充 childView, 这样滑动的时候,形成循环,回到前面的分析。
六. 数据变化
当数据变化的时候,我们会调用 BaseAdapter#notifyDataSetChanged(), 让 ListView 的布局也跟着更新。
Adapter 更新数据是个典型的观察者模式。
那我们看看 Adapter 中的观察者模式
从上图我们可以知道 DataSetObserver 是抽象的观察者,AdapterView$AdapterDataSetObserver 是具体的观察者。Observale
当我们调用 BaseAdapter#notifyDataSetChanged()时,就会调动 DataSetObservable#notifyChanged(), 在这个方面里面通过 for 循环通知所有的观察者,最终调动 AdapterView$AdapterDataSetObserver#onChanged() 方法。
在 AdapterView$AdapterDataSetObserver#onChanged() 方法中,会调动 reuestLayout 要求重新布局。布局的过程见上面的分析。
七. 参考资料
- Android ListView工作原理完全解析,带你从源码的角度彻底理解http://blog.csdn.net/guolin_blog/article/details/44996879