Android仿微信Viewpager-Fragment惰性加载(lazy-loading)

前言

今天起床,拿起手机开机第一时间当然是打开微信了,左右滑动Viewpager,发现它使用了一种叫惰性加载,或者说懒加载(lazy-loading)的方式加载Viewpager中的Fragment。效果如图:

什么是lazy-loading呢?顾名思义就是在必要的时候才加载,否则不进行View的绘制和数据的加载。原因是Viewpager一次只会显示一个页卡,那么刚开始的时候,只需加载第一张Fragment页卡,其他的不加载,当用户向右滑动切换再进行加载。因为其他Fragment对于用户来说是不可见的,如果一开始就把全部Fragment一起加载,可能造成启动时卡顿的问题,更重要的是可能白白耗费用户的流量,因为用户可能并不需要其他Fragment的信息。

今天Google了有关Fragment惰性加载的资料,并没有找到介绍得清楚详细的博文+demo。所以我找到了Github上的一个开源项目demo里有关惰性加载的代码,学习了这个知识点,并把它整理出来分享给大家。

你应该知道

你应该知道viewPager.setOffscreenPageLimit();方法。该方法设置ViewPager允许有多少张pages存在于屏幕外(不包括正在显示的page),默认值是1。在范围之外的pages 的View会被销毁,即onDestroyView()会被执行。

Viewpager里面FragmentPagerAdapter、FragmentStatePagerAdapter的区别:

1.FragmentPagerAdapter会将每一个生成的Fragment都放到内存中,即无论怎么滑动切换ViewPager,都不会有一个Fragment的onDestroy方法被调用。但Fragment不在viewPager.setOffscreenPageLimit(3);保护的范围内会调用FragmentManager的detach()方法,相应的Fragment的onDestroyView会执行,但Fragment实例仍在!所以该类适用于需要展示的Fragment比较少的情况。

2.FragmentStateAdapter 有点类似于LIstview的RecyclerBin机制,当Fragment不在viewPager.setOffscreenPageLimit(3);保护的范围内,Fragment的就会被销毁,onDestroy()、onDetach()方法会被执行。适用于要展示Fragment数量比较多,Fragment的子View和数据量复杂的情况。
熟知Fragment的生命周期。

Fragment的生命周期

3.刚被new出来的Fragment并没有开始它的生命周期,当它被添加到FragmentManager时生命周期才开始。

4.我们通常是在onCreateView()中对Fragment完成视图的构建。若是要实现延迟加载,可以在调用onCreateView时获得一个空container的引用。当等待用户切换到屏幕的时候,开始加载数据和视图。

那么如何得知我们的Fragment何时被切换到屏幕呢?核心方法就是getUserVisibleHint()和在Fragment中重写setUserVisibleHint(boolean isVisibleToUser){…}方法。

在官方文档是这样描述该方法的:

public void setUserVisibleHint (boolean isVisibleToUser)
Added in API level 15
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.
Parameters
isVisibleToUser true if this fragment's UI is currently visible to the user (default), false if it is not.

该方法的作用是设置一个提示或者标志,该标志代表的是Fragment在当前是否处于对用户的可见状态。注意这里的可见并不能与Activity或Fragment的onStart或者onResume混淆。因为Fragment处于onResume状态并不代表它对用户是可见的!仍觉得很困惑?那我们一起来Log一下吧。

我们把生命周期回调方法加了Log语句。

  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    Log.d("TAG", "setUserVisibleHint() called with: " + "isVisibleToUser = [" + isVisibleToUser + "]");
  }

我们允许有4张页卡的缓存,因为微信是有4个tab的。这样ViewPager来回切换就不会有页卡被销毁了。

viewPager.setOffscreenPageLimit(3);

启动ViewPager后的Log:

