解决ViewPager+Fragment预加载导致未显示界面提前加载数据

版权声明:终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客~ https://blog.csdn.net/hty1053240123/article/details/79456456

1. 简介

当我们使用ViewPager+Fragment结构时,由于谷歌在设计ViewPager时会提前加载下一个界面。虽然用户在滑动到下一页时不会感觉卡顿。但这也存在一个问题:当进入Activity时,当前界面和下一个界面均由网络请求时由于预加载了下一页导致同时有两个网络请求,在大多数情况下不显示的界面是不用联网把数据请求下来的。下面通过Fragment提供的setUserVisibleHint方法来解决该问题。

2. 具体分析

2.1 setUserVisibleHint

在分析之前先讲下setUserVisibleHint方法,官网对该方法的解释为:setUserVisibleHint

Set a hint to the system about whether this fragment's UI is currently visible to the user. This hint defaults to true and is persistent across fragment instance state save and restore.

An app may set this to false to indicate that the fragment's UI is scrolled out of visibility or is otherwise not directly visible to the user. This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.

Note: This method may be called outside of the fragment lifecycle and thus has no ordering guarantees with regard to fragment lifecycle method calls.

Note: Prior to Android N there was a platform bug that could cause setUserVisibleHint to bring a fragment up to the started state before its FragmentTransaction had been committed. As some apps relied on this behavior, it is preserved for apps that declare a targetSdkVersion of 23 or lower.

翻一下就是:

设置一个标志表明当前Fragment界面是否对用户可见的,默认值是true。并且该值保存在Fragment的实例中。

下面的Note就大家自己看了,该方法意思也很简单,设置一个标志来表明当前Fragment是否是可见的。该方法有一个参数,其含义是:

Parameters:
isVisibleToUser boolean: true if this fragment's UI is currently visible to the user (default), false if it is not.
回调传来的是true表明当前Fragment是对用户可见的,false表示对用户不可见。

2.2 getUserVisibleHint

官网解释如下:getUserVisibleHint,也就是返回当前的可见性,代码如下:

public boolean getUserVisibleHint() {
        return mUserVisibleHint;
    }

该方法也就是获取mUserVisibleHint。

2.3 setUserVisibleHint调用时机

在FragmentPagerAdapter.java中调用了Fragment的setUserVisibleHint,代码如下:


@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
        fragment.setUserVisibleHint(false);
    }
    return fragment;
}

还有一处调用为:

/**
 * Called to inform the adapter of which item is currently considered to
 * be the "primary", that is the one show to the user as the current page.
 */
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment)object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            //将当前的Fragment的Visibile置为false。
            mCurrentPrimaryItem.setUserVisibleHint(false);
        }
        if (fragment != null) {
            fragment.setMenuVisibility(true);
            //这时候也意味着这个fragment要呈现给用户了,传入参数为true
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

以上代码有三处调用,可以看出在初始加载的时候,如果该Fragment不是要显示的Fragment时,在调用setUserVisibleHint的时候传入的就为false,当setPrimaryItem将某个position的Fragment调到前台时,会先把当前正在显示的Fragment的visibility置为false,然后将当前的Fragment的visibility再置为true。

3. 解决方案

预加载Fragment的时候,我们先判断下当前加载的Fragment的visibility是否是true,如果不是true,那么就先不加载,等下次设置setUserVisibleHint的时候再去联网加载。代码如下:
BaseFragment.java

public abstract class BaseFragment extends Fragment {
    //当前View是否创建了。
    protected boolean isViewInitiated;
    //当前数据是否加载了。
    protected boolean isDataInitiated;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        prepareFetchData();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInitiated = true;
        prepareFetchData();
    }
    //即使预加载了第二页,但是网络请求不会被预加载
    public abstract void getData();

    public boolean prepareFetchData() {
        return prepareFetchData(false);
    }

    public boolean prepareFetchData(boolean forceUpdate) {
        if (getUserVisibleHint() && isViewInitiated && (!isDataInitiated || forceUpdate)) {
            getData();
            isDataInitiated = true;
            return true;
        }
        return false;
    }
}

在使用的时候,也很方便,直接继承BaseFragment,然后实现getData()函数实现联网请求。

4. 总结

解决这个还是比较容易的,方法就是当当前Fragment的visibility为true的时候才去联网请求数据。具体办法也就是利用Fragment给的回调函数setUserVisibileHint,然后获取显示状态。没加载数据则联网加载数据。

猜你喜欢

转载自blog.csdn.net/hty1053240123/article/details/79456456