RecyclerView 得宠之前,ListView 可以说是我们用的最多的组件。之前一直没有好好看看它的源码,知其然不知其所以然。

今天我们来窥一窥 ListView 中的观察者模式。

不熟悉观察者模式的可以看看这篇 观察者模式 : 一支穿云箭,千军万马来相见 巩固一下。

在我们使用 ListView 的过程中,经常需要修改 Item 的状态,比如添加、删除、选中等等,通常的操作是在对数据源进行操作后,调用 notifyDataSetChanged() ,比如:

    public void addData(String data) {
if (mData != null) {
mData.add(data);
notifyDataSetChanged();
}
}

随后 ListView 中的数据就会更新,我们可以猜到这个过程是把全部 Item View 重新绘制、数据绑定了一遍,这个场景跟观察者模式很一致,具体怎么实现的呢

前方高能预警,代码太多看不下去的可以先翻到篇尾看看流程图,有点印象再回来继续啃的,不然容易晕。

1.首先我们跟进去看下 notifyDataSetChanged() 源码,进入了系统的 BaseAdapter

    /**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}

看注释,“通知观察者数据已经改变,任何和数据集绑定的 View 都应该刷新”,的确是观察者模式。

那发布者、观察者是谁?在什么时候注册的?观察者的 notifyChanged() 方法又做了什么呢?

2.在 BaseAdapter 中我们可以看到这几个方法:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable(); public boolean hasStableIds() {
return false;
} /**
* BaseAdapter 提供了 注册订阅方法
*/
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
} /**
* 还提供了 解除订阅方法
*/
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
} /**
* 数据更新时通知观察者
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
} /**
* 提醒观察者散了,别看了,数据不可用了
* /
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
//省略无关代码
}

BaseAdapter 提供了 注册订阅、解除订阅、提醒观察者数据更新、告诉观察者数据不可用 等关键方法。

其中 DataSetObservable 是发布者:

/**
* A specialization of {@link Observable} for {@link DataSetObserver}
* that provides methods for sending notifications to a list of
* {@link DataSetObserver} objects.
*/
public class DataSetObservable extends Observable<DataSetObserver> {
/**
* 发出更新提醒
*/
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
} /**
* 发出数据集无法使用通知
*/
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
}

可以看到 notifyChanged 方法的注释中,是倒序遍历观察者集合并进行通知,这是为了避免观察者列表的 iterator 被使用时,进行删除操作导致出问题。

DataSetObservable 继承自 Observable < DataSetObserver > ,看下 Observable 源码:

public abstract class Observable<T> {
/**
* 观察者列表,不能重复,不能为空
*/
protected final ArrayList<T> mObservers = new ArrayList<T>(); /**
* 注册一个观察者,不能重复,不能为空
*/
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
} /**
* 解除注册一个观察者
*/
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
} /**
* 移除所有观察者
*/
public void unregisterAll() {
synchronized(mObservers) {
mObservers.clear();
}
}
}

DataSetObserver 就是观察者抽象类,将来需要被具体观察者者继承:

/**
* DataSetObserver must be implemented by objects which are added to a DataSetObservable.
*/
public abstract class DataSetObserver {
/**
* 数据改变时调用
*/
public void onChanged() {
// Do nothing
} /**
* 数据不可用时调用
*/
public void onInvalidated() {
// Do nothing
}
}

了解发布者、观察者基类后,接下来去看下在什么时候进行注册、通知。

3.ListView.setAdapter 源码:

public void setAdapter(ListAdapter adapter) {
//移除旧的观察者
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
} //省略不相关内容... if (mAdapter != null) {
//... //初始化新观察者并注册
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver); //...
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
} requestLayout();
}

可以看到在 ListView.setAdapter 方法中,先解除旧的观察者,然后初始化了新的观察者 AdapterDataSetObserver 并注册。

而 AdapterDataSetObserver 定义在 ListView 的父类 AbsListView 中:

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
} @Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}

AdapterDataSetObserver 继承自 AdapterView.AdapterDataSetObserver,在 onChanged 和 onInvalidated 方法中先调用 AdapterView.AdapterDataSetObserver 对应的方法,然后调用了 mFastScroll.onSectionsChanged();

先看 AdapterView.AdapterDataSetObserver :

class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;

        @Override