D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [true]
D/TAG: onCreateView() : getUserVisibleHint():true
D/TAG: onStart() : getUserVisibleHint():true
D/TAG: onResume() : getUserVisibleHint():true
D/TAG: onCreateView() : getUserVisibleHint():false
D/TAG: onCreateView() : getUserVisibleHint():false
D/TAG: onCreateView() : getUserVisibleHint():false
D/TAG: onStart() : getUserVisibleHint():false
D/TAG: onResume() : getUserVisibleHint():false
D/TAG: onStart() : getUserVisibleHint():false
D/TAG: onResume() : getUserVisibleHint():false
D/TAG: onStart() : getUserVisibleHint():false
D/TAG: onResume() : getUserVisibleHint():false

从Log中,可得知,setUserVisibleHint()比onCreateView()先调用,并且只有一个方法的isVisbleToUser==true。由此我们可以断定,正在展示的fragment对应的isVisibleToUser才为true。我们现在有4个page,onCreateView()、onStart()、onResume()分别共调用了4次,由此可知,尽管Fragment没有被展示,ViewPager也会将它们构建起来,会回调onStart、onResume。那么ViewPager初始化时构建Fragment的个数与什么有关呢?这个主要跟使用的Adapter类型和setOffscreenPageLimit()有关。

接下来向右滑,切换到第二页,Log如下:

D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [true]

这次只会调用两次setUserVisibleHint(),将要刚刚显示的Fragment的isVisibleToUser 设置为false,并把将要显示的Fragment的isVisibleToUser 设置为true。

当我退出程序,Log如下:

D/TAG: onPause() : getUserVisibleHint():true
D/TAG: onPause() : getUserVisibleHint():false
D/TAG: onPause() : getUserVisibleHint():false
D/TAG: onPause() : getUserVisibleHint():false
D/TAG: onStop() called: getUserVisibleHint():true
D/TAG: onStop() called: getUserVisibleHint():false
D/TAG: onStop() called: getUserVisibleHint():false
D/TAG: onStop() called: getUserVisibleHint():false
D/TAG: onDestroyView() : getUserVisibleHint():true
D/TAG: onDestroy() :
D/TAG: onDetach() :
D/TAG: onDestroyView() : getUserVisibleHint():false
D/TAG: onDestroy() :
D/TAG: onDetach() :
D/TAG: onDestroyView() : getUserVisibleHint():false
D/TAG: onDestroy() :
D/TAG: onDetach() :
D/TAG: onDestroyView() : getUserVisibleHint():false
D/TAG: onDestroy() :
D/TAG: onDetach() :

从这“死亡日志”中,我们发现,getUserVisibleHint()贯穿着Fragment的凋亡生命线。
到此,对这个关键的方法,我们算是有了一个宏观的认识。

具体实现

那么具体应该怎么实现呢?我们可以在自定义一个抽象类LazyFragment,重写onCreateView()方法,只返回一个简单的,甚至是空的(不是null)的ViewGroup作为Container,比如return new FrameLayout();当然这个ViewGroup我们需要保存为成员变量。接下来重写setUserVisibleHint(boolean isVisibleToUser)方法,如果该Fragment处于用户可见状态,就会调用该方法,并传过来的isVisibleToUser==true。所以根据这个hint做一个判断,若等于true,立即加载原本要正常显示的视图和数据。当然这个方法可以作为一个抽象方法交给子类去实现。具体的实现就是这样!Talk is simple,show you the code!

LazyFragment:

省去了一些,回调方法,只给出了核心的几个方法,完整的可以看文章末尾的项目源码。注释已经写得相对完善,如果有不明白的地方欢迎评论留言。

public class LazyFragment extends BaseFragment {
 private boolean isInit = false;//真正要显示的View是否已经被初始化(正常加载)
 private Bundle savedInstanceState;
 public static final String INTENT_BOOLEAN_LAZYLOAD = "intent_boolean_lazyLoad";
 private boolean isLazyLoad = true;
 private FrameLayout layout;
 private boolean isStart = false;//是否处于可见状态,in the screen

