该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!


前言

上一篇文章中我们使用底部导航+Fragment的方式实现了Android主流App中大都存在的设计。并命名其为“Fragment最佳实践”,作为想到单独使用Fragment的用户来说,这个说法并不夸大,它解决了许多用户在使用Fragment时产生的这样那样可见或不可见的问题。不过Fragment还有其他的使用方式,就是我们本章要介绍的。(本来是介绍ListView的,等着ListView的读者不好意思了,我会很快更新的。)

注:为什么临时插入这一章,因为有读者在上一篇文章中评论了,我觉得大有道理,感谢



这里我就不打码了,,哈哈哈哈

TabLayout

TabLayout的静态使用##

TabLayout是Android 5.0之后Google提供的一系列Material Design设计规范中的一个控件。我们在布局文件中可以这样使用

  1. <android.support.design.widget.TabLayout
  2. android:id="@+id/tab_layout"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:layout_alignParentBottom="true"
  6. app:tabIndicatorHeight="0dp"
  7. app:tabSelectedTextColor="@color/colorPrimary"
  8. >
  9. <android.support.design.widget.TabItem
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:text="Tab 1"/>
  13. <android.support.design.widget.TabItem
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:text="Tab 2"/>
  17. <android.support.design.widget.TabItem
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"
  20. android:text="Tab 3"/>
  21. </android.support.design.widget.TabLayout>

TabLayout间接继承于ViewGroup,其内可包含0到n个TabItem,这个TabItem就是我们经常使用的标签,其是个自定义View

,这样我们就定义了一个包含3个标签页的TabLayout。其运行结果如下图:

TabLayout的动态使用##

在布局文件中我们可以很方便定义顶部/底部 导航的布局。我们来看一下在代码中的使用

  1. public class TabActivity extends AppCompatActivity {
  2. @BindView(R.id.tab_layout)
  3. TabLayout mTabLayout;
  4. @BindView(R.id.view_pager)
  5. ViewPager mViewPager;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_tab);
  10. ButterKnife.bind(this);
  11. mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1"));
  12. mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2"));
  13. mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3"));
  14. //为TabLayout添加Tab选择事件监听
  15. mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
  16. @Override
  17. public void onTabSelected(TabLayout.Tab tab) {//当标签被选择时回调
  18. }
  19. @Override
  20. public void onTabUnselected(TabLayout.Tab tab) {//当标签从选择变为非选择时回调
  21. }
  22. @Override
  23. public void onTabReselected(TabLayout.Tab tab) {//当标签被重新选择时回调
  24. }
  25. });
  26. }
  27. }

关于运行结果我就不上图了,跟上面的运行结果是一样的。

TabLayout的更多属性##

关于TabLayout的更多属性以及使用的说明请查看其官方文档。在这里我们只关心TabLayout+ViewPager的化学反应,这个组合也是我们平常在开发中使用最多的。在此之前我们先介绍ViewPager

ViewPager

先看看官方对ViewPager的说明

  1. /*
  2. Layout manager that allows the user to flip left and right
  3. through pages of data. You supply an implementation of a
  4. {@link PagerAdapter} to generate the pages that the view shows.
  5. ViewPager is most often used in conjunction with {@link android.app.Fragment}
  6. There are standard adapters implemented for using fragments with the ViewPager,
  7. which cover the most common use cases. These are
  8. {@link android.support.v4.app.FragmentPagerAdapter} and
  9. {@link android.support.v4.app.FragmentStatePagerAdapter};*/
  10. public class ViewPager extends ViewGroup {
  11. }

上面英文的大致意思是ViewPager是一个布局管理类,这个类呢允许用户左右翻转页面。你必须实现一个PagerAdapter来生成这些显示的页面。ViewPager经常和Fragment一起使用。而且呢Google非常贴心的提供了两个类FragmentPagerAdapter和FragmentStatePagerAdapter来应付那些一般场景。

其实从ViewPager的说明中,我们基本上就能知道ViewPager是什么以及如何使用了。

PagerAdapter

