Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
android viewpager详解_android开发工具箱,希望能够帮助你!!!。
前面两篇文章中,对 Fragment 的基本使用、常见问题和状态恢复做了详细的分析总结。除了在 Activity 中单独使用 Fragment,Fragment + ViewPager 组合也是项目中使用非常频繁的方式,本文再来总结一下这种组合使用时的注意事项。在此之前,如果你对 Fragment 的认知和使用还有不清楚的地方,一定要先阅读前面两篇文章:
Android Fragment 的使用,一些你不可不知的注意事项
Android Activity 和 Fragment 状态保存与恢复的最佳实践
对于这种组合使用,ViewPager 提供了两种页面适配器来管理不同 Fragment 之间的滑动切换:FragmentPagerAdapter
和 FragmentStatePagerAdapter
。先来看一下他们的基本使用,稍后再分析二者之间的区别。
// private class ContentPagerAdapter extends FragmentStatePagerAdapter{ private class ContentPagerAdapter extends FragmentPagerAdapter{ public ContentPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return fragmentList.get(position); } @Override public int getCount() { return fragmentList.size(); } }
如上述代码所示,没有特别要求的话,无论是哪种适配器类,实现起来都比简单,不需要像普通的 ViewPager + View 组合那样,还需处理视图的初始化工作(instantiateItem
方法)和销毁(destroyItem
方法)等。FragmentPagerAdapter
和 FragmentStatePagerAdapter
在内部已经默认实现了这些功能。
源码定义中已经很清楚地描述了 FragmentPagerAdapter
和 FragmentStatePagerAdapter
的区别:
FragmentPagerAdapter
Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.
This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.
FragmentStatePagerAdapter
Implementation of PagerAdapter that uses a Fragment to manage each page. This class also handles saving and restoring of fragment’s state.
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
总结归纳如下:
使用 FragmentPagerAdapter
时,ViewPager 中的所有 Fragment 实例常驻内存,当 Fragment 变得不可见时仅仅是视图结构的销毁,即调用了 onDestroyView
方法。由于 FragmentPagerAdapter
内存消耗较大,所以适合少量静态页面的场景。
使用 FragmentStatePagerAdapter
时,当 Fragment 变得不可见,不仅视图层次销毁,实例也被销毁,即调用了 onDestroyView
和 onDestroy
方法,仅仅保存 Fragment 状态。相比而言, FragmentStatePagerAdapter
内存占用较小,所以适合大量动态页面,比如我们常见的新闻列表类应用。
“Talk is cheap, show me the code.” 如果这样表达还是不能理解二者之间的区别的话,最好的办法就是用代码来表达。
新建一个名为 BaseFragment 的基类,继承自 Fragment,重写 setUserVisibleHint
和 各个生命周期函数,添加日志打印。然后新建四个子类,分别命名为 OneFragment、TwoFragment、ThreeFragment 和 FourFragment,按照基本使用方法写好代码,运行并滑动页面,查看日志打印。
由于代码较为简单,考虑内容长度,这里就不贴相关代码,主要描述思想。对应日志截图如下:
使用 FragmentPagerAdapter
时:
使用 FragmentStatePagerAdapter
时:
图中做了相应标记说明,二者区别一目了然,无需过多解释。出现这样的区别,其实从源码中的 instantiateItem
和 destroyItem
也能读出一二,感兴趣的话可以翻看一下。
懒加载,顾名思义,是希望在展示相应 Fragment 页面时再动态加载页面数据,数据通常来自于网络或本地数据库。这种做法的合理性在于用户可能不会滑到一下页面,同时还能帮助减轻当前页面数据请求的带宽压力,如果是用户使用流量的话,还能避免无用的流量消耗。
从上面的截图中可以看出,ViewPager 在展示当前页面时,会同时预加载下一页面。事实上,可以通过 ViewPager 提供的 setOffscreenPageLimit(int limit)
方法设置 ViewPager 预加载的页面数量,默认值为 1,并且这个参数的值不能小于 1。所以也就无法通过这个方法实现 ViewPager 中 Fragment 的懒加载,一定要改 ViewPager 的话只能通过自定义一个 Viewpager 类来实现,这种做法就比较繁琐。其实可以从 Fragment 下手。
ViewPager 本质上是通过 Fragment 调用 setUserVisibleHint
方法实现 Fragment 页面的展示与隐藏,这一点从FragmentPagerAdapter
和 FragmentStatePagerAdapter
的源码和上面的截图中都可以看出。那么对应的解决方案就有了,自定义一个 LazyLoadFragment 基类,利用 setUserVisibleHint
和 生命周期方法,通过对 Fragment 状态判断,进行数据加载,并将数据加载的接口提供开放出去,供子类使用。参考代码如下:
public abstract class LazyLoadFragment extends BaseFragment { protected boolean isViewInitiated; protected boolean isDataLoaded; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); isViewInitiated = true; prepareRequestData(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); prepareRequestData(); } public abstract void requestData(); public boolean prepareRequestData() { return prepareRequestData(false); } public boolean prepareRequestData(boolean forceUpdate) { if (getUserVisibleHint() && isViewInitiated && (!isDataLoaded || forceUpdate)) { requestData(); isDataLoaded = true; return true; } return false; } }
然后在子类 Fragment 中实现 requestData 方法即可。这里添加了一个 isDataLoaded 变量,目的是避免重复加载数据。考虑到有时候需要刷新数据的问题,便提供了一个用于强制刷新的参数判断。这种思路来自于 这篇文章,在此基础上做了一些修改。实际上,在项目开发过程中,还需处理网络请求失败等特殊情况,我想,了解原理之后,这些问题都不再是问题。
前文描述FragmentPagerAdapter
与 FragmentStatePagerAdapter
的区别时有提到,这两种适配器类默认都会保存 Fragment 状态,包括 View 状态和成员变量数据状态。需要注意的是,View 状态包括的内容很多,比如用户在 EditText 中输入的内容、ScrollView 滑动的位置纪录等。
有关 Fragment 的具体使用细节和注意事项可以参考这篇文章:Android Activity 和 Fragment 状态保存与恢复的最佳实践。这里我说说另外两种简单粗暴的做法。
一种是通过 setOffscreenPageLimit
方法设置保留视图结构的 Fragment 数量,比较简单,比如保留所有 Fragment 视图结构: mContentVp.getAdapter().getCount()-1;另一种就是重写适配器的 Fragment 相关方法,比如:
// private class ContentPagerAdapter extends FragmentStatePagerAdapter { private class ContentPagerAdapter extends FragmentPagerAdapter { private FragmentManager fragmentManager; public ContentPagerAdapter(FragmentManager fm) { super(fm); this.fragmentManager = fm; } @Override public Fragment getItem(int position) { return fragmentList.get(position); } @Override public int getCount() { return fragmentList.size(); } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = (Fragment) super.instantiateItem(container, position); this.fragmentManager.beginTransaction().show(fragment).commit(); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = fragmentList.get(position); fragmentManager.beginTransaction().hide(fragment).commit(); } }
这种处理下,也就不用区分使用的是哪种适配器类,通过重写 instantiateItem
和 destroyItem
方法,使用 show 和 hide 方法处理 Fragment 的展示与隐藏,这样,视图结构就不会销毁,换一种角度解决了 Fragment 状态保存与恢复的问题。
可以看出,使用这两种处理方式时,Fragment 实例均保存在内存中,具有一定内存消耗,适合于页面较少的情况。至于大量页面,还是推荐通过 Fragment 自带的状态保存与恢复方式处理。
2017年08月22日 14:18:41 growing_c 阅读数:3971
众所周知,fragment的onResume()和onPause()方法是和activity绑定在一起的,此时fragment的onResume方法并不能确定在fragment切换前后台时会调用,而app开发中经常会需要在前后台切换时做一些操作,在activity中一般直接在onResume()中操作,而fragment中似乎没有这么方便的原生方法。
和别人合作做某一项目的时候,发现他的fragment中是使用setUserVisibeHint来区分fragment的显示与否,经研究发现,当使用ViewPager+Fragment组合的时候会多次调用setUserVisibleHint方法,然而此方法却有很多缺点,摘一段源码中的note:
* <strong>Note:</strong> This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.
既然有这些缺点,那么在这个方法中来进行数据加载和控件的操作 必然是不安全的
一.简单情景下的封装
于是我们就想要封装一个简单易用的方法来进行数据加载和控件操作
暂时先不管ViewPager+Fragment组合的情况,在一般使用Fragment的情景中
通过 onResume(),onPause(),onHiddenChanged()这三个方法来实现了我们的需求。其中onResume()和onPause()方法是和绑定的activity的生命周期绑定的,另外onHiddenChanged()是在fragment是否可见状态改变的时候调用的,于是就有了以下的解决方案,不再赘述,直接上代码:
public abstract class BaseFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
/**
* fragment可见的时候操作,取代onResume,且在可见状态切换到可见的时候调用
*/
protected void onVisible() {
}
/**
* fragment不可见的时候操作,onPause的时候,以及不可见的时候调用
*/
protected void onHidden() {
}
@Override
public void onResume() {//和activity的onResume绑定,Fragment初始化的时候必调用,但切换fragment的hide和visible的时候可能不会调用!
super.onResume();
if (isAdded() && !isHidden()) {//用isVisible此时为false,因为mView.getWindowToken为null
onVisible();
}
}
@Override
public void onPause() {
if (isVisible())
onHidden();
super.onPause();
}
@Override
public void onHiddenChanged(boolean hidden) {//默认fragment创建的时候是可见的,但是不会调用该方法!切换可见状态的时候会调用,但是调用onResume,onPause的时候却不会调用
super.onHiddenChanged(hidden);
if (!hidden) {
onVisible();
} else {
onHidden();
}
}
代码中的注释已经很详尽了,在fragment可见的时候会调用onVisible()不可见的时候会调用onHidden(),继承这个baseFragment你就可以快乐的使用onVisible()和onHidden()来进行数据加载咯~~
注意:如果您的fragment是配合viewpager使用的,因为viewpager当前页的左右两个fragment都会默认加载,且切换的时候并不会调用onhiddenChanged()方法,所以我上面的方法在viewpager+fragment的组合下有一定的缺陷(例如:viewpager中有两个fragment,当从别的页面回到该viewpager所在页面时,两个fragment都会调用onvisible方法,而在左右滑动两个fragment的时候并不会调用onvisible和onhidden)
因此就有了下面的 加强版 适配ViewPager setUserVisibleHint()的BaseFragment
二.包括ViewPager+Fragment的复杂情景通用封装
代码如下:
public abstract class BaseFragment extends Fragment {
protected final String TAG = this.getClass().getSimpleName();
/**
* Fragment当前状态是否可见
*/
protected boolean isVisible = false;
/**
* Fragment的view是否已创建
*/
protected boolean mIsViewCreated = false;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.xxx,container,false);
System.out.println(TAG + "--onCreateView");
mIsViewCreated = true;
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
System.out.println(TAG + "--onDestroyView");
mIsViewCreated = false;
}
@Override
public void onDestroy() {
super.onDestroy();
System.out.println(TAG + "--onDestroy");
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (!mIsViewCreated)//view没有创建的时候不进行操作
return;
if (getUserVisibleHint()) {
if (!isVisible) {//确保在一个可见周期中只调用一次onVisible()
isVisible = true;
onVisible();
}
} else {
if (isVisible) {
isVisible = false;
onHidden();
}
}
}
/**
* 可见
*/
protected void onVisible() {
}
/**
* fragment不可见的时候操作,onPause的时候,以及不可见的时候调用
*/
protected void onHidden() {
}
@Override
public void onResume() {//和activity的onResume绑定,Fragment初始化的时候必调用,但切换fragment的hide和visible的时候可能不会调用!
super.onResume();
System.out.println(TAG + "--Base onResume");
if (isAdded() && !isHidden()) {//用isVisible此时为false,因为mView.getWindowToken为null
onVisible();
isVisible = true;
}
}
@Override
public void onPause() {
System.out.println(TAG + "--Base onPause");
if (isVisible()||isVisible) {
onHidden();
isVisible = false;
}
super.onPause();
}
@Override
public void onHiddenChanged(boolean hidden) {//默认fragment创建的时候是可见的,但是不会调用该方法!切换可见状态的时候会调用,但是调用onResume,onPause的时候却不会调用
super.onHiddenChanged(hidden);
System.out.println(TAG + "--Base onHiddenChanged:" + hidden);
if (!hidden) {
onVisible();
isVisible = true;
} else {
onHidden();
isVisible = false;
}
}
}
此时 一样在继承的Fragment中 直接使用 onVisible() 以及 onHidden() 即可,此加强版适用于所有使用到fragment的地方,使用viewPager+fragment的亲们 可以愉快的开发啦~~
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
下一篇