public void onChanged() {
//更新 数据修改状态
mDataChanged = true;
//更新 数据数量
mOldItemCount = mItemCount;
//更新 ItemView 数量
mItemCount = getAdapter().getCount(); // 监测是否有数据之前不可用、现在可用
// 由于 BaseAdapter.hasStableIds() 默认返回 false ,所以我们直接看 else
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
//记录当前状态,接下来刷新时要用到这些状态
rememberSyncState();
}
checkFocus();
requestLayout();
} @Override
public void onInvalidated() {
mDataChanged = true; if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
} // Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false; checkFocus();
requestLayout();
} public void clearSavedState() {
mInstanceState = null;
}
}

看 onChanged() 方法,这个方法中先后更新了 数据更新状态(mDataChanged ),数据数量,而由于 BaseAdapter.hasStableIds() 默认返回 false , 所以我们直接看 else 情况下 rememberSyncState 方法:

 /**
* 保存屏幕状态
*
*/
void rememberSyncState() {
if (getChildCount() > 0) {
mNeedSync = true;
mSyncHeight = mLayoutHeight;
if (mSelectedPosition >= 0) {
//如果选择了内容,保存选择的位置和距离顶部的偏移量
View v = getChildAt(mSelectedPosition - mFirstPosition);
mSyncRowId = mNextSelectedRowId;
mSyncPosition = mNextSelectedPosition;
if (v != null) {
mSpecificTop = v.getTop();
}
mSyncMode = SYNC_SELECTED_POSITION;
} else {
// 如果没有选择内容就保存第一个 View 的偏移量
View v = getChildAt(0);
T adapter = getAdapter();
if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
mSyncRowId = adapter.getItemId(mFirstPosition);
} else {
mSyncRowId = NO_ID;
}
mSyncPosition = mFirstPosition;
if (v != null) {
mSpecificTop = v.getTop();
}
mSyncMode = SYNC_FIRST_POSITION;
}
}
}

rememberSyncState 方法中针对是否选择了 item,保存了当前状态,重新绘制时会恢复状态。当我们滑动 ListView 后进行刷新数据操作,ListView 并没有滚动到顶部,就是因为这个方法的缘故。

回到 AdapterDataSetObserver.onChanged() 方法:

class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
//更新 数据修改状态
mDataChanged = true;
//更新 数据数量
mOldItemCount = mItemCount;
//更新 ItemView 数量
mItemCount = getAdapter().getCount(); // 监测是否有数据之前不可用、现在可用
// 由于 BaseAdapter.hasStableIds() 默认返回 false ,所以我们直接看 else
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
//记录当前状态,接下来刷新时要用到这些状态
rememberSyncState();
}
checkFocus();
requestLayout();
}
//...
}

保存数据状态后,进入 chekFocus 方法:


void checkFocus() {
final T adapter = getAdapter();
final boolean empty = adapter == null || adapter.getCount() == 0;
final boolean focusable = !empty || isInFilterMode();
// The order in which we set focusable in touch mode/focusable may matter
// for the client, see View.setFocusableInTouchMode() comments for more
// details
super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
super.setFocusable(focusable && mDesiredFocusableState);
if (mEmptyView != null) {
updateEmptyStatus((adapter == null) || adapter.isEmpty());
}
}

在这里设置 FocusFocusableInTouchMode 状态,关于 FocusableInTouchMode 不熟悉的可以 查看这篇文章

最后终于到了 View 的重新绘制 requestLayout, 这里将遍历 View 树重新绘制:

public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
} mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

至此,我们了解了 ListView 中的观察者模式的大概流程,看得人快吐血了,一层调一层啊,还是画个 UML 图和流程图来回顾一下:

ListView 中的观察者模式

ListView 注册观察者 流程图 :

ListView 通知观察者更新 流程图 :

备注:

设计模式代码在这里

ListView 另外牛的一点就是可以加载各种各样的 Item View,这得益于当初设计的 Adapter,下篇文章我们来分析下 ListView 中的适配器模式。