ViewPager继承于ViewGroup,官方指导中就说了,你要自己实现PagerAdapter来生成显示的页面,那么我们来看看这个PagerAdapter

  1. /**
  2. * Base class providing the adapter to populate pages inside of
  3. * a {@link ViewPager}. You will most likely want to use a more
  4. * specific implementation of this, such as
  5. * {@link android.support.v4.app.FragmentPagerAdapter} or
  6. * {@link android.support.v4.app.FragmentStatePagerAdapter}.
  7. *
  8. * <p>When you implement a PagerAdapter, you must override the following methods
  9. * at minimum:</p>
  10. * <ul>
  11. * <li>{@link #instantiateItem(ViewGroup, int)}</li>
  12. * <li>{@link #destroyItem(ViewGroup, int, Object)}</li>
  13. * <li>{@link #getCount()}</li>
  14. * <li>{@link #isViewFromObject(View, Object)}</li>
  15. * </ul>
  16. * /
  17. public abstract class PagerAdapter {
  18. }

其实我们在看一个不太了解的类的时候,通过源码上的关于这个类的说明就可以知道很多信息了。关于PagerAdapter的说明就是如此。

先说了一下PagerAdapter的作用,是一个基类提供适配器给ViewPager中的页面,如果你想使用特定的实现类,那么你可以看两个类FragmentPagerAdapter和FragmentStatePagerAdapter,这两个类继承了PagerAdapter,并实现了其抽象方法。

后面一段的意思是你如果想自定义你自己的PagerAdapter,那么你最少要实现这4个方法

  1. instantiateItem(ViewGroup, int)

  2. destroyItem(ViewGroup, int, Object)

  3. getCount()

  4. isViewFromObject(View, Object)

下面我们以代码的形式,说明这4个方法的含义以及如何使用

  1. private class MyViewPagerAdapter extends PagerAdapter {
  2. /**
  3. * 获取View的总数
  4. *
  5. * @return View总数
  6. */
  7. @Override
  8. public int getCount() {
  9. return 0;
  10. }
  11. /**
  12. * 为给定的位置创建相应的View。创建View之后,需要在该方法中自行添加到container中。
  13. *
  14. * @param container ViewPager本身
  15. * @param position 给定的位置
  16. * @return 提交给ViewPager进行保存的实例对象
  17. */
  18. @Override
  19. public Object instantiateItem(ViewGroup container, int position) {
  20. return super.instantiateItem(container, position);
  21. }
  22. /**
  23. * 给定的位置移除相应的View。
  24. *
  25. * @param container ViewPager本身
  26. * @param position 给定的位置
  27. * @param object 在instantiateItem中提交给ViewPager进行保存的实例对象
  28. */
  29. @Override
  30. public void destroyItem(ViewGroup container, int position, Object object) {
  31. super.destroyItem(container, position, object);
  32. }
  33. /**
  34. * 确认View与实例对象是否相互对应。ViewPager内部用于获取View对应的ItemInfo。
  35. *
  36. * @param view ViewPager显示的View内容
  37. * @param object 在instantiateItem中提交给ViewPager进行保存的实例对象
  38. * @return 是否相互对应
  39. */
  40. @Override
  41. public boolean isViewFromObject(View view, Object object) {
  42. return false;
  43. }
  44. }

这4个方法是必须的,,另外还有一些不是必须,但是可能会用到的

  1. /**
  2. * 当ViewPager的内容有所变化时,进行调用。
  3. *
  4. * @param container ViewPager本身
  5. */
  6. @Override
  7. public void startUpdate(ViewGroup container) {
  8. super.startUpdate(container);
  9. }
  10. /**
  11. * ViewPager调用该方法来通知PageAdapter当前ViewPager显示的主要项,提供给用户对主要项进行操作的方法。
  12. *
  13. * @param container ViewPager本身
  14. * @param position 给定的位置
  15. * @param object 在instantiateItem中提交给ViewPager进行保存的实例对象
  16. */
  17. @Override
  18. public void setPrimaryItem(ViewGroup container, int position, Object object) {
  19. super.setPrimaryItem(container, position, object);
  20. }
  21. /**
  22. * 较多的用于Design库中的TabLayout与ViewPager进行绑定时,提供显示的标题。
  23. *
  24. * @param position 给定的位置
  25. * @return 显示的标题
  26. */
  27. @Override
  28. public CharSequence getPageTitle(int position) {
  29. return super.getPageTitle(position);
  30. }

