PagerAdapter分析与Fragment懒加载的几种实现

PagerAdapter分析与Fragment懒加载的几种实现

Deprecated

时间:2019年8月23日

新版的ViewPager的实现和之前不同,FragmentTransaction增加setMaxLifecycle(Fragment, Lifecycle.State)相关API,更好使用。下面实现方法个人不再推荐使用。

前言

相信使用过ViewPager的人都知道它的常规使用方法,当然用得最多的仍然是FragmentPagerAdapterFragmentStatePagerFragment,但是会用不一定用得好,从刚开始开发APP到现在,我也用过无数遍,但是最近为了优化界面,查看源码才发现之前自己“不会用”。

翻译

/**
 * Base class providing the adapter to populate pages inside of
 * a {@link ViewPager}.  You will most likely want to use a more
 * specific implementation of this, such as
 * {@link android.support.v4.app.FragmentPagerAdapter} or
 * {@link android.support.v4.app.FragmentStatePagerAdapter}.
 *
 * <p>When you implement a PagerAdapter, you must override the following methods
 * at minimum:</p>
 * <ul>
 * <li>{@link #instantiateItem(ViewGroup, int)}</li>
 * <li>{@link #destroyItem(ViewGroup, int, Object)}</li>
 * <li>{@link #getCount()}</li>
 * <li>{@link #isViewFromObject(View, Object)}</li>
 * </ul>
 *
 * <p>PagerAdapter is more general than the adapters used for
 * {@link android.widget.AdapterView AdapterViews}. Instead of providing a
 * View recycling mechanism directly ViewPager uses callbacks to indicate the
 * steps taken during an update. A PagerAdapter may implement a form of View
 * recycling if desired or use a more sophisticated method of managing page
 * Views such as Fragment transactions where each page is represented by its
 * own Fragment.</p>
 *
 * <p>ViewPager associates each page with a key Object instead of working with
 * Views directly. This key is used to track and uniquely identify a given page
 * independent of its position in the adapter. A call to the PagerAdapter method
 * {@link #startUpdate(ViewGroup)} indicates that the contents of the ViewPager
 * are about to change. One or more calls to {@link #instantiateItem(ViewGroup, int)}
 * and/or {@link #destroyItem(ViewGroup, int, Object)} will follow, and the end
 * of an update will be signaled by a call to {@link #finishUpdate(ViewGroup)}.
 * By the time {@link #finishUpdate(ViewGroup) finishUpdate} returns the views
 * associated with the key objects returned by
 * {@link #instantiateItem(ViewGroup, int) instantiateItem} should be added to
 * the parent ViewGroup passed to these methods and the views associated with
 * the keys passed to {@link #destroyItem(ViewGroup, int, Object) destroyItem}
 * should be removed. The method {@link #isViewFromObject(View, Object)} identifies
 * whether a page View is associated with a given key object.</p>
 *
 * <p>A very simple PagerAdapter may choose to use the page Views themselves
 * as key objects, returning them from {@link #instantiateItem(ViewGroup, int)}
 * after creation and adding them to the parent ViewGroup. A matching
 * {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the
 * View from the parent ViewGroup and {@link #isViewFromObject(View, Object)}
 * could be implemented as <code>return view == object;</code>.</p>
 *
 * <p>PagerAdapter supports data set changes. Data set changes must occur on the
 * main thread and must end with a call to {@link #notifyDataSetChanged()} similar
 * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
 * set change may involve pages being added, removed, or changing position. The
 * ViewPager will keep the current page active provided the adapter implements
 * the method {@link #getItemPosition(Object)}.</p>
 */

翻译:
ViewPager填充页面需要提供的adapter的基类。大多数时候会使用这个类的特定实现,如FragmentPagerAdapterFragmentStatePagerAdapter.

当你实现一个PagerAdapter时,你一定至少得重写以下方法:

  • instantiateItem(ViewGroup, int)
  • destroyItem(ViewGroup, int, Object)
  • getCount()
  • isViewFromObject(View, Object)

相对使用AdapterView的adapter, 使用PagerAdapter更为简单。ViewPager使用回调来指定操作的步骤,而不是直接使用循环回收机制。如果期望实现回收View,那么通过PagerAdapter也是可以实现的,或者使用更加复杂的方法来组织每一页的View, 例如Fragment事务那样,使用一个Fragment来管理View.