 @Deprecated
 protected final void onCreateView(Bundle savedInstanceState) {
  Log.d("TAG", "onCreateView() : " + "getUserVisibleHint():" + getUserVisibleHint());
  super.onCreateView(savedInstanceState);
  Bundle bundle = getArguments();
  if (bundle != null) {
   isLazyLoad = bundle.getBoolean(INTENT_BOOLEAN_LAZYLOAD, isLazyLoad);
  }
  //判断是否懒加载
  if (isLazyLoad) {
   //处于完全可见、没被初始化的状态,调用onCreateViewLazy显示内容
   if (getUserVisibleHint() && !isInit) {
    this.savedInstanceState = savedInstanceState;
    onCreateViewLazy(savedInstanceState);
    isInit = true;
   } else {
    //进行懒加载
    layout = new FrameLayout(getApplicationContext());
    layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.fragment_lazy_loading, null);
    layout.addView(view);
    super.setContentView(layout);
   }
  } else {
   //不需要懒加载,开门江山,调用onCreateViewLazy正常加载显示内容即可
   onCreateViewLazy(savedInstanceState);
   isInit = true;
  }
 }

 @Override
 public void setUserVisibleHint(boolean isVisibleToUser) {
  super.setUserVisibleHint(isVisibleToUser);
  Log.d("TAG", "setUserVisibleHint() called with: " + "isVisibleToUser = [" + isVisibleToUser + "]");
  //一旦isVisibleToUser==true即可对真正需要的显示内容进行加载

  //可见,但还没被初始化
  if (isVisibleToUser && !isInit && getContentView() != null) {
   onCreateViewLazy(savedInstanceState);
   isInit = true;
   onResumeLazy();
  }
  //已经被初始化(正常加载)过了
  if (isInit && getContentView() != null) {
   if (isVisibleToUser) {
    isStart = true;
    onFragmentStartLazy();
   } else {
    isStart = false;
    onFragmentStopLazy();
   }
  }
 }

 @Override
 public void setContentView(int layoutResID) {
  //判断若isLazyLoad==true,移除所有lazy view,加载真正要显示的view
  if (isLazyLoad && getContentView() != null && getContentView().getParent() != null) {
   layout.removeAllViews();
   View view = inflater.inflate(layoutResID, layout, false);
   layout.addView(view);
  }
  //否则,开门见山,直接加载
  else {
   super.setContentView(layoutResID);
  }
 }

 @Override
 public void setContentView(View view) {
  //判断若isLazyLoad==true,移除所有lazy view,加载真正要显示的view
  if (isLazyLoad && getContentView() != null && getContentView().getParent() != null) {
   layout.removeAllViews();
   layout.addView(view);
  }
  //否则,开门见山,直接加载
  else {
   super.setContentView(view);
  }
 }
}

具体的实现类:

public class MoreFragment extends LazyFragment {
 private TextView tvLoading;
 private ImageView ivContent;
 private int tabIndex;
 public static final String INTENT_INT_INDEX="index";

 public static MoreFragment newInstance(int tabIndex) {

  Bundle args = new Bundle();
  args.putInt(INTENT_INT_INDEX, tabIndex);
  MoreFragment fragment = new MoreFragment();
  fragment.setArguments(args);
  return fragment;
 }
 @Override
 protected void onCreateViewLazy(Bundle savedInstanceState) {
  super.onCreateViewLazy(savedInstanceState);
  setContentView(R.layout.fragment_tabmain_item);
  tabIndex = getArguments().getInt(INTENT_INT_INDEX);
  ivContent = (ImageView) findViewById(R.id.iv_content);
  tvLoading = (TextView) findViewById(R.id.tv_loading);
  getData();
 }

 private void getData() {
  new Thread(new Runnable() {
   @Override
   public void run() {
    //异步处理加载数据
    //...
    //完成后,通知主线程更新UI
    handler.sendEmptyMessageDelayed(1, 2000);
   }
  }).start();
 }

 @Override
 public void onDestroyViewLazy() {
  super.onDestroyViewLazy();
  handler.removeMessages(1);
 }

 private Handler handler = new Handler() {
  public void handleMessage(android.os.Message msg) {
   tvLoading.setVisibility(View.GONE);
   int id=0;
   switch (tabIndex){
    case 1:
     id=R.drawable.a;
     break;
    case 2:
     id=R.drawable.b;
     break;
    case 3:
     id=R.drawable.c;
     break;
    case 4:
     id=R.drawable.d;
     break;
   }
   ivContent.setImageResource(id);
   ivContent.setVisibility(View.VISIBLE);
  }
 };
}