FragmentPagerAdapter

上面呢只是列举说明了一下PagerAdapter,看起来有些枯燥,都是些说明,那么我们来看一下实践,ViewPager通畅跟Fragment一起使用,即其所管理的页面通畅是Fragment,所以Google提供了两个适配器FragmentPagerAdapter和FragmentStatePagerAdapter,我们这节分析FragmentPagerAdapter。

  1. /**
  2. *真是不看不知道,一看吓一跳。FragmentPagerAdapter也是个抽象类,
  3. *
  4. */
  5. public abstract class FragmentPagerAdapter extends PagerAdapter {
  6. private static final String TAG = "FragmentPagerAdapter";
  7. private static final boolean DEBUG = false;
  8. private final FragmentManager mFragmentManager;
  9. private FragmentTransaction mCurTransaction = null;
  10. private Fragment mCurrentPrimaryItem = null;
  11. public FragmentPagerAdapter(FragmentManager fm) {
  12. mFragmentManager = fm;
  13. }
  14. /**
  15. *抽象方法,看来这个函数要子类自己实现了
  16. *
  17. * @param position ViewPager中Item的位置
  18. * @return 位置相关联的Fragment
  19. */
  20. public abstract Fragment getItem(int position);
  21. @Override
  22. public void startUpdate(ViewGroup container) {
  23. if (container.getId() == View.NO_ID) {
  24. throw new IllegalStateException("ViewPager with adapter " + this
  25. + " requires a view id");
  26. }
  27. }
  28. /**
  29. * 为给定的位置创建相应的fragment。创建fragment之后,需要在该方法中自行添加到container中。
  30. *
  31. * @param container ViewPager本身
  32. * @param position 给定的位置
  33. * @return 提交给ViewPager进行保存的实例对象,这里是Fragment
  34. */
  35. @Override
  36. public Object instantiateItem(ViewGroup container, int position) {
  37. if (mCurTransaction == null) {
  38. mCurTransaction = mFragmentManager.beginTransaction();
  39. }
  40. final long itemId = getItemId(position);
  41. String name = makeFragmentName(container.getId(), itemId);
  42. Fragment fragment = mFragmentManager.findFragmentByTag(name);
  43. if (fragment != null) {
  44. if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
  45. mCurTransaction.attach(fragment);
  46. } else {
  47. fragment = getItem(position);
  48. if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
  49. mCurTransaction.add(container.getId(), fragment,
  50. makeFragmentName(container.getId(), itemId));
  51. }
  52. if (fragment != mCurrentPrimaryItem) {
  53. fragment.setMenuVisibility(false);
  54. fragment.setUserVisibleHint(false);
  55. }
  56. return fragment;
  57. }
  58. /**
  59. * 移除给定的位置相应的fragment。
  60. *
  61. * @param container ViewPager本身
  62. * @param position 给定的位置
  63. * @param object 在instantiateItem中提交给ViewPager进行保存的实例对象
  64. */
  65. @Override
  66. public void destroyItem(ViewGroup container, int position, Object object) {
  67. if (mCurTransaction == null) {
  68. mCurTransaction = mFragmentManager.beginTransaction();
  69. }
  70. if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
  71. + " v=" + ((Fragment)object).getView());
  72. mCurTransaction.detach((Fragment)object);
  73. }
  74. @Override
  75. public void setPrimaryItem(ViewGroup container, int position, Object object) {
  76. Fragment fragment = (Fragment)object;
  77. if (fragment != mCurrentPrimaryItem) {
  78. if (mCurrentPrimaryItem != null) {
  79. mCurrentPrimaryItem.setMenuVisibility(false);
  80. mCurrentPrimaryItem.setUserVisibleHint(false);
  81. }
  82. if (fragment != null) {
  83. fragment.setMenuVisibility(true);
  84. fragment.setUserVisibleHint(true);
  85. }
  86. mCurrentPrimaryItem = fragment;
  87. }
  88. }
  89. @Override
  90. public void finishUpdate(ViewGroup container) {
  91. if (mCurTransaction != null) {
  92. mCurTransaction.commitNowAllowingStateLoss();
  93. mCurTransaction = null;
  94. }
  95. }
  96. @Override
  97. public boolean isViewFromObject(View view, Object object) {
  98. return ((Fragment)object).getView() == view;
  99. }
  100. @Override
  101. public Parcelable saveState() {
  102. return null;
  103. }
  104. @Override
  105. public void restoreState(Parcelable state, ClassLoader loader) {
  106. }
  107. /**
  108. * @param position ViewPager中Item的位置
  109. * @return 唯一的ItemID
  110. */
  111. public long getItemId(int position) {
  112. return position;
  113. }
  114. private static String makeFragmentName(int viewId, long id) {
  115. return "android:switcher:" + viewId + ":" + id;
  116. }
  117. }