ViewPager使用一个键对象来关联每一页,而不是管理View。这个键用于追踪和唯一标识在adapter中独立位置中的一页。调用方法startUpdate(ViewGroup)表明ViewPager中的内容需要更改。

通过调用一次或多次调用instantiateItem(ViewGroup, int)来构造页面视图。
调用destroyItem(ViewGroup, int, Object)来取消ViewPager关联的页面视图。
最后,当一次更新(添加和/或移除)完成之后将会调用finishUpdate(ViewGroup)来通知adapter, 提交关联和/或取消关联的操作。这三个方法就是用于ViewPager使用回调的方式来通知PagerAdapter来管理其中的页面。

一个非常简单的方式就是使用每页视图作为key来关联它们自己,在方法instantiateItem(ViewGroup, int)中创建和添加它们到ViewGroup之后,返回该页视图。与之相匹配的方法destroyItem(ViewGroup, int, Object)实现从ViewGroup中移除视图。当然必须在isViewFromObject(View, Object)中这样实现:return view == object;.

PagerAdapter支持数据改变时刷新界面,数据改变必须在主线程中调用,并在数据改变完成后调用方法notifyDataSetChanged(), 和AdapterView中派生自BaseAdapter相似。一次数据的改变可能关联着页面的添加、移除、或改变位置。ViewPager将根据adapter中实现getItemPosition(Object)方法返回的结果,来判断是否保留当前已经构造的活动页面(即重用,而不完全自行构造)。

原理详解

ViewPager+PagerAdapter的合作关系:ViewPager来控制一页界面构造和销毁的时机,使用回调来通知PagerAdapter具体做什么,PagerAdapter只需要按照相应的步骤做。当然为了使用得更好、提供更多的功能,又建议了使用View的回收工作和管理工作,同时提供当数据改变时的界面刷新工作。

instantiateItem(ViewGroup, int): 构造指定位置的页面。adapter负责在这个方法中添加view到容器中,即使是在finishUpdate(ViewGroup)才保证完成的。在FragmentPagerAdapterFragmentStatePagerAdapter中,都是返回一个构造的Fragment.

destroyItem(ViewGroup, populate, Object): 移除指定位置的页面。adapter负责从容器中移除view, 即是最后实在finishUpdate(ViewGroup)保证完成的。在FragmentPagerAdapterFragmentStatePagerAdapter中,分别使用FragmentTransition.detach(Fragment)FragmentTransition.remove(Fragment)来逻辑上销毁Fragment.

finishUpdate(ViewGroup): 当页面的显示变化完成式调用。在这里,你一定保证所有的页面从容器中合理的添加或移除掉。

setPrimaryItem(ViewGroup, int, Object): 被ViewPager调用来通知adapter此时那个item应该被认为是主要的页面,这个页面将在当前页面展示给用户。正是因为这个方法,才有在ViewPager中实现Fragment懒加载的机制。

isViewFromObject(View, Object): 指定当前页面View是否和指定的key对象相关联(这个key对象是在instantiateItem(ViewGroup, int)方法返回的)。这个方法需要PagerAdapter恰当的实现。即只要匹配好键值对即可。FragmentPagerAdapterFragmentStatePagerAdapter的实现: return ((Fragment)object).getView() == view;.

虽然简单或很少使用到的一些方法不想细究,不过还是一次性分析完为好,如getPageTitle(int), getPageWidth(int), getItemPosition(Object)等。

getPageTitle(int): 返回每页的标题,多用于关联indicator

getPageWidth(int): 返回指定的页面相对于ViewPager宽度的比例,范围(0.f-1.f]。默认值为1.f, 即占满整个屏幕。如果是0.5f, 那么在初始状态下,默认会出现前两个页面,而primary主页面是在ViewPager的起始位置(通常是屏幕左侧),直到最后一个页面在屏幕右侧,如果总共5个页面,返回值为0.2f, 那么将一次性出现所有的页面.

getItemPosition(Object): 用于数据刷新时的页面处理方式。返回值包括三类:POSITION_UNCHANGED表示位置没有变化,即在添加或移除一页或多页之后该位置的页面保持不变,可以用于一个ViewPager中最后几页的添加或移除时,保持前几页仍然不变;POSITION_NONE,表示当前页不再作为ViewPager的一页数据,将被销毁,可以用于无视View缓存的刷新;根据传过来的参数Object来判断这个key所指定的新的位置,如总共有5个页面,我们想交换第0个和第4个的页面,那么在返回时,可以参考下面的代码。

// ...
private List<Object> mItems = new ArrayList<>();