最熟悉的陌生人:ListView 中的观察者模式的更多相关文章

  1. UWP: ListView 中与滚动有关的两个需求的实现

    在 App 的开发过程中,ListView 控件是比较常用的控件之一.掌握它的用法,能帮助我们在一定程度上提高开发效率.本文将会介绍 ListView 的一种用法--获取并设置 ListView 的滚 ...

  2. ListView中的setOnScrollListener

    ListView是Android中最常用的控件之一,随着时代发展,RecyclerView有取代它的趋势,但是在一些老代码中,ListView依然扮演着重要的作用.项目中遇到一个需求,需要监听List ...

  3. ListView中的数据表格写入Excel中

    SaveFileDialog sfd = new SaveFileDialog(); sfd.DefaultExt = "xls"; sfd.Filter = "Exce ...

  4. ListView中动态显示和隐藏Header&Footer

    ListView的模板写法 ListView模板写法的完整代码: android代码优化----ListView中自定义adapter的封装(ListView的模板写法) 以后每写一个ListView ...

  5. Android 如何在 ListView 中更新 ProgressBar 进度

    =======================ListView原理============================== Android 的 ListView 的原理打个简单的比喻就是: 演员演 ...

  6. Xamarin.Forms listview中的button按钮,实现带着参数返回上一级页面

    今天在做列表显示的时候遇到一个问题,就是在ListView中如何才能让一个button的按钮工作并且包含参数呢? 其实有点类似于rep里的控件无法起获取一样.在Xamarin中,当你button绑定事 ...

  7. ListView 中含有 EditText 导致焦点丢失的问题

    ListView 中的 item 中有 EditText 时. 如果activity的输入法选项设置为 android:windowSoftInputMode="adjustResize&q ...

  8. ListView 中的ImageView Button

    1.ListView 中的ImageView  Button的点击事件会屏蔽掉ItemView的点击事件 原因是ImageView Button在回执过程中会抢夺item的焦点 解决办法:在xml中添 ...

  9. C#中清空ListView中的数据

    我的显示数据的方式通过button按钮点击事件,当点击之后查询数据库库并将数据显示出来. 代码如下: private void button6_Click(object sender, EventAr ...

随机推荐

  1. poj2284 That Nice Euler Circuit(欧拉公式)

    题目链接:poj2284 That Nice Euler Circuit 欧拉公式:如果G是一个阶为n,边数为m且含有r个区域的连通平面图,则有恒等式:n-m+r=2. 欧拉公式的推广: 对于具有k( ...

  2. HTML:表格与表单

    一.图片热点:规划出图片上的一个区域,可以做出超链接,直接点击图片区域就可以完成跳转的效果. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...

  3. 教你如何做好SEO优化中的前端优化

    网站的速度是很多人都面临的问题,其实许多网站,都没有特意的去优化加载速度,对于一个网站来说,加速不但提高了用户体验(如果一个网站在几秒内没 有打开,大多数用户选择的是关闭而非等待),而且对于SEO的流 ...

  4. linux 安装 apache

    1.  系统基本信息 CentOS  6.4   内存2G  硬盘 200G   cpu 4核  (cat /proc/cpuinfo |grep 'processor'|wc -l  查看cpu核数 ...

  5. vim多行注释和取消多行注释

    多行注释: 1. 进入命令行模式,按ctrl + v进入 visual block模式(可视快模式),然后按j, 或者k选中多行,把需要注释的行标记起来 2. 按大写字母i,再插入注释符,例如// 3 ...

  6. java面向对象编程——第八章 类的高级概念

    8.1访问修饰符: public:该类或非该类均可访问: private: 只有该类可以访问: protected:该类及其子类的成员可以访问,同一个包中的类也可以访问: 默认:相同数据包中的类可以访 ...

  7. 获取手机通讯录放入PinnedSectionListView中,按名字首字母排序,并且实现拨打电话功能。

    package com.lixu.tongxunlu; import java.util.ArrayList; import com.lixu.tongxunlu.PinnedSectionListV ...

  8. 关于HTML5应用开发功耗调优化小结

    HTML5的优化一直是困扰我的难题,特别是在移动端开发游戏和应用,所以对此进行了一些总结: 功耗优化点介绍 在移动设备中主要的功耗点在: 1. 网络的传输, 不管是3G网络还是WiFi传输都是移动设备 ...

  9. 关于Android的onResume的2点体会(程序切换之后恢复状态)

    Android有点儿差劲:按home键之后,立即长按home键选择程序切换回来,居然activity就跑回初始状态去了. 我的程序里面有2个webview,2个按钮,我做到把他们都恢复了. 1 web ...

  10. MM 不哭 (tyvj 1097)

    题目大意: 一条数轴上有 n 个 MM 在哭,需要tcboy去安慰,tcboy 一开始站在第k个MM身边,每个MM 哭都会减掉tcboy的RP. 确定安慰MM的顺序使得RP扣得最少.求 min(Rp_ ...