代码比较少,总共也就100多行,逻辑也比较清晰明了,我们来着重分析instantiateItem和destroyItem

  1. /**
  2. * 为给定的位置创建相应的fragment。创建fragment之后,需要在该方法中自行添加到container中。
  3. *
  4. * @param container ViewPager本身
  5. * @param position 给定的位置
  6. * @return 提交给ViewPager进行保存的实例对象,这里是Fragment
  7. */
  8. @Override
  9. public Object instantiateItem(ViewGroup container, int position) {
  10. //开启事务
  11. if (mCurTransaction == null) {
  12. mCurTransaction = mFragmentManager.beginTransaction();
  13. }
  14. //得到指定位置Item的ID
  15. final long itemId = getItemId(position);
  16. //根据id和ViewPager的ID生成item的name
  17. String name = makeFragmentName(container.getId(), itemId);
  18. //以name为Tag查找对应的Fragment
  19. Fragment fragment = mFragmentManager.findFragmentByTag(name);
  20. if (fragment != null) {//如果找到了
  21. if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
  22. //调用事务的attach
  23. mCurTransaction.attach(fragment);
  24. } else {//没找到
  25. //通过我们重写的getItem方法得到相应fragment
  26. fragment = getItem(position);
  27. if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
  28. //调用事务的add方法,并设置Tag
  29. mCurTransaction.add(container.getId(), fragment,
  30. makeFragmentName(container.getId(), itemId));
  31. }
  32. //如果frament不等于当前主要的Item
  33. if (fragment != mCurrentPrimaryItem) {
  34. //设置其Menu不可见
  35. fragment.setMenuVisibility(false);
  36. //设置其不可见
  37. fragment.setUserVisibleHint(false);
  38. }
  39. return fragment;
  40. }

instantiateItem方法主要功能是为ViewPager生成Item。

那么destroyItem方法的主要功能是销毁ViwePager内的Item

  1. @Override
  2. public void destroyItem(ViewGroup container, int position, Object object) {
  3. if (mCurTransaction == null) {
  4. mCurTransaction = mFragmentManager.beginTransaction();
  5. }
  6. //调用事务的detach方法
  7. mCurTransaction.detach((Fragment)object);
  8. }

FragmentStatePagerAdapter

关于FragmentStatePagerAdapter,读者可自行分析,代码也不长。需要注意的地方是,两者对于destroyItem的不同实现

  1. @Override
  2. public void destroyItem(ViewGroup container, int position, Object object) {
  3. Fragment fragment = (Fragment) object;
  4. if (mCurTransaction == null) {
  5. mCurTransaction = mFragmentManager.beginTransaction();
  6. }
  7. if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
  8. + " v=" + ((Fragment)object).getView());
  9. while (mSavedState.size() <= position) {
  10. mSavedState.add(null);
  11. }
  12. mSavedState.set(position, fragment.isAdded()
  13. ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
  14. mFragments.set(position, null);
  15. //调用事务的remove方法
  16. mCurTransaction.remove(fragment);
  17. }

