FragmentPageAdapter#notifyDataSetChanged做了什么?

 这是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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值