ListView通过一个Adapter来完成数据和组件的绑定。以ListActivity为例,它集成自Activity,里面包含有一个ListAdapter和一个ListView。绑定的操作通过setListAdapter来完成。本文主要通过源码,来说明,具体的绑定过程究竟是如何进行的,以及convertView(Adapter的getView的第二个参数)缓存实现机制。

如下是ListActivity的代码片段:

    /**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected ListAdapter mAdapter;
/**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected ListView mList;

最关键的绑定动作由下:

    public void setListAdapter(ListAdapter adapter) {
synchronized (this) {
ensureList();
mAdapter = adapter;
mList.setAdapter(adapter);
}
}

其中调用了ListView的setAdapter完成,传入的参数类型为ListAdapter。

    public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
} resetList();
mRecycler.clear();
...

由上述代码,resetList主要是用来重置ListView的headerView和footView的。关键的一个方法是mRecycler.clear(),成员变量mRecycler是从AbsListView继承而来的,它的类型是RecycleBin,从名字看起来好像和“回收”有关。查看源码,有这样一段描述RecycleBin的话:

     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
* storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
* start of a layout. By construction, they are displaying current information. At the end of
* layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
* could potentially be used by the adapter to avoid allocating views unnecessarily.

总而言之,RcycleBin是用来缓存View,以避免不必要的回收View的——设想一下,如果向下滚动ListView后,再回滚到原来位置,如果要重新把View都生成一遍,那要消耗一定的时间。如果缓存起来,对View直接填充数据即可,这也是现在通用的办法。

    class RecycleBin {
private RecyclerListener mRecyclerListener; /**
* The position of the first view stored in mActiveViews.
*/
private int mFirstActivePosition; /**
* Views that were on screen at the start of layout. This array is populated at the start of
* layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
* Views in mActiveViews represent a contiguous range of Views, with position of the first
* view store in mFirstActivePosition.
*/
private View[] mActiveViews = new View[0]; /**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View>[] mScrapViews; private int mViewTypeCount; //代表需要显示的View的类型的个数:ListView中不是所有的View的类型都一样,不过在BaseAdapter里,默认是1 private ArrayList<View> mCurrentScrap; //表示当前类型的scrap的View

  根据解释,mFirstActivePosition是存储在mActiveViews的第一个View的position。什么意思?待会解释。其中mActiveViews是一个View数组,保存的是当前屏幕可见的所有View。不可见的View都“移动到”mScrapViews(scrap的含义是废弃的,因此mScrapViews保存的应该是所有“废弃”的View)——把不可见的View都当做废弃的View保存起来,并没有直接释放,这就是缓存。在这里可以解释一下mFirstActivePosition的含义:比如现在屏幕上显示的是3,4,5三个View,则mFirstActivePosition为3,即第一个处于Active状态的View是第三个。

  保存“废弃”的View的mScrapViews是ArrayList<View>[]类型,即它是一个数组,每个数组保存的是View的链表。不像mActiveViews,mScrapViews是可以动态改变的,结合实际情况,每个屏幕可以显示的View的数量是一定的,但是不可见的View可就太多太多了,所以这符合实际需求。其中“Unsorted views that can be used by the adapter as a convert view”,表明,convertView就是mScrapViews中的某个View。

  在setAdapter里面调用了mRecycler.clear(),下面来看看这个方法:

        /**
* Clears the scrap heap.
*/
void clear() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
}
} else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
}
}
}
}

  从clear()方法中可以看到,如果mViewTypeCount == 1,则只需清楚mCurrentScrap的内容即可;否则按照不同的类型,都统统清除掉。这里的清除是调用ViewGroup的removeDetachedView将View从View树中去掉。

  分析到这里,还是在setListAdapter方法里面,它首先作了清除View的动作。

    public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
} resetList();
mRecycler.clear();
... // AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter); if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount(); //获取Adapter的getCount值,这个值是根据数据源的个数来设定的
checkFocus(); mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver); //注册数据集观察者,待数据源大小有变时,需要更新ListView mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); //默认是1(BaseAdapter里实现)
...
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
} requestLayout(); //布局
}

  分析到这里,View就加载完成了。下面分析数据源有变化时,如何利用NotifyDataSetChanged来更新View。

    public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}

  实际调用的是DataSetObservable的方法notifyChanged(),经过多次调用,最终调用到的是AdapterView的内部类AdapterDataSetObserver的onChanged——重写了DataSetObserver:

    class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;

        @Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount(); //再次获取到View的个数 // 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();
requestLayout(); //布局
}

  在ListView滚动时,只需要通知数据源发生了变化即可自动更新View。在ListView中有一个makeAndAddView方法,该方法根据需要重新生成一个View,或者使用(reuse)缓存起来的View。ListView通过AbsListView的obtainView调用getView——这个getView就是我们需要重载的那个getView。

    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); //判断特定position的View是否存在,如果存在,则选出来。
if (child != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
position, getChildCount());
} // 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
child = obtainView(position, mIsScrap); // This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child;
}

至此,基本上就分析完了。

小结:

  1、可以加深为什么在继承BaseAdapter时,需要重写几个方法那几个方法。

  2、数据源通知更新。

最新文章

  1. github免密码设置
  2. Discuz门户首页关键词和描述显示“首页”的解决方法
  3. ANdroid5.0不能隐式启动service,必须显示,解决办法,加服务端包名
  4. JS弹出窗口代码大全(详细整理)
  5. js返回上一页报网页过期问题解决
  6. Lua游戏脚本语言入门(一)
  7. 64位Python安装PIL
  8. LeetCode_Word Search
  9. MySql优化方案
  10. gulp工作流
  11. SQL Server2008数据库中删除用户,提示数据库主体在该数据库中拥有 架构,无法删除
  12. operator用法:隐式类型转换
  13. Codeforces 519D A and B and Interesting Substrings(二维map+前缀和)
  14. 基于Centos搭建Django 环境搭建
  15. Go学习笔记(一)安装Go语言环境
  16. java图片压缩(Thumbnails)
  17. php使用jquery Form ajax 提交表单,并上传文件
  18. 动态列 Excel 导出
  19. c++计算器后续(4)
  20. JSP、EL表达式、JSTL

热门文章

  1. 概率分析方法与推断统计(来自我写的python书)
  2. Expose Loader &amp; shit jquery
  3. Node.js Learning Paths
  4. bob and brad physical therapy knee exercise
  5. Vue 3.x Composition API
  6. HOC in Depth
  7. Objec.assign &amp; bug
  8. Flutter: 粘贴板
  9. WPF权限控制——【3】数据库、自定义弹窗、表单验证
  10. java: 类 RegisterController 是公共的, 应在名为 RegisterController.java 的文