小结

ViewPager是个ViewGroup,与其他布局LinearLayout或者其他任意的ViewGroup并无本质的不同,它被Google建议与Fragment结伴使用,也是说ViewPager所包裹的是Fragment布局。ViewPager需要适配器PagerAdapter操作Fragment,这一点就像ListView需要适配器操作其内部的Item一样。

适配器PagerAdapter是个抽象类,并且依照官方说明,我们必须至少实现其4个重要方法。4个方法可能太多,所以Google提供了FragmentPagerAdapter以及FragmentStatePagerAdapter,这两个也是抽象类,不过我们的自定义Adapter只需要实现其中的getItem(int position)方法即可。

关于FragmentPagerAdapter以及FragmentStatePagerAdapter的不同,我这里再总结一下。FragmentPagerAdapter销毁item的时候最终调用FragmentTransaction的detach()方法,使用detach()会将view从viewtree中删除,和FragmentStatePagerAdapter中使用的remove()不同,此时fragment的状态依然保持着,在使用attach()时会再次调用onCreateView()来重绘视图,注意使用detach()后fragment.isAdded()方法将返回false。

实例##

更改后的TabActivity对应的布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:fitsSystemWindows="true"
  8. <!--ViewPager-->
  9. <android.support.v4.view.ViewPager
  10. android:id="@+id/view_pager"
  11. android:layout_width="match_parent"
  12. android:layout_height="wrap_content"
  13. android:layout_alignParentTop="true"
  14. >
  15. </android.support.v4.view.ViewPager>
  16. <!--分割线-->
  17. <ImageView
  18. android:id="@+id/image_1"
  19. android:layout_width="match_parent"
  20. android:layout_height="1dp"
  21. android:background="#919292"
  22. android:layout_above="@+id/tab_layout"/>
  23. <!--TabLayout-->
  24. <android.support.design.widget.TabLayout
  25. android:id="@+id/tab_layout"
  26. android:layout_width="match_parent"
  27. android:layout_height="wrap_content"
  28. android:layout_alignParentBottom="true"
  29. app:tabIndicatorHeight="0dp"
  30. app:tabSelectedTextColor="@color/colorPrimary"
  31. >
  32. </android.support.design.widget.TabLayout>
  33. </RelativeLayout>

更改后的TabActivity

  1. public class TabActivity extends AppCompatActivity {
  2. @BindView(R.id.tab_layout)
  3. TabLayout mTabLayout;
  4. @BindView(R.id.view_pager)
  5. ViewPager mViewPager;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_tab);
  10. ButterKnife.bind(this);
  11. mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1"));
  12. mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2"));
  13. mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3"));
  14. //自定义的Adapter继承自FragmentPagerAdapter
  15. final PagerAdapter adapter = new PagerAdapter
  16. (getSupportFragmentManager(), mTabLayout.getTabCount());
  17. //ViewPager设置Adapter
  18. mViewPager.setAdapter(adapter);
  19. //为ViewPager添加页面改变监听
  20. mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
  21. //为TabLayout添加Tab选择监听
  22. mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
  23. @Override
  24. public void onTabSelected(TabLayout.Tab tab) {
  25. mViewPager.setCurrentItem(tab.getPosition());
  26. }
  27. @Override
  28. public void onTabUnselected(TabLayout.Tab tab) {
  29. }
  30. @Override
  31. public void onTabReselected(TabLayout.Tab tab) {
  32. }
  33. });
  34. }
  35. }