为了简化布局,demo中只用了微信上的几张截图,希望大家能专注重点。具体效果如图:

听说留下完整示例代码和demo是一种美德。(^__^) —Github跳转

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android UI设计与开发之使用ViewPager实现欢迎引导页面

    本系列文章都会以一个程序的实例开发为主线来进行讲解,以求达到一个循序渐进的学习效果,这样更能加深大家对于程序为什么要这样写的用意,理论加上实际的应用才能达到事半功倍的效果,不是吗? 最下方有源码的下载地址,几乎源码的每一行都有注释,写的通俗易懂,非常清晰,如有不懂的可以留言,本博主一定尽心尽力,为大家答题解惑,希望大家多多支持,好的,话不多说,让我们回归到今天的正题. 一.实现的效果图 也许是养成了这样一个习惯,每次看别人的代码前,必须要先看实现的效果图达到了一个什么样的效果,是不是跟自己想要实

  • Android ViewPager导航小圆点实现无限循环效果

    之前用View Pager做了一个图片切换的推荐栏(就类似与淘宝.头条客户端顶端的推荐信息栏),利用View Pager很快就能实现,但是一次无意间使用淘宝APP的时候,突然发现它的效果和我做的还不一样,淘宝APP的推荐栏可以左右无限循环切换,而ViewPager自身其实并没有支持这个功能. 其实实现这个无限循环不难,只需要在数据源的首尾各添加一张多余的图片,在onPagerChangeListener()中监听position<1和position>(总数据条目-1)就可以了.另外一点需要注

  • Android中TabLayout+ViewPager实现tab和页面联动效果

    TabLayout+ViewPager实现tab和页面联动效果 xml中: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" an

  • Android开发中如何解决Fragment +Viewpager滑动页面重复加载的问题

    前言 之前在做一个Viewpager上面加载多个Fragment时总会实例化已经创建好的Fragmnet对象类似 viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { switch(position){ case 0: fragments=new Fragmnet01(); break; case

  • Android ViewPager实现智能无限循环滚动回绕效果

    android系统提供的ViewPager标准方式是左右可以自由滑动,但是滑动到最左边的极限位置是第一个page,滑动到最右边的位置是最后一个page,当滑动到最左或者最右时候,就不能再滑动/滚动了,这是Android系统默认的ViewPager实现方式. 但是有些情况下开发者可能希望ViewPager能够智能的无限循环滚动回绕,比如现在总共有编号1, 2, 3, 4, 5的5个Page. (1)当用户手指从右往左滚动到最右边/最后面的页面5时候,如果此时用户继续拖住ViewPager往左边滑动

  • Android使用ViewPager实现图片滑动预览效果

    本文为大家分享了Android ViewPager实现图片滑动预览效果展示的具体代码,供大家参考,具体内容如下 效果图: 滑动前: 滑动后: 代码非常简单,实现起来很容易 xml代码: <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/ap

  • Android 中ViewPager中使用WebView的注意事项

    Android 中ViewPager中使用WebView的注意事项 前言: 今天在做项目时遇到了一个小问题 首先使用ViewPager显示多个页面,然后在每个页面上使用Fragment显示数据,其中有一部分数据是通过WebView加载的Html标签. 具体xml布局如下 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.andr

  • android ListView结合xutils3仿微信实现下拉加载更多

    前言:最近涉及到和QQ打交道,定义所有的好友一共只能有300条消息,如果一次性从数据库读取300条或者更多,界面会有细微的卡顿.所以考虑了下分页,第一次进来只显示20条(仿微信),当用户滑到第一条后,如果数据库有消息,则再加载20条. 步骤-问把大象关冰箱,总共分几步? 1.自定义absListview.scrollListerner 核心的东西是监听ListView的scrollListerner,这里采取拿来主义 原文连接找不到了,如果原作者看到,请联系青楼爱小生,将您的文章链接更新出来 ,

  • Android仿微信Viewpager-Fragment惰性加载(lazy-loading)

    前言 今天起床,拿起手机开机第一时间当然是打开微信了,左右滑动Viewpager,发现它使用了一种叫惰性加载,或者说懒加载(lazy-loading)的方式加载Viewpager中的Fragment.效果如图: 什么是lazy-loading呢?顾名思义就是在必要的时候才加载,否则不进行View的绘制和数据的加载.原因是Viewpager一次只会显示一个页卡,那么刚开始的时候,只需加载第一张Fragment页卡,其他的不加载,当用户向右滑动切换再进行加载.因为其他Fragment对于用户来说是不

  • Android 多层嵌套后的 Fragment 懒加载实现示例

    多层嵌套后的 Fragment 懒加载 印象中从 Feed 流应用流行开始,Fragment 懒加载变成了一个大家都需要关注的开发知识,关于 Fragment 的懒加载,网上有很多例子,GitHub 上也有很多例子,就连我自己在一年前也写过相关的文章.但是之前的应用可能最多的是一层 Activity + ViewPager 的 UI 层次,但是随着页面越来越复杂,越来越多的应用首页一个页面外层是一个 ViewPager 内部可能还嵌套着一层 ViewPager,这是之前的懒加载就可能不那么好用了

  • Android仿微信网络加载弹出框

    本文实例为大家分享了Android仿微信网络加载弹出框的具体代码,供大家参考,具体内容如下 没有饿了么的动画效果好看,但是,特别适用,拿来就用! 看一下效果图 图片素材 好了,其实很简单,就是一个自定义Dialog的控件而已 1. 自定义view的style样式 <resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.L

  • Android仿今日头条多个fragment懒加载的实现

    前言 最近有时间,所以我又双叒叕推新一篇文章了,fragment懒加载实现虽然是个小模块,但做过的人都有体会,通常并不会轻易就成功了的,让你辗转反侧,彻夜难眠,绵绵无绝期.我就按照今日头条的样式做了一个懒加载功能.文章到一半会解释大家可能遇到的不加载数据了的坑,先不剧透. Fragment的生命周期回顾 github代码直通车 (本地下载) 这里是今日头条效果: 自制效果,有图有真相: 实现思路: 使用Fragment类自带方法setUserVisibleHint()判断当前fragment是否

  • Android 开发中fragment预加载问题

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

  • Android开发之Fragment懒加载的几种方式及性能对比

    目录 1. Support时代的懒加载 2. AndrodX时代的懒加载 3. ViewPager2时代的懒加载 4. ViewPage和ViewPager2的性能对比 Android开发中ViewPager+Fragment的懒加载 TabLayout+ViewPager+Fragment是我们开发常用的组合.ViewPager的默认机制就是把全部的Fragment都加载出来,而为了保障一些用户体验,我们使用懒加载的Fragment,就是让我们再用户可见这个Fragment之后才处理业务逻辑.

  • Android中Fragment的加载方式与数据通信详解

    一.加载方式 1. 静态加载 1.1 加载步骤 (1) 创建fragment:创建自定义Fragment类继承自Fragment类,同时将自定义Fragment类与Fragment视图绑定(将layout转换成View) View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) inflater用于绑定Fragment的布局文件,同时将该布局转换成View对象并返回:con

  • Android仿微信发朋友圈浏览图片效果

    先看一下效果吧: 下面就来说一下具体怎么实现的: 实现思路 1.首先我们要获取数据源,数据源就是我们的每条说说(包括姓名.标题.图片数组) 2.自定义适配器(ListView嵌套着GridView) 3.图片点击浏览图片(Fragment+ViewPager) 具体实现 1.初始化数据源,设置适配器,看一下代码: public class MyActivity extends Activity { /*图片显示列表*/ private ListView listView; /*图片URL数组*/

  • Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏的设置方法类似于TabLayout的关联,View需要创建关联方法,用来关联VIewPager; 3. 通过关联方法获取ViewPager实例后,根据ViewPager页面数创建底部导航栏的图标按钮; 代码实现: 1. 新建第一个自定义View, 图标 + 文字 的底部按钮; /** * 自定义控件

随机推荐