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、数据源通知更新。

ListView解析的更多相关文章

  1. 20150226—C# winform中的ListView解析

    ListView在WinForm中多用于表的构建,可以直观的显示表的信息,其格式如同SQL的表 这是他的位置,在公共控件中: Listview的几个重要属性:Columms(集合).Groups(集合 ...

  2. S1293和S2220KTV项目结束

    1.界面原型(前台的界面搭建一下) 2.数据库 3.架构设计 4.约定的文件抽取 2015年7月20日下午 歌星点歌三界面的联动,数据动态加载 01.点击第一个LIstView,弹出第二个ListVi ...

  3. S1的小成果:MyKTV系统

    转眼之间,已经到了2016年,即新的一年了!S1也结束了,收获的也不多 ,想想最后留给大家的就一个KTV项目了. 希望大家看时有所收获           现在我们一起来看KTV前台管理 主界面的运行 ...

  4. 织梦后台如何生成站点地图sitemap.xml

    第一步在网站根目录建立sitemap.php文件 内容如下: 写一个计划任务文件命名为generate_sitemap.php,放在/plus/task目录里,文件内容如下: <?php//定时 ...

  5. 解析ListView联动的实现--仿饿了么点餐界面

    一.博客的由来 大神王丰蛋哥 之前一篇博客仿饿了点餐界面2个ListView联动(http://www.cnblogs.com/wangfengdange/p/5886064.html) 主要实现了2 ...

  6. 安卓解析json,使用BaseAdapter添加至ListView中,中间存储用JavaBean来实现

    这是一个小练习,要求解析一个提供的json文件.并将其中的id,title值获取,以ListView形式展示出来.(开发工具是android studio) 下面开始: 首先我想到的是先把json文件 ...

  7. Json文件放入Assets文件,读取解析并且放入listview中显示。

    package com.lixu.TestJson; import android.app.Activity; import android.content.Context; import andro ...

  8. 一步步教你为网站开发Android客户端---HttpWatch抓包,HttpClient模拟POST请求,Jsoup解析HTML代码,动态更新ListView

    本文面向Android初级开发者,有一定的Java和Android知识即可. 文章覆盖知识点:HttpWatch抓包,HttpClient模拟POST请求,Jsoup解析HTML代码,动态更新List ...

  9. json解析,异步下载(listview仅滑动时加载)Demo总结

    异步加载的练习demo 主要涉及知识点: 1.解析json格式数据,主要包括图片,文本 2.使用AsynTask异步方式从网络下载图片 3.BaseAdapter的"优雅"使用 4 ...

随机推荐

  1. Linux添加系统调用

    Linux添加系统调用 1 概述 通常添加系统调用有两种方案: * 重新编译内核 * 添加内核模块 此处我们采用重新编译内核的方式增加系统调用. 实验环境:X86_64 GNU/Linux 4.15. ...

  2. woj1019 Curriculum Schedule 输入输出 woj1020 Adjacent Difference 排序

    title: woj1019 Curriculum Schedule 输入输出 date: 2020-03-19 10:43:00 categories: acm tags: [acm,woj] 水题 ...

  3. hautoj 1268 小天使改名

    1268: 小天使改名 时间限制: 2 秒  内存限制: 128 MB提交: 437  解决: 123提交 状态 题目描述 小天使的b站帐号被大家发现啦.于是小天使决定改名,将他原有ID中的两个不同位 ...

  4. js array contains All In One

    js array contains All In One includes & contains & has Array.prototype.contains "use st ...

  5. how to tell a function arguments length in js

    how to tell a function arguments length in js JavaScript函数不对参数值(参数)执行任何检查 https://www.w3schools.com/ ...

  6. background & background-image & border-image

    background & background-image & border-image https://developer.mozilla.org/en-US/docs/Web/CS ...

  7. Flutter 获取本地图片并剪切

    安装依赖 dependencies: ... image_picker: image_cropper android\app\src\main\AndroidManifest.xml 将UCropAc ...

  8. 13_MySQL如何去除结果集中的重复记录

    本节所涉及的sql语句 -- 去除结果集中的重复记录 SELECT job FROM t_emp; SELECT DISTINCT job FROM t_emp; SELECT DISTINCT jo ...

  9. 无法将“node.exe”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

    有些天没有启动前端项目,发现npm run dev,启动不了,经过一番查找发现问题所在 然后我查看了一下报错位置,发现并没有改动过什么 解决方法: 方法一: 检查一下npm目录: 这里发现少了node ...

  10. 高级FTP

      一.作业需求 1. 用户加密认证(已完成) 2. 多用户同时登陆(已完成) 3. 每个用户有自己的家目录且只能访问自己的家目录(已完成) 4. 对用户进行磁盘配额.不同用户配额可不同(已完成) 5 ...