类微信底部Tab+ViewPager延缓加载实现

效果分析

    Android APP采用底部tab切换效果越来越多,像微信,QQ, 支付宝等巨型APP都是采用这种设计。我们今天要介绍的就是基于这样的设计的一种内存优化方案 - ViewPager延迟加载。先看看微信的效果吧。



滑动过程中看到的是正在加载...,等切换完成才会开始加载页面内容。
android ViewPager默认加载当前页面的前一张和后一张,一般情况下会初始化三个界面,先不考虑三个页面同时加载导致的线程网络阻塞的问题,用户打开app并不需要马上初始化不可见的页面,这部分内存是可以优化的,可见的时候才开始加载内容,这样尽量减少内存的占用。

原理分析和实现效果

    我们采用ViewPager + FragmentPagerAdapter来实现底部Tag切换的效果,要实现微信这种效果首先需要在不可见时文本占位提示正在加载..., 其次需要获取到当前页面可见与不可见的事件监听。在可见的时候加载内容。这两条都可以通过重写ViewPager来实现,但是比较麻烦,有兴趣的朋友可以去尝试下。我们这里介绍一种更简单的方案,通过重写Fragment的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: 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 UI是否可见,获取是否可见用getUserVisibleHint()方法

主要代码分析

BaseLazyFragment.java,所有需要延迟加载的类继承这个类
提供四个回调函数,onFirstUserVisible,onFirstUserInvisible, onUserVisible, onUserInvisible

public abstract class BaseLazyFragment extends Fragment {  
    private boolean isPrepared;
    private boolean isFirstResume = true;
    private boolean isFirstVisible = true;
    private boolean isFirstInvisible = true;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initPrepare();
    }

    private synchronized void initPrepare() {
        if (isPrepared) {
            onFirstUserVisible();
        } else {
            isPrepared = true;
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (isFirstResume) {
            isFirstResume = false;
            return;
        }
        if (getUserVisibleHint()) {
            onUserVisible();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (getUserVisibleHint()) {
            onUserInvisible();
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            if (isFirstVisible) {
                isFirstVisible = false;
                initPrepare();
            } else {
                onUserVisible();
            }
        } else {
            if (isFirstInvisible) {
                isFirstInvisible = false;
                onFirstUserInvisible();
            } else {
                onUserInvisible();
            }
        }
    }

    /**
     * 第一次fragment可见(进行初始化工作)
     */
    public void onFirstUserVisible() {

    }

    /**
     * 第一次fragment不可见(不建议在此处理事件)
     */
    public void onFirstUserInvisible() {

    }

    /**
     * fragment可见(切换回来或者onResume)
     */
    public void onUserVisible() {

    }

    /**
     * fragment不可见(切换掉或者onPause)
     */
    public void onUserInvisible() {

    }
}

MainTabFragment.java 默认显示占位文本内容,当界面可见时初始化布局

public abstract class MainTabFragment extends BaseLazyFragment {

    private ViewStub viewStub;
    private View emptyView;
    private View mView;
    private boolean viewPrepared;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_main_tab, null);
        viewStub = (ViewStub) root.findViewById(R.id.viewStub);
        emptyView = root.findViewById(R.id.tv_empty);
        return root;
    }

    @Override
    public void onFirstUserVisible() {
        super.onFirstUserVisible();
        if (getLayout() <= 0) {
            throw new Resources.NotFoundException("layout not found");
        }
        viewStub.setLayoutResource(getLayout());
        emptyView.setVisibility(View.GONE);
        mView = viewStub.inflate();
        if (mView == null) {
            return;
        }
        initView(mView);
        viewPrepared = true;
    }

    public boolean isViewPrepared() {
        return  getRootView() != null && viewPrepared;
    }

    /**
     * 初始化View
     * @param mView
     */
    protected void initView(View mView) {

    }

    public View getRootView() {
        return mView;
    }

    protected abstract int getLayout();
}

效果预览



Github源码下载