FragmentPagerAdapter 的那些坑

引子

image

开始前,首先来看一下这种常用的布局,每个底部导航栏项中呢都有一个ViewPager。
笔者先来说一下自己遇到的问题,第一次进入的时候ViewPager(第一页)显示正常,但是切换到别的底部tab再切换回来的时候,ViewPager的第一项和第二项的视图就没有了,试过FragmentPagerAdapter 和 FragmentStatePagerAdapter 但都没有解决问题,所以笔者决定从源码的角度出发,去探索问题所在。

起始

首先
底部Tab1是HomeFragement
底部Tab2是OrdersFragment

由HomeFragment切换到OrdersFragment之后的周期调用情况如下:

— 后台打印如下 —
07-29 11:21:56.560 18830-18830/com.finobusiness.forhomelife D/HomeFragment: onDestroyView:
07-29 11:21:56.565 18830-18830/com.finobusiness.forhomelife D/OrdersFragment: onCreateView:

HomeFragment只是调用了onDestroyView方法,没有调用onDestroy方法。

问题源头

FragmentPagerAdapterinstantiateItem()方法如下:

 @Override
    public Object instantiateItem(ViewGroup container, int position) {

        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) {
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }

        return fragment;
    }

需要注意的是这段代码:

        Fragment fragment = mFragmentManager.findFragmentByTag(name);

可以看到在去实例化一个Fragment之前会先去FragmentManager当中通过Tag查找是否已经有了这个Fragment,如果有,则不再调用public abstract Fragment getItem(int position) 这个方法,通常这是咱们实现的用来提供Fragment的方法。

Fragment 的 onDestroyView()

因为ViewPager当中的Fragment的视图已经被销毁了

onDestroyView的源码

    public void onDestroyView() {
        mCalled = true;
    }

没错,只有这么一行,但是里面的mCalled威力可是很惊人的,一会儿就会涉及到。
在代码中查询,发现onDestroyView()方法在全局中只有一个地方被调用到了,那就是

 void performDestroyView() {
        if (mChildFragmentManager != null) {
            mChildFragmentManager.dispatchDestroyView();
        }
        mState = CREATED;
        mCalled = false;
        onDestroyView();
        if (!mCalled) {
            throw new SuperNotCalledException("Fragment " + this
                    + " did not call through to super.onDestroyView()");
        }
        if (mLoaderManager != null) {
            mLoaderManager.doReportNextStart();
        }
    }

首先呢可以看到如果onDestroyView()方法在在子类中没有被调用,则会抛出SuperNotCalledException异常

其次

mChildFragmentManager.dispatchDestroyView 最终会调用到FragmentManagervoid moveToState(int newState, int transit, int transitStyle, boolean always)方法。

moveToState方法的源码如下

    void moveToState(int newState, int transit, int transitStyle, boolean always) {
        mCurState = newState;
        if (mActive != null) {
            boolean loadersRunning = false;
            for (int i=0; i<mActive.size(); i++) {
                Fragment f = mActive.get(i);
                if (f != null) moveToState(f, newState, transit, transitStyle, false);
            }
        }
    }
    case Fragment.ACTIVITY_CREATED:
            if (newState < Fragment.ACTIVITY_CREATED) {

                f.performDestroyView();
                if (f.mView != null && f.mContainer != null) {
                    f.mContainer.removeView(f.mView);
                }
                f.mContainer = null;
                f.mView = null;
                f.mInnerView = null;
    }

最终可以看到fragment.view被移除并且被置为null。
下一次进来的时候Fragment被通过getFragmentByTag方法找到,但是他的View却是空,所以我们的ViewPager的前两页(默认的缓存页)就没有视图。

解决之道

解决的办法就是ViewPager所在的那个Fragment中手动调用PagerAdapter 的destroyItem方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值