而我们自定义的MyPagerAdapter也非常简单

  1. public class MyPagerAdapter extends FragmentPagerAdapter {
  2. //fragment的数量
  3. int nNumOfTabs;
  4. public MyPagerAdapter(FragmentManager fm, int nNumOfTabs)
  5. {
  6. super(fm);
  7. this.nNumOfTabs=nNumOfTabs;
  8. }
  9. /**
  10. * 重写getItem方法
  11. *
  12. * @param position 指定的位置
  13. * @return 特定的Fragment
  14. */
  15. @Override
  16. public Fragment getItem(int position) {
  17. switch(position)
  18. {
  19. case 0:
  20. GoodsFragment tab1=new GoodsFragment();
  21. return tab1;
  22. case 1:
  23. CategoryFragment tab2=new CategoryFragment();
  24. return tab2;
  25. case 2:
  26. TaskFragment tab3=new TaskFragment();
  27. return tab3;
  28. }
  29. return null;
  30. }
  31. /**
  32. * 重写getCount方法
  33. *
  34. * @return fragment的数量
  35. */
  36. @Override
  37. public int getCount() {
  38. return nNumOfTabs;
  39. }
  40. }

ViewPager预加载与网络请求#

ViewPager的预加载机制##

ViewPager可通过setOffscreenPageLimit(int limit)函数设置ViewPager预加载的View数目

  1. public void setOffscreenPageLimit(int limit) {
  2. //DEFAULT_OFFSCREEN_PAGES=1
  3. if (limit < DEFAULT_OFFSCREEN_PAGES) {
  4. Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
  5. + DEFAULT_OFFSCREEN_PAGES);
  6. limit = DEFAULT_OFFSCREEN_PAGES;
  7. }
  8. if (limit != mOffscreenPageLimit) {
  9. mOffscreenPageLimit = limit;
  10. populate();
  11. }
  12. }

可以看到该函数的源码,当我们传入的limit<1时,limit还是被设置为1,当limit与成员变量mOffscreenPageLimit的值不同时(成员变量mOffscreenPageLimit的默认值为1),更新成员变量mOffscreenPageLimit的值,然后调用populate()函数。

而这个populate()函数就是给我们的ViewPager准备缓存页面并显示当前页面用的。

假如说我采用下面的方法调用setOffscreenPageLimit(2),此时ViewPager的简单示意图

注:从上面的代码也可以看出ViewPager最少会预加载一个页面。在本例中,也是我们在显示TAB1的时候,ViewPager已经加载了TAB2,具体方式是通过instantiateItem方法,该方法内部调用了我们重写的getItem方法,TAB2所表示的Fragment的onCreateView等相关生命周期方法会被回调。

ViewPager的网络请求##

ViewPager的预加载机制其实在某些时候是个很让人不爽的问题,比如我们在Fragment做网络请求数据的时候,我们网络请求的代码通常会放在onCreateView中,我们只是打开第1个Fragment,但是由于ViewPager会加载第2个Fragment,可能也执行了第2个Fragment的网络请求代码。

而避免上述问题的主要依靠

  1. public void setUserVisibleHint(boolean isVisibleToUser)

setUserVisibleHint(boolean isVisibleToUser)是Fragment中的一个回调函数。当前Fragment可见时,setUserVisibleHint()回调,其中isVisibleToUser=true。当前Fragment由可见到不可见或实例化时,setUserVisibleHint()回调,其中isVisibleToUser=false。

setUserVisibleHint(boolean isVisibleToUser)调用时机

  1. 在Fragment实例化,即在ViewPager中,由于ViewPager默认会预加载左右两个页面。此时预加载页面回调的生命周期流程:setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> onActivityCreate() --> onStart() --> onResume()

    此时,setUserVisibleHint() 中的参数为false,因为不可见。

  2. 在Fragment可见时,即ViewPager中滑动到当前页面时,因为已经预加载过了,之前生命周期已经走到onResume() ,所以现在只会回调:setUserVisibleHint()。

    此时,setUserVisibleHint() 中的参数为true,因为可见。

  3. 在Fragment由可见变为不可见,即ViewPager由当前页面滑动到另一个页面,因为还要保持当前页面的预加载过程,所以只会回调:setUserVisibleHint()。

    此时,setUserVisibleHint() 中的参数为false,因为不可见。

  4. 由TabLayout直接跳转到一个未预加载的页面,此时生命周期的回调过程:setUserVisibleHint() -->setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> onActivityCreate() --> onStart()

    --> onResume()

    此时回调了两次setUserVisibleHint() ,一次代表初始化时,传入参数是false,一次代表可见时,传入参数是true。这种情况比较特殊。