{
    for (int i = 0; i < 5; i++) {
        mItems.add(null);
    }
}

@Override
public int getItemPosition(Object object) {
    int position = mItems.indexOf(object);
    if (position == 0) {
        return 4;
    } else if (position == 4) {
        return 0;
    } else {
        // 下面两种都可以
//            return POSITION_UNCHANGED;
        return position;
    }
}
// ...

saveState(): 保存页面状态。用得不多,见到的也是在FragmentStatePagerAdapter中使用,有不同于FragmentPagerAdapter的对于Fragment的管理机制。当然我们也可以使用这个来优化我们自己的PagerAdapter, 如主页Banner.

restoreState(Parcelable, ClassLoader): 和saveState()搭配使用,用于恢复页面状态。

FragmentPagerAdapter

可以从很多文章中看到类似这样的一句话:超出范围的Fragment会被销毁。所以之前,我一直认为的是,FragmentPagerAdapter中通常最多会保留3个Fragment, 超出左右两侧的Fragment将被销毁,滑动到时又会被重新构造。不过这是错误的。下面是翻译。

/**
 * Implementation of {@link PagerAdapter} that
 * represents each page as a {@link Fragment} that is persistently
 * kept in the fragment manager as long as the user can return to the page.
 *
 * <p>This version of the pager is best for use when there are a handful of
 * typically more static fragments to be paged through, such as a set of tabs.
 * The fragment of each page the user visits will be kept in memory, though its
 * view hierarchy may be destroyed when not visible.  This can result in using
 * a significant amount of memory since fragment instances can hold on to an
 * arbitrary amount of state.  For larger sets of pages, consider
 * {@link FragmentStatePagerAdapter}.
 *
 * ...
 */

翻译:

PagerAdapter的实现类,使用将一直保留在FragmentManager中的Fragment来代表每一页,直到用户返回上一页。

当用于典型地使用多静态化的Fragment时,FragmentPagerAdapter无疑是最好使用的,例如一组tabs. 每个用户访问过的页面的Fragment都将会保留在内存中,即使它的视图层在不可见时已经被销毁。这可能导致使用比较大数量的内存,因为Fragment实例持有任意数量的状态。如果使用大数据的页面,考虑使用FragmentStatePagerAdapter.

从上面可以看出,即使是超出可视范围和缓存范围之外的Fragment,它的视图将会被销毁,但是它的实例将会保留在内存中,所以每一页的Fragment至始至终都只需要构造一次而已。通常是在主页中使用FragmentPagerAdapter, 但是超出范围的Fragment的视图会被销毁,我们也可以在Fragment中缓存View来避免状态的丢失,也可以使用另外的机制,如缓存View的状态。

@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);
}

从上面源码可以看出,当被销毁时,Fragment并没有从FragmentTransition中移除,而是调用了FragmentTransition.detach(Fragment)方法,这样销毁了Fragment的视图,但是没有移除Fragment本身。

FragmentStatePagerAdapter

/**
 * Implementation of {@link PagerAdapter} that
 * uses a {@link Fragment} to manage each page. This class also handles
 * saving and restoring of fragment's state.
 *
 * <p>This version of the pager is more useful when there are a large number
 * of pages, working more like a list view.  When pages are not visible to
 * the user, their entire fragment may be destroyed, only keeping the saved
 * state of that fragment.  This allows the pager to hold on to much less
 * memory associated with each visited page as compared to
 * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
 * switching between pages.
 *
 * ...
 */

PagerAdapter的实现类,使用Fragment来管理每一页。这个类也会管理保存和恢复Fragment的状态。

当使用一个大数量页面时,FragmentStatePagerAdapter将更加有用,工作机制类似于ListView. 当每页不再可见时,整个Fragment将会被销毁,只保留Fragment的状态。相对于FragmentPagerAdapter, 这个将允许页面持有更少的内存。

@Override
public Object instantiateItem(ViewGroup container, int position) {
    // If we already have this item instantiated, there is nothing
    // to do.  This can happen when we are restoring the entire pager
    // from its saved state, where the fragment manager has already
    // taken care of restoring the fragments we previously had instantiated.
    if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    Fragment fragment = getItem(position);
    if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
    if (mSavedState.size() > position) {
        Fragment.SavedState fss = mSavedState.get(position);
        if (fss != null) {
            fragment.setInitialSavedState(fss);
        }
    }
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    fragment.setMenuVisibility(false);
    fragment.setUserVisibleHint(false);
    mFragments.set(position, fragment);
    mCurTransaction.add(container.getId(), fragment);

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    while (mSavedState.size() <= position) {
        mSavedState.add(null);
    }
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    mFragments.set(position, null);

    mCurTransaction.remove(fragment);
}

