ViewPager 源码分析(二) —— 关于 notifyDataSetChanged()
来源:互联网 发布:淘宝众划算怎么样 编辑:程序博客网 时间:2024/06/08 17:06
写在前面
关于 ViewPager,我准备写一个系列。我水平也不咋地,估计有不少纰漏,各位爱看不看:
- ViewPager 源码分析(一) —— setAdapter() 与 populate()
知识点
上回说到PagerAdapter
这个类以及setAdapter()
和populate()
如何在数据变化时重建和销毁页面。本节我们继续来看PagerAdapter
,体味一下 android 中典型的观察者模式。其中涉及这几个类:
- Observable
- DataSetObservable
- DataSetObserver
- ViewPager.PagerObserver
源码分析
关于这个观察者模式的架构,我们来看张图:
我们可以在图中看出,PagerAdapter
的观察者模式是基于Observable
和DataSetObserver
这两个基类。事实上,不管ListView
还是RecyclerView
的Adapter
都是基于它们。考虑到ListView
、RecyclerView
也是极其常用的控件,了解该观察者模式的实现很有必要。
通常意义上的Observable
,和Observer
长这样:
public class Observable{ private List mObservers; // 一堆 observer public void notifyChanged(); // 遍历 mObservers, 依次回调 observer.onChange() public void registerObserver(Observer observer); // 注册: mObservers.add(observer) public void unregisterObserver(Observer observer); // 移除: mObservers.remove(observer)}public abstract class Observer { public void onChanged() {}}
在图中,你能发现一些类似的痕迹。但不知为何被分成了两层。notifyChanged()
被移到了子类DataSetObservable
中。注意到,PagerAdapter
也有类似Observable
的三个接口,并且持有一个DataSetObservable
,事实上是一种代理模式Proxy
。
notifyDataSetChanged()
差不多说完背景,我们从notifyDataSetChanged()
看起。一般我们改变了Adapter的数据之后,都要调用一下它。根据之前的分析,我们知道会回调到observer.onChange()
这个函数。那这个observer
又是啥呢?图中,我们看到PagerObserver
的身影,结合代码看看:(想回调,肯定少不了注册的过程啦)
public class ViewPager extends ViewGroup { private PagerObserver mObserver; // Observer public void setAdapter(PagerAdapter adapter) { ... if (mAdapter != null) { if (mObserver == null) { mObserver = new PagerObserver(); } mAdapter.registerDataSetObserver(mObserver); // 注册 Observer } private class PagerObserver extends DataSetObserver { @Override public void onChanged() { // 回调到这里 dataSetChanged(); } @Override public void onInvalidated() { dataSetChanged(); } }}
dataSetChanged()
注册完mObserver
之后,一切都指向dataSetChanged()
。
先别急着看代码,你是否遇到过调用notifyDataSetChanged()
数据不更新的情况(instantiateItem()
未被回调)?其原因就在这个函数里。它留了一个口给我们——mAdapter.getItemPosition()
,用于控制数据更新流程:
- PagerAdapter.POSITION_UNCHANGED: 页面未改变,啥都不做(默认行为)
- PagerAdapter.POSITION_NONE:页面已销毁,populate() 重建页面,然后间接回调instantiateItem()。不了解 populate() 的童鞋可以先看本系列的第一篇。
void dataSetChanged() { // This method only gets called if our observer is attached, so mAdapter is non-null. final int adapterCount = mAdapter.getCount(); mExpectedAdapterCount = adapterCount; // 0. 是否调用 populate() 重建页面,由最大缓存数和数据总数决定。 boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < adapterCount; int newCurrItem = mCurItem; // 1. 由 getItemPosition() 的返回值决定,是否 needPopulate boolean isUpdating = false; for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); // 1.0 mAdapter.getItemPosition() 开了一个口,对是否更新数据起到决定性的作用 final int newPos = mAdapter.getItemPosition(ii.object); // 1.1 这个是默认值: 页面未改变,啥都不做。 // 所以解释了为何 notifyDataSetChanged() 会无效, // 你需要重写 getItemPosition(), 返回 PagerAdapter.POSITION_NONE。 // 这个时候会回调 instantiateItem(), // 记得做缓存,而不是每次 new 一个 page 出来。 if (newPos == PagerAdapter.POSITION_UNCHANGED) { continue; } // 1.2 表示页面已销毁,使得 needPopulate = true; if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; if (!isUpdating) { mAdapter.startUpdate(this); isUpdating = true; } mAdapter.destroyItem(this, ii.position, ii.object); needPopulate = true; if (mCurItem == ii.position) { // Keep the current item in the valid range newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); needPopulate = true; } continue; } // 1.3 TODO: 尚不明白 if (ii.position != newPos) { if (ii.position == mCurItem) { // Our current item changed position. Follow it. newCurrItem = newPos; } ii.position = newPos; needPopulate = true; } } if (isUpdating) { mAdapter.finishUpdate(this); } Collections.sort(mItems, COMPARATOR); // 2. 如果需要 populate(), 调用 requestLayout(); // 会重新走一遍 measure() -> layout() -> draw(), // 其中 onMeasure() 会间接调用 populate(). if (needPopulate) { // Reset our known page widths; populate will recompute them. final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor) { lp.widthFactor = 0.f; } } setCurrentItemInternal(newCurrItem, false, true); requestLayout(); } }
总结
通过源码的分析,我们了解了观察者模式的内部实现。同时,我们看到了notifyDataSetChanged()
在使用上的一个坑:
Android viewPage notifyDataSetChanged无刷新
它必须配合getItemPosition()
的重写才能正常使用,这样的设计不知是bug还是处于什么特殊目的。另外,在instantiateItem()
时,我们得相应得做好缓存。
至此,分析源码还是有益的,可以发现一些设计上的坑。事实上,上面的坑在工作中,我的确踩到过,为此还花了不少时间。
- ViewPager 源码分析(二) —— 关于 notifyDataSetChanged()
- 解析ViewPager(二)——ViewPager源码解析
- 【android】从源码分析调用ViewPager的notifyDataSetChanged无刷新的问题及解决方法
- Android:关于ViewPager 适配器notifyDataSetChanged不能刷新
- Android学习笔记——关于Android ViewPager 在 adapter.notifyDataSetChanged 后 setCurrentItem 方法失效的解决办法
- 追溯源码解决Viewpager之notifyDataSetChanged无刷新
- ViewPager 源码分析(一) —— setAdapter() 与 populate()
- viewpager源码分析
- ViewPager源码分析
- ViewPager源码分析
- ViewPager实现源码分析
- ViewPager.notifyDataSetChanged() 失效问题
- ViewPager的PagerAdapter.notifyDataSetChanged()
- 关于notifyDataSetChanged()
- 源码分析listview的adapter的notifyDataSetChanged方法分析
- Adapter——notifyDataSetChanged
- perf_event源码分析(二)——cmd_report
- chatofpomelo源码分析(二)——聊天
- 工厂方法模式
- 建造者模式
- 模板方法模式
- Learn Python the Hard Way 7: Ex05 More Variables and Printing
- angular的directive指令的link方法
- ViewPager 源码分析(二) —— 关于 notifyDataSetChanged()
- OPENGL学习笔记(十二)
- Android Studio cmake方式jni开发,在c层使用其它动态链接库*.so
- Cocos2d-x 记录游戏日志并上传
- js变量 命名 交换
- TCP/UDP的区别
- epoll的水平触发和边缘触发
- jsDOM元素的创建-操作(creatEle)
- 分布式一致性原理学习笔记(4)