总结:无论何时,setUserVisibleHint()都是先于其他生命周期的调用,并且初始化时调用,可见时调用,由可见转换成不可见时调用,一共三次时机。

ViewPager的网络请求的优化实现###

我们在使用ViewPager+Fragment显示数据的时候,我们通常会把网络请求的操作放在onCreateView->onResume之间的生命周期内。这可能带来的问题我们上面已经探讨了。那么怎么解决这个问题呢?

本篇总结

我们在本篇博客中比较详细的探讨了TabLayout+ViewPager+Fragment的使用,我们在许多主流App中都能看到这种顶部、底部导航的效果,并且在此基础上我们探讨了TabLayout+ViewPager+Fragment网络数据加载问题。

我们希望Fragment可见时加载网络数据,不可见时不进行或者取消网络请求。

  1. public abstract class BaseFragment extends Fragment {
  2. protected View rootView;
  3. private Unbinder mUnbinder;
  4. //当前Fragment是否处于可见状态标志,防止因ViewPager的缓存机制而导致回调函数的触发
  5. private boolean isFragmentVisible;
  6. //是否是第一次开启网络加载
  7. public boolean isFirst;
  8. @Nullable
  9. @Override
  10. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  11. if (rootView == null)
  12. rootView = inflater.inflate(getLayoutResource(), container, false);
  13. mUnbinder = ButterKnife.bind(this, rootView);
  14. initView();
  15. //可见,但是并没有加载过
  16. if (isFragmentVisible && !isFirst) {
  17. onFragmentVisibleChange(true);
  18. }
  19. return rootView;
  20. }
  21. //获取布局文件
  22. protected abstract int getLayoutResource();
  23. //初始化view
  24. protected abstract void initView();
  25. @Override
  26. public void setUserVisibleHint(boolean isVisibleToUser) {
  27. super.setUserVisibleHint(isVisibleToUser);
  28. if (isVisibleToUser) {
  29. isFragmentVisible = true;
  30. }
  31. if (rootView == null) {
  32. return;
  33. }
  34. //可见,并且没有加载过
  35. if (!isFirst&&isFragmentVisible) {
  36. onFragmentVisibleChange(true);
  37. return;
  38. }
  39. //由可见——>不可见 已经加载过
  40. if (isFragmentVisible) {
  41. onFragmentVisibleChange(false);
  42. isFragmentVisible = false;
  43. }
  44. }
  45. @Override
  46. public void onDestroyView() {
  47. super.onDestroyView();
  48. mUnbinder.unbind();
  49. }
  50. /**
  51. * 当前fragment可见状态发生变化时会回调该方法
  52. *
  53. * 如果当前fragment是第一次加载,等待onCreateView后才会回调该方法,其它情况回调时机跟 {@link #setUserVisibleHint(boolean)}一致
  54. * 在该回调方法中你可以做一些加载数据操作,甚至是控件的操作.
  55. *
  56. * @param isVisible true 不可见 -> 可见
  57. * false 可见 -> 不可见
  58. */
  59. protected void onFragmentVisibleChange(boolean isVisible) {
  60. }
  61. }

我们设计抽象基类BaseFragment,所有的公共行为我们都可以在这个基类中定义,那么我们的Fragment是否可见就是其中的一种行为,所以我们上面重写了Fragment的setUserVisibleHint方法。

  1. public class GoodsFragment extends BaseFragment {
  2. @Override
  3. protected void onFragmentVisibleChange(boolean isVisible) {
  4. if(isVisible){
  5. //可见,并且是第一次加载
  6. lazyLoad();
  7. }else{
  8. //取消加载
  9. }
  10. }
  11. private void lazyLoad() {
  12. if (!isFirst) {
  13. isFirst = true;
  14. }
  15. }
  16. @Override
  17. protected int getLayoutResource() {
  18. return R.layout.fragment_goods;
  19. }
  20. @Override
  21. protected void initView() {
  22. }
  23. }