从源码可以看出,当销毁Fragment时,缓存了Fragment的状态,并移除了Fragment的引用。而在构造时,显示判断是否已经在构造,如果是则直接返回该Fragment, 如果不是,则重新构造一个新的Fragment, 并且如果已经缓存了状态,则将改状态传入Fragment用于恢复状态。

Fragment懒加载的原理

概念:当需要时才加载,加载之后一直保持该对象。

而关于Fragment实现的PagerAdapter都没有完全保存其引用和状态。FragmentPageAdapter需要重建视图,FragmentStatePageAdapter使用状态恢复,View都被销毁,但是恢复的方式不同,而通常我们想得到的结果是,Fragment一旦被加载,其视图也不会被销毁,即不会再重新走一遍生命周期。而且ViewPager为了实现滑动效果,都是预加载左右两侧的页面。

我们通常想要实现的两种效果:不提供滑动,需要时才构造,并且只走一遍生命周期,避免在Fragment中做过多的状态保存和恢复,参考斗鱼、QQ、全名,这也是大多数APP的实现方式;提供滑动,但是如果没有真正显示在界面上,那么使用站位页面代替真正的数据页面,参考微信,使用这种实现的较少。

是否提供滑动

  • 提供滑动,当然使用ViewPager是最为方便的,但是会预加载新的页面,所以不可避免得,会提前加载数据,或是使用站位页面的方式来实现懒加载。

  • 不提供滑动,一种方式是使用ViewPager, 但是限制其不可滑动,同时在选择指定的页面时,直接跳转,而不是翻页的方式;另外的实现方式则是完全抛弃ViewPager,自己使用FragmentManager来管理Fragment, 这种方式的缺点是,离开了ViewPager,那么与之对应的indicator库也可能不能使用,所以会要求在这方面多做一些工作。

ViewPager中实现懒加载的原理

懒加载需要处理的几个问题

  • 预加载

虽然没有显示在界面上,但是当前页面的上一页和下一页的Fragment已经执行了一个Fragment能够显示在界面上的所有生命周期方法,但是我们想在跳转到该页时才真正构造数据视图和请求数据。那么我们可以使用一个占位视图,那么可以想到使用ViewStub,当真正跳转到该页时,执行ViewStub.inflate()方法,加载真正的数据视图和请求数据。

  • 视图保存

当某一页超出可视范围和预加载范围,那么它将会被销毁,FragmentStatePagerAdapter销毁整个Fragment, 我们可以自己保存该Fragment, 或使用FragmentPagerAdapterFragmentTransition来保留Fragment的引用。虽然这样,但是它的周期方法已经走完,那么我们只能手动的保存FragmentView的引用,当再次重新进入新的声明周期方法时,返回原来的View

  • 是否已经被用户所看到

其实本身而言,FragmentManager并没有提供为Fragment被用户所看到的回调方法,而是在FragmentPagerAdapterFragmentStatePagerAdapter中,调用了Fragment.setUserVisibleHint(boolean)来表明Fragment是否已经被作为primaryFragment. 所以这个方法可以被认为是一个回调方法。

懒加载实现

通常想到的,我们是在Fragment中来根据生命周期来控制视图的缓存和数据的加载来实现懒加载的效果,但是我们也可以自定义PagerAdapter来实现懒加载。

  • 重写Fragment实现懒加载
/**
 * Created by Mycroft on 2017/1/9.
 */
public abstract class LazyFragment extends Fragment {

    // Fragment的根View
    private View mRootView;

    // 检测声明周期中,是否已经构建视图
    private boolean mViewCreated = false;

    // 占位图
    private ViewStubCompat mViewStub;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (mRootView != null) {
            mViewCreated = true;
            return mRootView;
        }

        final Context context = inflater.getContext();
        FrameLayout root = new FrameLayout(context);
        mViewStub = new ViewStubCompat(context, null);
        mViewStub.setLayoutResource(getResId());
        root.addView(mViewStub, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        root.setLayoutParams(new ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.MATCH_PARENT, ViewGroup.MarginLayoutParams.MATCH_PARENT));

        mRootView = root;

