2017-7-14更新: 目前有人使用后出现了诸如首次打开显示空白界面,但点击有反应;或来回切换又变空白界面的问题。这些问题我暂时还不知道该怎么解决,后期有时间时会具体去分析下问题该怎么解决。因此你若是要使用该代码,但愿考虑一下,我本身的小应用目前是没碰到这些问题。git
老规矩,先来看看效果图github
演示缓存
log服务器
没错,我又入坑了,又从新作了个 Gank 客户端,由于以前那个代码写得太烂了,此次有好好的考虑了下架构之类的事,代码应该会更容易读懂了点了,吧。哈哈,再次欢迎来 star 交流哈。架构
上面的截图里有注释解析了,稍微认真点看看 log 的内容哈,看看是否是你须要的需求。ide
若是想直接看代码,直接跳到最下面的代码部分和使用介绍便可,若是感兴趣,能够慢慢往下看看个人唠叨。函数
以前写过一篇 Fragment懒加载和ViewPager的坑,里面分析了 Fragment 结合 ViewPager 使用时会碰到的一些状况,以及为何要用懒加载,如何用,感兴趣的也能够再回去看看。布局
后来发现,我在那篇博客里封装的 Fragment 基类不足以知足你们的懒加载需求,因此决定从新来封装一次,此次封装的支持如下的功能:ui
第一点应该是比较须要且经常使用的一点,以前那篇博客里没有考虑到这点应用场景是个人疏忽。稍微讲解一下,有些时候,咱们打开一个 Fragment 页面时,但愿它是在可见时才去加载数据,也就是不要在后台就开始加载数据,并且,咱们也但愿加载数据的操做只是第一次打开该 Fragment 时才进行的操做,之后若是再从新打开该 Fragment 的话,就不要再重复的去加载数据了。spa
具体点说,Fragment 和 ViewPager 一块儿用时,因为 ViewPager 的缓存机制,在打开一个 Fragment 时,它旁边的几个 Fragment 其实也已经被建立了,若是咱们是在 Fragment 的 onCreat()
或者 onCreateView()
里去跟服务器交互,下载界面数据,那么这时这些已经被建立的 Fragment,就都会出如今后台下载数据的状况了。因此咱们一般须要在 setUserVisibleHint()
里去判断当前 Fragment 是否可见,可见时再去下载数据,可是这样仍是会出现一个问题,就是每次可见时都会重复去下载数据,咱们但愿的是只有第一次可见时才须要去下载,那么就还须要再作一些判断。这就是要封装个基类来作这些事了,具体代码见后面。
即便咱们在 setUserVisibleHint()
作了不少判断,实现了可见时加载而且只有第一次可见时才加载,可能仍是会遇到其余问题。好比说,我下载完数据就直接须要对 ui 进行操做,将数据展现出来,但有时却报了 ui 控件 null 异常,这是由于 setUserVisibleHint()
有可能在 onCreateView()
建立 view 以前调用,并且数据加载时间很短,这就可能出现 null 异常了,那么咱们还须要再去作些判断,保证在数据下载完后 ui 控件已经建立完成。
除了懒加载,只加载一次的需求外,可能咱们还须要每次 Fragment 的打开或关闭时显示数据加载进度。对吧,咱们打开一个 Fragment 时,若是数据还没下载完,那么应该给个下载进度或者加载框提示,若是这个时候打开了新的 Fragment 页面,而后又从新返回时,若是数据还没加载完,那么也还应该继续给提示,对吧。这就须要有个 Fragment 可见与不可见时触发的回调方法,而且该方法还得保证是在 view 建立完后才触发的,这样才能支持对 ui 进行操做。
以上,就是咱们封装的 BaseFragment 基类要干的活了。下面上代码。
/** * Created by dasu on 2016/9/27. * * Fragment基类,封装了懒加载的实现 * * 一、Viewpager + Fragment状况下,fragment的生命周期因Viewpager的缓存机制而失去了具体意义 * 该抽象类自定义新的回调方法,当fragment可见状态改变时会触发的回调方法,和 Fragment 第一次可见时会回调的方法 * * @see #onFragmentVisibleChange(boolean) * @see #onFragmentFirstVisible() */ public abstract class BaseFragment extends Fragment { private static final String TAG = BaseFragment.class.getSimpleName(); private boolean isFragmentVisible; private boolean isReuseView; private boolean isFirstVisible; private View rootView; //setUserVisibleHint()在Fragment建立时会先被调用一次,传入isVisibleToUser = false //若是当前Fragment可见,那么setUserVisibleHint()会再次被调用一次,传入isVisibleToUser = true //若是Fragment从可见->不可见,那么setUserVisibleHint()也会被调用,传入isVisibleToUser = false //总结:setUserVisibleHint()除了Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调 //若是咱们须要在 Fragment 可见与不可见时干点事,用这个的话就会有多余的回调了,那么就须要从新封装一个 @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); //setUserVisibleHint()有可能在fragment的生命周期外被调用 if (rootView == null) { return; } if (isFirstVisible && isVisibleToUser) { onFragmentFirstVisible(); isFirstVisible = false; } if (isVisibleToUser) { onFragmentVisibleChange(true); isFragmentVisible = true; return; } if (isFragmentVisible) { isFragmentVisible = false; onFragmentVisibleChange(false); } } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initVariable(); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { //若是setUserVisibleHint()在rootView建立前调用时,那么 //就等到rootView建立完后才回调onFragmentVisibleChange(true) //保证onFragmentVisibleChange()的回调发生在rootView建立完成以后,以便支持ui操做 if (rootView == null) { rootView = view; if (getUserVisibleHint()) { if (isFirstVisible) { onFragmentFirstVisible(); isFirstVisible = false; } onFragmentVisibleChange(true); isFragmentVisible = true; } } super.onViewCreated(isReuseView ? rootView : view, savedInstanceState); } @Override public void onDestroyView() { super.onDestroyView(); } @Override public void onDestroy() { super.onDestroy(); initVariable(); } private void initVariable() { isFirstVisible = true; isFragmentVisible = false; rootView = null; isReuseView = true; } /** * 设置是否使用 view 的复用,默认开启 * view 的复用是指,ViewPager 在销毁和重建 Fragment 时会不断调用 onCreateView() -> onDestroyView() * 之间的生命函数,这样可能会出现重复建立 view 的状况,致使界面上显示多个相同的 Fragment * view 的复用其实就是指保存第一次建立的 view,后面再 onCreateView() 时直接返回第一次建立的 view * * @param isReuse */ protected void reuseView(boolean isReuse) { isReuseView = isReuse; } /** * 去除setUserVisibleHint()多余的回调场景,保证只有当fragment可见状态发生变化时才回调 * 回调时机在view建立完后,因此支持ui操做,解决在setUserVisibleHint()里进行ui操做有可能报null异常的问题 * * 可在该回调方法里进行一些ui显示与隐藏,好比加载框的显示和隐藏 * * @param isVisible true 不可见 -> 可见 * false 可见 -> 不可见 */ protected void onFragmentVisibleChange(boolean isVisible) { } /** * 在fragment首次可见时回调,可在这里进行加载数据,保证只在第一次打开Fragment时才会加载数据, * 这样就能够防止每次进入都重复加载数据 * 该方法会在 onFragmentVisibleChange() 以前调用,因此第一次打开时,能够用一个全局变量表示数据下载状态, * 而后在该方法内将状态设置为下载状态,接着去执行下载的任务 * 最后在 onFragmentVisibleChange() 里根据数据下载状态来控制下载进度ui控件的显示与隐藏 */ protected void onFragmentFirstVisible() { } protected boolean isFragmentVisible() { return isFragmentVisible; } }
使用很简单,新建你须要的 Fragment 类继承自该 BaseFragment,而后重写两个回调方法,根据你的须要在回调方法里进行相应的操做好比下载数据等便可。
例如:
public class CategoryFragment extends BaseFragment { private static final String TAG = CategoryFragment.class.getSimpleName(); @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_category, container, false); initView(view); return view; } @Override protected void onFragmentVisibleChange(boolean isVisible) { if (isVisible) { //更新界面数据,若是数据还在下载中,就显示加载框 notifyDataSetChanged(); if (mRefreshState == STATE_REFRESHING) { mRefreshListener.onRefreshing(); } } else { //关闭加载框 mRefreshListener.onRefreshFinish(); } } @Override protected void onFragmentFirstVisible() { //去服务器下载数据 mRefreshState = STATE_REFRESHING; mCategoryController.loadBaseData(); } }
若是想要让 fragment 的布局复用成功,须要重写 viewpager 的适配器里的 destroyItem()
方法,将 super 去掉,也就是不销毁 view。
若是出现切换回来或不相邻的Tab切换时致使空白界面的问题,解决方法:在 onCreateView中复用布局 + ViewPager 的适配器中复写 destroyItem() 方法去掉 super。
最后,继续不要脸的贴上我最近在作的 Gank 客户端的项目地址啦,项目没引入什么高级的库,都是用的最基本的代码实现的,项目也按模块来划分,也尽量的实现ui和逻辑的划分,各模块也严格控制权限,尽可能让模块之间,类之间的耦合减小些,之因此这样是为了后面更深刻理解mvp作准备,总之,代码应该仍是很容易能够看懂的吧,欢迎你们star交流。
GanHuo:https://github.com/woshidasusu/GanHuo
Meizi:https://github.com/woshidasusu/Meizi
GanHuo
QQ图片20180316094923.jpg
最近刚开通了公众号,想激励本身坚持写做下去,初期主要分享原创的Android或Android-Tv方面的小知识,感兴趣的能够点一波关注,谢谢支持~~
做者:请叫我大苏 连接:https://www.jianshu.com/p/254dc5ddffea 來源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。