我们设计GoodsFragment继承BaseFragment并重写其onFragmentVisibleChange以控制自身的网络请求。


本篇总结

本篇为读者介绍了另外一种导航页切换的实现,我们使用TabLayout+ViewPager+Fragment的方式,其中读者需要重点理解以下几点

  1. ViewPager是个ViewGroup,它所关机的布局就是通常是我们的Fragment布局。
  2. ViewPager的预加载机制、可能带来的问题及如何解决。
  3. 理解PagerAdapter,以及如何实现它
  4. 理解Google提供了两个特定场景的PagerAdapter实现类FragmentPagerAdapter以及FragmentStatePagerAdapter,以及他们的主要区别

下篇预告

下篇打算往Fragment中加点东西,ListView


此致,敬礼

Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment的更多相关文章

  1. Android开发之漫漫长途 番外篇——自定义View的各种姿势2

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android开发之漫漫长途 番外篇——内存泄漏分析与解决

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Android开发之漫漫长途 番外篇——自定义View的各种姿势1

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  4. Android开发之漫漫长途 XII——Fragment详解

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  5. Android开发之漫漫长途 XIII——Fragment最佳实践

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. Android开发之漫漫长途 XI——从I到X的小结

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  7. Android开发之漫漫长途 XIV——RecyclerView

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  8. Android开发之漫漫长途 XVI——ListView与RecyclerView项目实战

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  9. Android开发之漫漫长途 XV——RecyclerView

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

随机推荐

  1. [linux] C语言Linux系统编程-socket开发响应HTTP协议

    #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h&g ...

  2. css样式中如何设置中文字体?

    代码如下: .selector{ font-family: SimHei,"微软雅黑",sans-serif; }  注意:加上中文名“微软雅黑”是为了兼容opera浏览器,中文字 ...

  3. jQuery_serialize的用法

    jQuery_serialize(form表单序列化)用于在前端要传很多值往后端的时候: <!DOCTYPE html> <html lang="en"> ...

  4. Java源码解读(一) 8种基本类型对应的封装类型

    说起源码其实第一个要看的应该是我们的父类Object,这里就不对它进行描述了大家各自对其进行阅读即可. 一.八种基本类型 接下来介绍我们的八种基本类型(这个大家都知道吧):char.byte.shor ...

  5. bzoj:3400 [Usaco2009 Mar]Cow Frisbee Team 奶牛沙盘队

    Description     农夫顿因开始玩飞盘之后,约翰也打算让奶牛们享受飞盘的乐趣.他要组建一只奶牛飞盘 队.他的N(1≤N≤2000)只奶牛,每只部有一个飞盘水准指数Ri(1≤Ri≤10000 ...

  6. 洛谷 P1219 八皇后【经典DFS,温习搜索】

    P1219 八皇后 题目描述 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 上面的布局可以用序 ...

  7. zlib1.2.11静态编译

    1.进入官网http://zlib.net/,下载且解压zlib1211.zip: 2. 打开已解压的zlib-1.2.11目录,找到win32文件夹 3.将Makefile.msc复制到上一层,也就 ...

  8. 解决jsp中编辑和删除时候弹出框闪退的问题。

    ---恢复内容开始--- /* 火箭设备特殊记载</li> <!-- yw4 --> */ function getYw4DL(){ var controlparm={&quo ...

  9. 安装linux的关键步骤

  10. i++是否原子操作?并解释为什么?

    都不是原子操作.理由: 1.i++分为三个阶段: 内存到寄存器寄存器自增写回内存这三个阶段中间都可以被中断分离开.  2.++i首先要看编译器是怎么编译的, 某些编译器比如VC在非优化版本中会编译为以 ...