zoukankan      html  css  js  c++  java
  • [转]ViewPager学习笔记(一)——懒加载

    在项目中ViewPager和Fragment接口框架已经是处处可见,但是在使用中,我们肯定不希望用户在当前页面时就在前后页面的数据,加入数据量很大,而用户又不愿意左右滑动浏览,那么这时候ViewPager中本来充满善意的预加载就有点令人不爽了。我们能做的就是屏蔽掉ViewPager的预加载机制。虽然ViewPager中提供的有setOffscreenPageLimit()来控制其预加载的数目,但是当设置为0后我们发现其根本没效果,这个的最小值就是1,也就是你只能最少前后各预加载一页。那么,这时候就得另觅方法了。

    以下三种方法各有千秋,可结合不同场景使用。

    方法一

    在Fragment可见时请求数据。此方案仍预加载了前后的页面,但是没有请求数据,只有进入到当前Framgent时才请求数据。

    优点:实现了数据的懒加载

    缺点:一次仍是三个Framgment对象,不是完全意义的懒加载。

    public class FragmentSample extends Fragment{    @Override
         public void setUserVisibleHint(boolean isVisibleToUser) {
             super.setUserVisibleHint(isVisibleToUser);
             if (isVisibleToUser) {
                 requestData(); // 在此请求数据
             }
         }
         ...
     }

    方法二

    直接修改ViewPager源码。通过查看ViewPager源码可知,控制其预加载的是一个常量

    DEFAULT_OFFSCREEN_PAGES,其默认值为1,表示当前页面前后各预加载一个页面,在这里我们直接将其设置为0即可,即去掉预加载。但是,这样有一个问题,那就是在使用其他控件时需要传入ViewPager时,这个就不能用了。

    优点:完全屏蔽掉了预加载

    缺点:应用太受限制,比如使用ViewPagerIndicator时需要传入ViewPager对象,这时傻眼了。

    // 注意,这是直接拷贝的ViewPager的源码,只修改了注释处的代码
    2.public class LazyViewPager extends ViewGroup {
    3.    private static final String TAG = "LazyViewPager";
    4.    private static final boolean DEBUG = false;
    5.
    6.    private static final boolean USE_CACHE = false;
    7.
    8.     // 默认为1,即前后各预加载一个页面,设置为0去掉预加载
    9.      private static final int DEFAULT_OFFSCREEN_PAGES = 0;
    10.
    11.    private static final int MAX_SETTLE_DURATION = 600; // ms
    12.
    13.    static class ItemInfo {
    14.        Object object;
    15.        int position;
    16.        boolean scrolling;
    17.    }
    18.
    19.    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
    20.        @Override
    21.        public int compare(ItemInfo lhs, ItemInfo rhs) {
    22.            return lhs.position - rhs.position;
    23.        }
    24.    };
    25.      ............
    26.}

    方法三

    直接继承ViewPager,结合PagerAdapter实现懒加载。该方案是我用到的最完善的方法,完全的懒加载,每次只会建立一个Fragment对象。

    优点:完全屏蔽预加载

    缺点:稍微复杂,但是人家已经造好的轮子,直接用吧,很简洁

    开源库:https://github.com/lianghanzhen/LazyViewPager

    这个库就4个类,作者通过继承ViewPager(保证其普适性)、自定义ViewPagerAdapter和 LazyFragmentPagerAdapter以及设置懒加载的标记接口,很好的实现了懒加载。感谢作者。

    在此贴出关键代码,有兴趣的同学可以学习下。

    LazyViewPager:

    public class LazyViewPager extends ViewPager {
    2.
    3.    private static final float DEFAULT_OFFSET = 0.5f;
    4.
    5.    private LazyPagerAdapter mLazyPagerAdapter;
    6.    private float mInitLazyItemOffset = DEFAULT_OFFSET;
    7.
    8.    public LazyViewPager(Context context) {
    9.        super(context);
    10.    }
    11.
    12.    public LazyViewPager(Context context, AttributeSet attrs) {
    13.        super(context, attrs);
    14.
    15.        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LazyViewPager);
    16.        setInitLazyItemOffset(a.getFloat(R.styleable.LazyViewPager_init_lazy_item_offset, DEFAULT_OFFSET));
    17.        a.recycle();
    18.    }
    19.
    20.    /**
    21.     * change the initLazyItemOffset
    22.     * @param initLazyItemOffset set mInitLazyItemOffset if {@code 0 < initLazyItemOffset <= 1}
    23.     */
    24.    public void setInitLazyItemOffset(float initLazyItemOffset) {
    25.        if (initLazyItemOffset > 0 && initLazyItemOffset <= 1) {
    26.            mInitLazyItemOffset = initLazyItemOffset;
    27.        }
    28.    }
    29.
    30.    @Override
    31.    public void setAdapter(PagerAdapter adapter) {
    32.        super.setAdapter(adapter);
    33.        mLazyPagerAdapter = adapter != null && adapter instanceof LazyPagerAdapter ? (LazyPagerAdapter) adapter : null;
    34.    }
    35.
    36.    @Override
    37.    protected void onPageScrolled(int position, float offset, int offsetPixels) {
    38.        if (mLazyPagerAdapter != null) {
    39.            if (getCurrentItem() == position) {
    40.                int lazyPosition = position + 1;
    41.                if (offset >= mInitLazyItemOffset && mLazyPagerAdapter.isLazyItem(lazyPosition)) {
    42.                    mLazyPagerAdapter.startUpdate(this);
    43.                    mLazyPagerAdapter.addLazyItem(this, lazyPosition);
    44.                    mLazyPagerAdapter.finishUpdate(this);
    45.                }
    46.            } else if (getCurrentItem() > position) {
    47.                int lazyPosition = position;
    48.                if (1 - offset >= mInitLazyItemOffset && mLazyPagerAdapter.isLazyItem(lazyPosition)) {
    49.                    mLazyPagerAdapter.startUpdate(this);
    50.                    mLazyPagerAdapter.addLazyItem(this, lazyPosition);
    51.                    mLazyPagerAdapter.finishUpdate(this);
    52.                }
    53.            }
    54.        }
    55.        super.onPageScrolled(position, offset, offsetPixels);
    56.    }
    57}
    public abstract class LazyFragmentPagerAdapter extends LazyPagerAdapter<Fragment> {
    2.
    3.    private static final String TAG = "LazyFragmentPagerAdapter";
    4.    private static final boolean DEBUG = false;
    5.
    6.    private final FragmentManager mFragmentManager;
    7.    private FragmentTransaction mCurTransaction = null;
    8.
    9.    public LazyFragmentPagerAdapter(FragmentManager fm) {
    10.        mFragmentManager = fm;
    11.    }
    12.
    13.    @Override
    14.    public void startUpdate(ViewGroup container) {
    15.    }
    16.
    17.    @Override
    18.    public Object instantiateItem(ViewGroup container, int position) {
    19.        if (mCurTransaction == null) {
    20.            mCurTransaction = mFragmentManager.beginTransaction();
    21.        }
    22.
    23.        final long itemId = getItemId(position);
    24.
    25.        // Do we already have this fragment?
    26.        String name = makeFragmentName(container.getId(), itemId);
    27.        Fragment fragment = mFragmentManager.findFragmentByTag(name);
    28.        if (fragment != null) {
    29.            if (DEBUG)
    30.                Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
    31.            mCurTransaction.attach(fragment);
    32.        } else {
    33.            fragment = getItem(container, position);
    34.            if (fragment instanceof Laziable) {
    35.                mLazyItems.put(position, fragment);
    36.            } else {
    37.                mCurTransaction.add(container.getId(), fragment, name);
    38.            }
    39.        }
    40.        if (fragment != getCurrentItem()) {
    41.            fragment.setMenuVisibility(false);
    42.            fragment.setUserVisibleHint(false);
    43.        }
    44.
    45.        return fragment;
    46.    }
    47.
    48.    @Override
    49.    public void destroyItem(ViewGroup container, int position, Object object) {
    50.        if (mCurTransaction == null) {
    51.            mCurTransaction = mFragmentManager.beginTransaction();
    52.        }
    53.        if (DEBUG)
    54.            Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment) object).getView());
    55.
    56.        final long itemId = getItemId(position);
    57.        String name = makeFragmentName(container.getId(), itemId);
    58.        if (mFragmentManager.findFragmentByTag(name) == null) {
    59.            mCurTransaction.detach((Fragment) object);
    60.        } else {
    61.            mLazyItems.remove(position);
    62.        }
    63.    }
    64.
    65.    @Override
    66.    public Fragment addLazyItem(ViewGroup container, int position) {
    67.        Fragment fragment = mLazyItems.get(position);
    68.        if (fragment == null)
    69.            return null;
    70.
    71.        final long itemId = getItemId(position);
    72.        String name = makeFragmentName(container.getId(), itemId);
    73.        if (mFragmentManager.findFragmentByTag(name) == null) {
    74.            if (mCurTransaction == null) {
    75.                mCurTransaction = mFragmentManager.beginTransaction();
    76.            }
    77.            mCurTransaction.add(container.getId(), fragment, name);
    78.            mLazyItems.remove(position);
    79.        }
    80.        return fragment;
    81.    }
    82.
    83.    @Override
    84.    public void finishUpdate(ViewGroup container) {
    85.        if (mCurTransaction != null) {
    86.            mCurTransaction.commitAllowingStateLoss();
    87.            mCurTransaction = null;
    88.            mFragmentManager.executePendingTransactions();
    89.        }
    90.    }
    91.
    92.    @Override
    93.    public boolean isViewFromObject(View view, Object object) {
    94.        return ((Fragment) object).getView() == view;
    95.    }
    96.
    97.    public long getItemId(int position) {
    98.        return position;
    99.    }
    100.
    101.    private static String makeFragmentName(int viewId, long id) {
    102.        return "android:switcher:" + viewId + ":" + id;
    103.    }
    104.
    105.    /**
    106.     * mark the fragment can be added lazily
    107.     */
    108.    public interface Laziable {
    109.   }
    110.
    111.}

    最后提醒一下:填充LazyViewPager的Fragment一定要实现接口LazyFragmentPagerAdapter.Laziable。

    =========================================================================================

    我们在做应用开发的时候,一个Activity里面可能会以viewpager(或其他容器)与多个Fragment来组合使用,而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源。这样的结果,我们当然不会满意。那么,能不能做到当切换到这个fragment的时候,它才去初始化呢?

    答案就在Fragment里的setUserVisibleHint这个方法里。该方法用于告诉系统,这个Fragment的UI是否是可见的。所以我们只需要继承Fragment并重写该方法,即可实现在fragment可见时才进行数据加载操作,即Fragment的懒加载。
    代码如下:

    public abstract class LazyFragment extends Fragment {
    12.    protected boolean isVisible;
    13.    /**
    14.     * 在这里实现Fragment数据的缓加载.
    15.     * @param isVisibleToUser
    16.     */
    17.    @Override
    18.    public void setUserVisibleHint(boolean isVisibleToUser) {
    19.        super.setUserVisibleHint(isVisibleToUser);
    20.        if(getUserVisibleHint()) {
    21.            isVisible = true;
    22.            onVisible();
    23.        } else {
    24.            isVisible = false;
    25.            onInvisible();
    26.        }
    27.    }
    28.    protected void onVisible(){
    29.        lazyLoad();
    30.    }
    31.    protected abstract void lazyLoad();
    32.    protected void onInvisible(){}
    33.}

    在LazyFragment,我增加了三个方法,一个是onVisiable,即fragment被设置为可见时调用,一个是onInvisible,即fragment被设置为不可见时调用。另外再写了一个lazyLoad的抽象方法,该方法在onVisible里面调用。你可能会想,为什么不在getUserVisibleHint里面就直接调用呢?

    我这么写是为了代码的复用。因为在fragment中,我们还需要创建视图(onCreateView()方法),可能还需要在它不可见时就进行其他小量的初始化操作(比如初始化需要通过AIDL调用的远程服务)等。而setUserVisibleHint是在onCreateView之前调用的,那么在视图未初始化的时候,在lazyLoad当中就使用的话,就会有空指针的异常。而把lazyLoad抽离成一个方法,那么它的子类就可以这样做:

    public class OpenResultFragment extends LazyFragment{
    2.    // 标志位,标志已经初始化完成。
    3.    private boolean isPrepared;
    4.    @Override
    5.    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    6.        Log.d(LOG_TAG, "onCreateView");
    7.        View view = inflater.inflate(R.layout.fragment_open_result, container, false);
    8.        //XXX初始化view的各控件
    9.    isPrepared = true;
    10.        lazyLoad();
    11.        return view;
    12.    }
    13.    @Override
    14.    protected void lazyLoad() {
    15.        if(!isPrepared || !isVisible) {
    16.            return;
    17.        }
    18.        //填充各控件的数据
    19.    }
    20.}

    在上面的类当中,我们增加了一个标志位isPrepared,用于标志是否初始化完成。然后在我们所需要的初始化操作完成之后调用,如上面的例子当中,在初始化view之后,设置 isPrepared为true,同时调用lazyLoad()方法。而在lazyLoad()当中,判断isPrepared和isVisible只要有一个不为true就不往下执行。也就是仅当初始化完成,并且可见的时候才继续加载,这样的避免了未初始化完成就使用而带来的问题。

  • 相关阅读:
    js数据类型转换
    html5的onhashchange和history历史管理
    Javascript语言精粹-毒瘤和糟粕
    [夏天Object]运行时程序执行的上下文堆栈(一)
    [Object]继承(经典版)(五)封装
    [Object]继承(经典版)(四)多重继承和组合继承
    flex 弹性布局的大坑!!
    带视觉差的轮播图
    不用循环的数组求和
    CSS3盒模型display:box简述
  • 原文地址:https://www.cnblogs.com/DarrenChan/p/5774919.html
Copyright © 2011-2022 走看看