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内部维护了一个元素类型为DataSetObserverArrayList,DataSetObserver则有两个方法onchangeonInvalidated可供子类去重写。ArrayList的出现使得我们也认识到了一点,就是一个Adapter可以被多个ViewPager去绑定,不然这个list的意义在哪呢?
 我们回到PagerAdapter这个类,当调用notifyDataSetChanged时,调用了DataSetObservernotifyChanged方法,而在这个方法的定义中又取出ArrayList所有的DataSetObserver去执行onChange方法。而PagerAdapter中管理mObservableregisterDataSetObserverunregisterDataSetObserver两个方法,这个两个方法标记为公开,从名字就能看出来这两个方法分别是将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);            //省略        }    }

 果不其然,在ViewPagersetAdapter(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的instantiateItemgetCount方法来重新实例化item,所以。除非你在instantiateItemgetCount一起做手脚,否则只是靠getItemPosition方法来变更item是不现实的。

 所以总结一下,needPopulate将在以下情况为true。
- 存在某个Item的getItemPosition方法返回了POSITION_NONE
- 存在某个Item的getItemPosition方法返回了与之前下标不同的的值。

 由于populate方法实在太长,而且逻辑很复杂,难度也很大,本文不再分析这个方法,我们需要知道的时,如果你需要通过notifyDataSetChanged方法来更新ViewPager,请在调用notifyDataSetChanged方法前也更新PagerAdapter的instantiateItemgetCount方法(FragmentPageAdapter与其对应的是getItemgetCount方法)。

    @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中,达到了刷新的效果。

0 0
原创粉丝点击