FragmentPageAdapter#notifyDataSetChanged做了什么?
来源:互联网 发布:dota2怎么看数据 编辑:程序博客网 时间:2024/05/16 05:53
这是Android源代码分析第二篇,第一篇写的太烂了,真的是烂,烂到我自己都不想去改。
所以以后看源码啊,还得看继承关系比较浅的……不然分分钟看死你。接下来是正文。
本次文中所提到源码出处均基于support-v4-22.2.1-source.jar。在这之后的支持库源码已经发生变动,以下所述可能和新版源码中有所不同。
我们在使用Fragment+ViewPager来实现滑动页卡的需求时,避不开一个类,就是FragmentPageAdapter
(出自android.support.v4.app
扩展包),这个类用来管理ViewPager
中的Fragment
,其中有一个我们可以在很多其他地方都能找到的方法notifyDataSetChanged
,不管你是习惯使用ListView
+BaseAdapter
,还是RecyclerView
+RecyclerView.Adapter
,都有这个熟悉的方法来帮助你在更新集合数据之后来刷新界面。接下来我们就来看看FragmentPageAdapter
是如何来做这件事情的。
FragmentPageAdapter
继承自PagerAdapter
,方法notifyDataSetChanged
也是在PagerAdapter
中定义的。在FragmentPagAdapter
类中没有关于这个方法的声明,说明这个方法在调用的时候完全出自PagerAdpater
。所以我们的标题也可以改成PageAdapter#notifyDataSetChanged
做了什么。。。接下来我们来看看这个类。
package android.support.v4.view;public abstract class PagerAdapter { ... private DataSetObservable mObservable = new DataSetObservable(); public void notifyDataSetChanged() { mObservable.notifyChanged(); } public void registerDataSetObserver(DataSetObserver observer) { mObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mObservable.unregisterObserver(observer); } ...}
值得注意的是,PagerAdapter这个抽象类定义了一个成员变量mObservable
,类型为DataSetObervable。我们来看看这个类的一些相关情况。
package android.database;public class DataSetObservable extends Observable<DataSetObserver> { public void notifyChanged() { synchronized(mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } } ...}
package android.database;import java.util.ArrayList;public abstract class Observable<T> { protected final ArrayList<T> mObservers = new ArrayList<T>(); public void registerObserver(T observer) { //方法体省略 } public void unregisterObserver(T observer) { //方法体省略 } public void unregisterAll() { //方法体省略 }}
package android.database;public abstract class DataSetObserver { public void onChanged() { // Do nothing } public void onInvalidated() { // Do nothing }}
DataSetObervable
继承自Observable
,同时将父类的泛型定义明确为一个抽象类DataSetObserver
,看名字很是接近,如果有同学熟悉RxJava肯定能明白其中的联系。DataSetObervable
内部维护了一个元素类型为DataSetObserver
的ArrayList
,DataSetObserver
则有两个方法onchange
和onInvalidated
可供子类去重写。ArrayList
的出现使得我们也认识到了一点,就是一个Adapter
可以被多个ViewPager
去绑定,不然这个list的意义在哪呢?
我们回到PagerAdapter
这个类,当调用notifyDataSetChanged时,调用了DataSetObserver
的notifyChanged
方法,而在这个方法的定义中又取出ArrayList
所有的DataSetObserver去执行onChange
方法。而PagerAdapter
中管理mObservable
有registerDataSetObserver
和unregisterDataSetObserver
两个方法,这个两个方法标记为公开,从名字就能看出来这两个方法分别是将observer
添加和移除到ArrayList
中,所以,我们现在去ViewPager
类中寻找,是否调用了这两个方法。
我们在ViewPager类里面又发现一个很像上面描述的成员变量
private PagerObserver mObserver; private class PagerObserver extends DataSetObserver { @Override public void onChanged() { dataSetChanged(); } @Override public void onInvalidated() { dataSetChanged(); } }
这个成员变量连带着定义出现在了ViewPager
类中,这个是一个私有的内部类,当ViewPager#PagerObserver
调用DataSetObserver#onChange()
方法时,内部只调用了一行,便是ViewPager#dataSetChanged()方法。这个方法十分复杂,我们稍后再看,我们先来找一找mObserver
是如何和我们的PagerAdapter建立关联的。
public void setAdapter(PagerAdapter adapter) { if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mObserver); mAdapter.startUpdate(this); //省略 } //省略 if (mAdapter != null) { if (mObserver == null) { mObserver = new PagerObserver(); } mAdapter.registerDataSetObserver(mObserver); //省略 } }
果不其然,在ViewPager
的setAdapter(PagerAdapter adapter)
方法中,我们发现了一些东西。首先如果ViewPager
已经绑定了Adapter
的话,首先移除之前的mObserver
,然后在重新绑定了新的mObserver
。
所以目前我们可以总结一下,调用FragmentPageAdapter#notifyDataSetChanged
实际上调用了ViewPager#dataSetChanged()
方法。同时在这个方法里面,我们也需要关注另外一个方法,那就是PagerAdapter#getItemPosition(Object object)
方法
/** * Called when the host view is attempting to determine if an item's position * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given * item has not changed or {@link #POSITION_NONE} if the item is no longer present * in the adapter. * * <p>The default implementation assumes that items will never * change position and always returns {@link #POSITION_UNCHANGED}. * * @param object Object representing an item, previously returned by a call to * {@link #instantiateItem(View, int)}. * @return object's new position index from [0, {@link #getCount()}), * {@link #POSITION_UNCHANGED} if the object's position has not changed, * or {@link #POSITION_NONE} if the item is no longer present. */ public int getItemPosition(Object object) { return POSITION_UNCHANGED; }
根据API的解释,大体的意思就是当item的位置发生改变时,在主视图尝试更新的时候会调用这个方法,这里的传入参数Object就是在PagerAdapter#instantiateItem(ViewGroup container, int position)
方法中返回的单个fragment,如果返回POSITION_UNCHANGED
表示参数Object的位置没有发生变化,这也是默认的返回值,如果该Object(fragment)不再出现时,应当返回POSITION_NONE
。
所以我们的每一个fragment都应该维护一个int值来表示当前的位置,在完成业务逻辑之后准备调用notifyDataSetChanged
之前,将不再出现的fragment该int值更新为POSITION_NONE,同时对于不更新和位置变化的fragment,也要将其更新为其他的逻辑。
接下来我们来看看最关键的方法,更新的具体操作,dataSetChanged()
。
void dataSetChanged() { final int adapterCount = mAdapter.getCount();//当前适配器数据数量 mExpectedAdapterCount = adapterCount; boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < adapterCount; //需要填充的标记(如果当前item数目小于ViewPager的缓存区大小,且新的数据大于之前集合) int newCurrItem = mCurItem;//记录当前位置下标 boolean isUpdating = false; for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); //此处应当返回PagerAdapter的getItemPosition值,默认为PagerAdapter#POSITION_UNCHANGED final int newPos = mAdapter.getItemPosition(ii.object); //如果为默认的POSITION_UNCHANGED值,直接跳出到下一次for循环 if (newPos == PagerAdapter.POSITION_UNCHANGED) { continue; } //如果为POSITION_NONE,移除这个item的内容 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; } //如果返回的值不等于之前的下标,则更新下标 if (ii.position != newPos) { //如果是当前显示的item,更新其下标为getItemPosition的返回值 if (ii.position == mCurItem) { // Our current item changed position. Follow it. newCurrItem = newPos; } ii.position = newPos; needPopulate = true; } } /** * needPopulate为true的情况: * 1:存在某个item的返回值为PagerAdapter.POSITION_NONE; * 2:存在某个item的getItemPosition数值和之前初始化保存item下标不同; */ if (isUpdating) { mAdapter.finishUpdate(this); } Collections.sort(mItems, COMPARATOR); //刷新布局 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(); } }
在程序的第10行,for循环遍历了当前的item列表,逐一取出getItemPosition
的返回值,
1. 如果是POSITION_UNCHANGED
的话,直接continue。
2. 如果为POSITION_NONE
,则对item进行PagerAdapter#destroyItem
来销毁这个item。而且如果是当前显示的item,needPopulate
置为true。
3. 如果返回的值不是上述的两个,而且还和之前的值也不一样的话,将下标更新为新的值。needPopulate
置为true。
有的同学就要问了,如果我想捣乱呢?我在覆写FragmentAdapter的时候将getItemPosition的返回值全部设置为POSITION_NONE
,就像下面这样。
@Override public int getItemPosition(Object object) { return PagerAdapter.POSITION_NONE; }
如果按照这个逻辑,那岂不是所有的item都被销毁了?整个ViewPager都没有内容了?
我想你还是太年轻,在dataSetChanged()
方法中,存在一个比较重要的boolean 类型的变量needPopulate
,当这个变量为true的时候,将刷新布局,同时也使用setCurrentItemInternal
方法来刷新当前位置的item内容。而在setCurrentItemInternal
方法中,又调用了一个超级无比复杂的方法populate
,这个方法将会根据你PagerAdapter的instantiateItem
和getCount
方法来重新实例化item,所以。除非你在instantiateItem
和getCount
一起做手脚,否则只是靠getItemPosition
方法来变更item是不现实的。
所以总结一下,needPopulate
将在以下情况为true。
- 存在某个Item的getItemPosition
方法返回了POSITION_NONE
。
- 存在某个Item的getItemPosition
方法返回了与之前下标不同的的值。
由于populate
方法实在太长,而且逻辑很复杂,难度也很大,本文不再分析这个方法,我们需要知道的时,如果你需要通过notifyDataSetChanged
方法来更新ViewPager,请在调用notifyDataSetChanged
方法前也更新PagerAdapter的instantiateItem
和getCount
方法(FragmentPageAdapter
与其对应的是getItem
和getCount
方法)。
@Override public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); mCurTransaction.detach((Fragment)object); }
上面的代码出自
android.support.v4.app.FragmentPagerAdapter
,可以看见在instantiateItem
方法中在当前FragmentManager中找不到指定Fragment时,调用了getItem
方法来获得一个新的Fragment,而在Fragment存在的时候只是attach了这个Fragment,同时在destroyItem
方法中也并未销毁指定的Fragment,只是detach
了这个Fragment。
如果你只是想更新其中某一个item或者其他item的内容,期望重新初始化一下相应的Fragment(刷新数据,重新载入等等),你可以将需要刷新的item的getItemPosition
方法返回POSITION_NONE
,这样的话系统会认为你不需要这个item而首先destroyItem
(源码中实际上只是通过事务栈来detach
了这个Fragment),然后又会因为getCount
方法和getItem
并没有发生改变而重新将这个Fragment加入指定的item中,达到了刷新的效果。
- FragmentPageAdapter#notifyDataSetChanged做了什么?
- 今天做了什么?
- 该做什么了?
- 今天做了什么?
- using做了什么?
- 今天做了什么
- interface_cast做了什么
- ActivityGroup做了什么?
- chkconfig做了什么
- IDE做了什么?
- 今天我做了什么?
- 看看你做了什么?
- precreatewindow里面做了什么?
- DUPLICATE到底做了什么?
- PeekMessage究竟做了什么?
- 窗口关闭做了什么?
- Android做了些什么?
- 十年了,你做什么
- 从零开始学网络爬虫之Requests库
- ARM时钟系统(S5PV210)
- Mutt and Smtp Notes
- [模板题] 最小密度路径
- 用node.js搭建服务器
- FragmentPageAdapter#notifyDataSetChanged做了什么?
- 数据结构====》排序之选择排序
- 硬盘通电时间
- java 继承
- 【Dongle】【Java】classpath配置中点“.”作用
- 怎么获取一个数字中的1的个数
- QuickHit
- 数据结构==》排序之归并排序
- POJ 2392 Space Elevator 已被翻译