        mViewCreated = true;
        if (mUserVisible) {
            realLoad();
        }
        return mRootView;
    }

    private boolean mUserVisible = false;

    @Override
    public final void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        mUserVisible = isVisibleToUser;
        if (mUserVisible && mViewCreated) {
            realLoad();
        }
    }

    // 判断是否已经加载
    private boolean mLoaded = false;

    /**
     * 控制只允许加载一次
     */
    private void realLoad() {
        if (mLoaded) {
            return;
        }

        mLoaded = true;
        onRealViewLoaded(mViewStub.inflate());
    }

    @Override
    public void onDestroyView() {
        mViewCreated = false;
        super.onDestroyView();
    }

    /**
     * 获取真正的数据视图
     *
     * @return
     */
    protected abstract int getResId();

    /**
     * 当视图真正加载时调用
     */
    protected abstract void onRealViewLoaded(View view);
}

示例可以参考FragmentApp

  • 重写PagerAdapter

重写PagerAdapter解决的是Fragment生命周期所带来的视图保存的问题。

/**
 * 和{@link android.support.v4.app.FragmentPagerAdapter}唯一的不同是
 * 使用{@link FragmentTransaction#add(int, Fragment, String)}和{@link FragmentTransaction#remove(Fragment)}
 * 来代替{@link FragmentTransaction#attach(Fragment)}和{@link FragmentTransaction#detach(Fragment)}
 * <p>
 * 这样{@link Fragment}只会走一遍生命周期
 * <p>
 * Created by Mycroft on 2017/1/9.
 */
public abstract class LazyFragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "LazyFragmentPagerAdapter";
    private static final boolean DEBUG = true;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private Fragment mCurrentPrimaryItem = null;

    public LazyFragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    /**
     * Return the Fragment associated with a specified position.
     */
    public abstract Fragment getItem(int position);

    @Override
    public void startUpdate(ViewGroup container) {
        if (container.getId() == View.NO_ID) {
            throw new IllegalStateException("ViewPager with adapter " + this
                    + " requires a view id");
        }
    }

    @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.show(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.hide((Fragment) object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

    /**
     * Return a unique identifier for the item at the given position.
     * <p>
     * <p>The default implementation returns the given position.
     * Subclasses should override this method if the positions of items can change.</p>
     *
     * @param position Position within this adapter
     * @return Unique identifier for the item at position
     */
    public long getItemId(int position) {
        return position;
    }

    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }
}

而同时,仍然需要重写Fragment来进行预加载

/**
 * Created by Mycroft on 2017/1/9.
 */
public abstract class BaseLazyFragment extends Fragment {

    // 检测声明周期中,是否已经构建视图
    private boolean mViewCreated = false;

    // 占位图
    private ViewStubCompat mViewStub;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        final Context context = inflater.getContext();
        FrameLayout root = new FrameLayout(context);
        mViewStub = new ViewStubCompat(context, null);
        mViewStub.setLayoutResource(getResId());
        root.addView(mViewStub, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        root.setLayoutParams(new ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.MATCH_PARENT, ViewGroup.MarginLayoutParams.MATCH_PARENT));

        mViewCreated = true;
        if (mUserVisible) {
            realLoad();
        }
        return root;
    }

    private boolean mUserVisible = false;

    @Override
    public final void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        mUserVisible = isVisibleToUser;
        if (mUserVisible && mViewCreated) {
            realLoad();
        }
    }

    // 判断是否已经加载
    private boolean mLoaded = false;

    /**
     * 控制只允许加载一次
     */
    private void realLoad() {
        if (mLoaded) {
            return;
        }

        mLoaded = true;
        onRealViewLoaded(mViewStub.inflate());
    }

    @Override
    public void onDestroyView() {
        mViewCreated = false;
        super.onDestroyView();
    }

    /**
     * 获取真正的数据视图
     *
     * @return
     */
    protected abstract int getResId();

    /**
     * 当视图真正加载时调用
     */
    protected abstract void onRealViewLoaded(View view);
}

LazyFragment唯一的不同是不用自己来保留根View.

示例可以参考FragmentApp

不使用ViewPager,实现懒加载的原理

不使用ViewPager就避免了预加载的问题,同时也不会有Fragment是否用户可见的问题,因为只有加载时,用户才可见。

关于这一点,已经有了开源项目,可以参考FragmentNavigator

使用示例可以参考FragmentApp

参考

FragmentNavigator

如何高效的使用ViewPager,以及FragmentPagerAdapter与FragmentStatePagerAdapter的区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容