一、基本原理

ListView 是一个显示一列可滚动项目的视图组。 系统使用 Adapter 自动将列表项目插入列表,适配器从来源(例如数组或数据库查询)提取内容,并将每个项目结果转换为视图放置到列表中。

cf0a376846fb

列表数据的显示需要 4 个元素:

ListView 视图组;

用来把数据映射到 ListView 上的 Adapter;

需要展示的数据集;

数据展示的 View 模版。

ListView 只负责加载和管理视图,核心实现是由 Adapter 类完成。

在 Adapter 里主要实现这四个函数:

cf0a376846fb

获取数据的个数

cf0a376846fb

获取 position 位置的数据

cf0a376846fb

获取 position 位置的数据 id,一般直接返回 position 即可

cf0a376846fb

获取 position 位置上的每项数据(Item View)视图

在 getView 函数中用户必须构建 Item View,然后将该 position 位置上数据绑定到 Item View。

1. 视图复用机制

Android 采用视图复用的形式避免创建过多的 Item View,提升性能,降低内存占有率。

@Override

public View getView(int position, View convertView, ViewGroup parent) {

View view = null;

//有视图缓存,复用视图

if (convertView != null) {

view = convertView;

} else {

//重新加载视图

}

//进行数据绑定

//返回Item View

return view;

}

position 表示该视图是第几项数据,convertView 表示缓存的 Item View,parent 表示该 Item View 的父视图,对于 ListView 来说这个 parent 就是 ListView 本身。

数据量较大时,ListView 不会构建全部 Item View,ListView 只会构建足够铺满屏幕所需的 Item View 个数,例如 8 个 Item View 足够铺满屏幕,数据项有 1000 个,那么 Item View 可以只产生 8 个,即反复利用容器,变换里面的数据,这样可以节约内存,提高效率。

2. Adapter(适配器)模式

适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。

共有两类适配器模式:

对象适配器模式

在这种适配器模式中,适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。

类适配器模式

这种适配器模式下,适配器继承自已实现的类(一般多重继承)。

cf0a376846fb

ListView 要想使用 getView() 方法,但是不同的数据,不同的需求,就会有不同的 getView() 结果,所以 getView() 必须是可复写的,那么就可以想到用适配器模式。

ListView 里面包含了一个 ListAdapter 的成员变量,实际上是 ListView 继承了 AbsListView,ListAdapter 变量是在 AbsListView 中声明的。

BaseAdapter 是实现了 ListAdapter 的,于是,我们自定义的 xxxAdapter 继承了 BaseAdapter,其实就相当于间接实现了 ListAdapter,然后我们就可以复写 getView() 方法。

ListView 就可以通过调用 setAdapter(ListAdapter adapter) 方法,将自定义的 Adapter 传进去,调用我们复写后的方法,从而得到想要的结果。

3. 观察者模式

Adapter 内部有一个可观察者类,ListView 作为它的其中一个观察者。

ListView 的 setAdapter() 方法:

@Override

public void setAdapter(ListAdapter adapter) {

if (mAdapter != null && mDataSetObserver != null) {

mAdapter.unregisterDataSetObserver(mDataSetObserver);

}

resetList();

//清空视图缓存mRecycler

mRecycler.clear();

if (mAdapter != null) {

......

mDataSetObserver = new AdapterDataSetObserver();

//将 mDataSetObserver 注册到 adapter 中

mAdapter.registerDataSetObserver(mDataSetObserver);

......

} else {

......

}

requestLayout();

}

BaseAdapter 部分代码:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {

private final DataSetObservable mDataSetObservable = new DataSetObservable();

public boolean hasStableIds() {

return false;

}

public void registerDataSetObserver(DataSetObserver observer) {

mDataSetObservable.registerObserver(observer);

}

public void unregisterDataSetObserver(DataSetObserver observer) {

mDataSetObservable.unregisterObserver(observer);

}

注册观测者实际上是调用了 DataSetObservable 对应的函数。DataSetObservable 拥有一个观察者集合,当可观察者发生变更时,就会通知观察者做出相应的处理。

public abstract class Observable {

/**

* The list of observers. An observer can be in the list at most

* once and will never be null.

*/

protected final ArrayList mObservers = new ArrayList();

public void registerObserver(T observer) {

if (observer == null) {

throw new IllegalArgumentException("The observer is null.");

}

synchronized(mObservers) {

mObservers.add(observer);

}

}

Adapter 的数据源发生变化时,我们会调用 Adapter 的

notifyDataSetChanged() 函数,在该函数中又会调用

DataSetObservable 对象的 notifyChanged() 函数通知所有的观察者数据发生了变化,使观察者进行相应的操作。

public class DataSetObservable extends Observable {

public void notifyChanged() {

synchronized(mObservers) {

for (int i = mObservers.size() - 1; i >= 0; i--) {

mObservers.get(i).onChanged();

}

}

}

对于 ListView 来说,这个观察者就是 AdapterDataSetObserver 对象,该类声明在 AdapterView 类中,也是 ListView 中的一个父类。

class AdapterDataSetObserver extends DataSetObserver {

private Parcelable mInstanceState = null;

@Override

public void onChanged() {

mDataChanged = true;

mOldItemCount = mItemCount;

//获取元素个数

mItemCount = getAdapter().getCount();

if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null

&& mOldItemCount == 0 && mItemCount > 0) {

AdapterView.this.onRestoreInstanceState(mInstanceState);

mInstanceState = null;

} else {

rememberSyncState();

}

checkFocus();

//重新布局

requestLayout();

}

onChanged() 函数中,实际调用的是 AdapterView 或者 ViewGroup 类中的属性或者函数完成功能。因此,AdapterDataSetObserver 只是在外层做了一下包装,真正的核心功能是 ListView,确切地说应该是 AdapterView。因此,源码里使用 AdapterDataSetObserver 对象作为观察者并不违反 ListView 作为真正观察者的意图。

二、处理点击事件

可以通过实现 AdapterView.OnItemClickListener 来响应 AdapterView 中每一项上的点击事件。 例如:

// Create a message handling object as an anonymous class.

private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {

public void onItemClick(AdapterView parent, View v, int position, long id) {

// Do something in response to the click

}

};

listView.setOnItemClickListener(mMessageClickedHandler);

三、其他

GridView 与 ListView 相似, 同样继承 AbsListView,AbsListView 又是 AdapterView 的子类,所以原理机制基本相同,不同的是,GridView 通过网格布局形式展示。

同一个 Adapter 可以设置给多个的列表视图,数据会同时显示且根据数据变化而变化,这些列表视图都作为该 Adapter 的观察者,这可能也是为什么要使用观察者模式的一个原因。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