Fragment简单介绍
Posted tuacy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fragment简单介绍相关的知识,希望对你有一定的参考价值。
一、Fragment概念以及设计原理
Fragment是android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿,而且我们普通手机开发也会加入这个Fragment,我们可以把他看成一个小型的Activity,又称Activity片段!想想,如果一个很大的界面,我们就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦!而使用Fragment我们可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理!从而可以更加方便的在运行过程中动态地更新Activity的用户界面!另外Fragment并不能单独使用,他需要嵌套在Activity中使用,尽管他拥有自己的生命周期,但是还是会受到宿主Activity的生命周期的影响。比如,当Activity暂停时,其中的所有片段也会暂停;当Activity被销毁时,所有片段也会被销毁。 不过,当Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个片段,如添加或移除它们。 当您执行此类片段事务时,您也可以将其添加到由Activity管理的返回栈,Activity中的每个返回栈条目都是一条已发生片段事务的记录。返回栈让用户可以通过按返回按钮撤消片段事务(后退)。
二、android.support.v4.app.Fragment和android.app.Fragment
- android.app.Fragment 兼容的最低版本是android:minSdkVersion=”11” 即3.0版。
- android.support.v4.app.Fragment 兼容的最低版本是android:minSdkVersion=”4” 即1.6版。
推荐用android.support.v4.app.Fragment。
请注意:一个应用中,千万不要混合使用给自己增加不必要的麻烦。最好一个应用里面都用v4包或者app包的Fragment。
三、Fragment生命周期函数
生命周期函数 | 相关解释 |
---|---|
onAttach() | 关联到Activity的时候调用。如果,需要使用Activity的引用或者使用Activity作为其他操作的上下文,将在此回调方法中实现 |
onCreate() | 系统创建Fragment的时候回调 |
onCreateView() | 当第一次绘制Fragment的UI时系统调用这个方法,该方法将返回一个View,如果Fragment不提供UI也可以返回null。注意,如果继承自ListFragment,onCreateView()默认的实现会返回一个ListView,所以不用自己实现。这个函数的Bundle参数和onCretate()函数的Bundle蚕食是同一个 |
onActivityCreated() | 当Activity中的onCreate方法执行完后调用。可以在这个函数里面做和Activity UI交互的操作(因为Activity的onCreate()函数之后Activity的UI已经准备好了,可以UI交互)。这个函数的Bundle参数和onCretate()函数的Bundle蚕食是同一个 |
onStart() | 启动Fragment的时候回调,这个时候Fragment可见 |
onResume() | Fragment变为活动状态获取焦点的时候是回调,这个时候Fragment已经完全展示在前台,并且可以和用户交互 |
onPause() | Fragemnt变成非活动状态失去焦点的时候调用,注意这个时候Fragment还是可见的,只是不能和用户交互了而已 |
onStop() | Fragment变成不可见的时候调用。这个时候Fragment还是活着的,只是可能别加入到了Fragment的回退栈中 |
onDestroyView() | Fragment中的布局被移除的时候调用 |
onDestroy() | Fragment被销毁的时候调用 |
onDetach() | Fragment和Activity解除关联的时候调用个 |
>
除了上面列出的标准周期函数之外,还有几个函数也要特别注意:
- onViewCreated(): 是不是和onCreateView()很像,onViewCreated()是在onCreateView()函数之后执行,我们通常在onViewCreated()函数里面findViewById。
- setUserVisibleHint():当前页面是否可见(一般ViewPager+Fragemnt配合使用会用到,懒(延时)加载的时候这个函数有大用处),因为ViewPager+Fragemnt的时候是会同时去加载前后多个Fragment的,这个时候就有些Fragment是可见的一些Fragment是不可见的。有一点要注意setUserVisibleHint()只在ViewPager+Fragment这情况下才会回调,其他静态加载和动态加载Fragment不会被调用到。。
- onHiddenChanged():hide()、show()来回切换Fragment显示的时候,Fragment只会回调onHiddenChanged()。Fragment在add()的时候不会回调onHiddenChanged()函数,这点要切记。还有,在ViewPager+Fragment使用的时候Fragment也不会回调onHiddenChanged()函数的。
四、Fragment的使用
在Fragment使用之前,有三几个特别重要的类要先来了解下:FragmentManager、FragmentTransaction、FragmentManager.BackStackEntry。
- FragmentManager:FragmentManager是负责管理Fragment并将它们的视图添加到Activity视图层级结构中的一个管理类。
FragmentManage可以做那些事情:
- 通过 findFragmentById()(对于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段。
- 通过 popBackStack()(模拟用户发出的返回命令)将片段从返回栈中弹出。
- 通过 addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。
- 开启一个事务。
FragmentManager相关API函数解释如下:
/**
* 开启一个事务,用于对Fragment操作的一系列处理
*/
public abstract FragmentTransaction beginTransaction();
/**
* 立即执行挂起的事物FragmentTransaction里面的,commit()、popBackStack()都不是立即执行的,
* 它会被发送到主线程的任务队列当中去, 当主线程准备好执行它的时候执行.
* 但是有时候你希望你的操作是立即执行的,在commit()只会调用该函数。
*/
public abstract boolean executePendingTransactions();
/**
* 通过Fragment所在的布局id,查找Fragment.查找规则:
* 1. 先在add列表里面查找。记住一定是拿最顶上的那个Fragment, (add A、add B. 这个时候就算你把B隐藏了,拿到的还是B).
* 2. 第一步没找到的情况下,接着就去回退栈里面查找。
*/
public abstract Fragment findFragmentById(@IdRes int id);
/**
* 通过Fragment的Tag找到Fragment(添加Fragment的时候会保证tag唯一)
*/
public abstract Fragment findFragmentByTag(String tag);
/**
* 弹出堆栈中顶部的Fragment并且显示,类似按下返回键的操作(不是立即执行的,
* 它会被发送到主线程的任务队列当中去, 当主线程准备好执行它的时候执行)
*/
public abstract void popBackStack();
/**
* 弹出堆栈中顶部的Fragment并且显示(立即执行)
*/
public abstract boolean popBackStackImmediate();
/**
* name可以为null或者相对应的BackStackEntry 的名字(在FragmentTransaction的addToBackStack()可以设置该名字),flags只有0和1(POP_BACK_STACK_INCLUSIVE)两种情况
* 1. 如果name为null,flags为0时,弹出回退栈中最上层的那个fragment。
* 2. 如果name为null ,flags为1时,弹出回退栈中所有fragment。
* 3. 如果name不为null,flags为0时,那就会找到这个tag所对应的fragment,弹出该fragment以上的Fragment,
* 4. 如果name不为null,flag为是1,弹出该fragment(包括该fragment)以上的fragment。
*/
public abstract void popBackStack(String name, int flags);
/**
* 同上(唯一的区别就是会立即执行)
*/
public abstract boolean popBackStackImmediate(String name, int flags);
/**
* 与popBackStack(String name, int flags)类似,id是BackStackEntry对应的id
*/
public abstract void popBackStack(int id, int flags);
/**
* 同上
*/
public abstract boolean popBackStackImmediate(int id, int flags);
/**
* 得到回退栈中BackStackEntry的数量
*/
public abstract int getBackStackEntryCount();
/**
* 根据序号返回后台堆栈中的BackStackEntry对象(按照添加回退栈的顺序)
*/
public abstract FragmentManager.BackStackEntry getBackStackEntryAt(int index);
/**
* 为添加回退堆栈添加一个监听器,用于监听堆栈的改变情况
*/
public abstract void addOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener);
/**
* 移除监听堆栈的监听器
*/
public abstract void removeOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener);
/**
* 把一个Fragment对象放入到bundle中, 和getFragment函数对应。比如你可以在onSaveInstanceState()函数中调用该函数
* 保存一个Fragment
*/
public abstract void putFragment(Bundle bundle, String key, Fragment fragment);
/**
* 根据key从bundle中取出之前putFragment()的Fragment。比如你可以在onRestoreInstanceState()函数中调用该函数
* 恢复之前保存的Fragment
*/
public abstract Fragment getFragment(Bundle bundle, String key);
/**
* 得到加入FragmentManager中所有的Fragment(这些Fragment都是通过FragmentTransaction加入)。
* 不包括回退栈中,以及已经detached或者removed掉的。
*/
public abstract List<Fragment> getFragments();
/**
* 保存给定Fragment的当前实例状态,返回值得到的状态可以用Fragment的
* setInitialSavedState()方法设置给新的Fragment实例, 作为初始状态.
* 当如这个函数的使用也是有限制的:
* 1. 保持状态的Fragment必须attach到FragmentManager中。
* 2. 新创建的Fragment必须和状态对应的Fragment相同的class类型。
* 3. 保存状态的Fragment不能依赖其他的Fragment,并且不能使用 putFragment(Bundle, String, Fragment)函数
*/
public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);
/**
* Activity的onDestroy()是否调用
*/
public abstract boolean isDestroyed();
/**
* 注册监听FragmentManager中所有Fragment的生命周期
*/
public abstract void registerFragmentLifecycleCallbacks(FragmentManager.FragmentLifecycleCallbacks cb, boolean recursive);
/**
* 注销监听FragmentManager中所有Fragment的生命周期
*/
public abstract void unregisterFragmentLifecycleCallbacks(FragmentManager.FragmentLifecycleCallbacks cb);
/**
* 返回FragmentManager里面当前活动的主导航Fragment。
*/
public abstract Fragment getPrimaryNavigationFragment();
- FragmentTransaction:所有对Fragment的动态操作都是通过FragmentTransaction事务来提交执行。FragmentTransaction是一个事物(事务是在同一时刻执行的一组动作,很像数据库中的事务)。你可以用add(),remove(),replace()等方法构成事务,最后使用commit()方法提交事务。在调用commint()之前,你可以用addToBackStack()把事务添加到一个后退栈中,这个后退栈属于所在的Activity。有了它,就可以在用户按下返回键时,返回到Fragment们执行事务之前的状态。
FragmentTransaction中对Fragment的操作大致可以分为三类:
- 显示操作:add()、 replace()、 show()、 attach()。
- 隐藏操作:remove() 、hide() 、detach()。
- 添加回退栈:addToBackStack()。
只能在Activity处于可保存状态的状态时,比如running中,onPause()方法和onStop()方法中提交事务,否则会引发异常。这是因为Fragment的状态会丢失。如果要在可能丢失状态的情况下提交事务,请使用commitAllowingStateLoss()。
FragmentTransaction相关API函数解释如下:
/**
* 添加一个Fragment到FragmentManager中(注意这里没写Fragment要显示在那个id上,所以这个Fragment要显示的时候是看不到的)
* 同一个Fragment(或者相同的tag)不能多次添加否则会报IllegalStateException
*/
public abstract FragmentTransaction add(Fragment fragment, String tag);
/**
* 添加一个Fragment到FragmentManager中(tag = null)
*/
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);
/**
* 添加一个Fragment到FragmentManager中(containerViewId 表示Fragment要放置在在哪个位置)
*/
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment, @Nullable String tag);
/**
* 替换掉指定位置(containerViewId)上所有的Fragment(记住是containerViewId上所有的)
* containerViewId 上加入了两个Fragment A、B。如果用C来replace掉containerViewId上的Fragment。
* 那么A,B都会被相当于调用了FragmentTransaction里面的remove()函数。
*/
public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment);
/**
* 替换掉指定位置(containerViewId)上所有的Fragment
*/
public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment, @Nullable String tag);
/**
* 移除掉指定的Fragment
*/
public abstract FragmentTransaction remove(Fragment fragment);
/**
* 隐藏指定的Fragment
*/
public abstract FragmentTransaction hide(Fragment fragment);
/**
* 显示指定的Fragment(配合hide使用)
*/
public abstract FragmentTransaction show(Fragment fragment);
/**
* 会将view与fragment分离,将此将view从view tree中删除(onPause、onStop、onDestroyView)!而且将fragment
* 从Activity的ADD队列中移除!所以在使用detach()后,使用fragment::isAdded()
* 返回的值是false;但此fragment实例并不会删除,此fragment的状态依然保持着使用,
* 所以在fragmentManager中仍然可以找到,即通过FragmentManager::findViewByTag()仍然是会有值的
*/
public abstract FragmentTransaction detach(Fragment fragment);
/**
* 显然这个方法与detach()所做的工作相反,它一方面利用fragment的onCreateView()
* 来重建视图(onCreateView、onActivityCreate、onStart、onResume),一方面将此fragment添加到ADD队列中;这里最值得注意的地方在这里:
* 由于是将fragment添加到ADD队列,所以只能添加到列队头部,所以attach()操作的结果是,
* 最新操作的页面始终显示在最前面!
*/
public abstract FragmentTransaction attach(Fragment fragment);
/**
* 设置一个当前活动的主导航Fragment。(还没有搞清楚这个东西的作用)
*/
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);
/**
* 当前事务是否有操作
*/
public abstract boolean isEmpty();
/**
* 设置进入/退出的动画效果(资源文件)。这个必须位于replace、add、remove之前,否则效果不起作用。
*/
public abstract FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int enter, @AnimatorRes @AnimRes int exit);
/**
* 设置进入/退出的动画效果(资源文件)。这个必须位于replace、add、remove之前,否则效果不起作用。
* 四个参数分别表示:添加、移除、从BackStack中pop出来、进入的动画效果。
*/
public abstract FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int enter,
@AnimatorRes @AnimRes int exit,
@AnimatorRes @AnimRes int popEnter,
@AnimatorRes @AnimRes int popExit);
/**
* Fragment切换时, 有一些元素(element)会保持不变, 使用该函数使这些元素切换时, 赋予动画效果。
* 关于这部分的内容可以去搜素下Android的共享元素动画,很有意思的一个东西。
*/
public abstract FragmentTransaction addSharedElement(View sharedElement, String name);
/**
* 设置切换效果。目前API提供:TRANSIT_NONE、 TRANSIT_FRAGMENT_OPEN、TRANSIT_FRAGMENT_CLOSE三种。
*/
public abstract FragmentTransaction setTransition(/*@FragmentTransaction.Transit*/ int transit);
/**
* 设置切换的风格
*/
public abstract FragmentTransaction setTransitionStyle(@StyleRes int styleRes);
/**
* 添加commit执行之前的操作到后台堆栈中(对应会生成一个FragmentManager.BackStackEntry对象)
*/
public abstract FragmentTransaction addToBackStack(@Nullable String name);
/**
* 是否允许添加到后台堆栈,如果是不允许的状态addToBackStack()会抛异IllegalStateException常
*/
public abstract boolean isAddToBackStackAllowed();
/**
* 设置不允许添加后台堆栈
*/
public abstract FragmentTransaction disallowAddToBackStack();
/**
* 设置面包屑导航栏的长标题
* (你可以认为就是保存了一个标题,然后可以通过FragmentManager.BackStackEntry 的getBreadCrumbTitle()
* 获取到该设置的标题)
*/
public abstract FragmentTransaction setBreadCrumbTitle(@StringRes int res);
/**
* 设置面包屑导航栏的长标题(可以看FragmentManager.BackStackEntry里面有对应的获取函数)
*/
public abstract FragmentTransaction setBreadCrumbTitle(CharSequence text);
/**
* 设置面包屑导航栏的短标题(可以看FragmentManager.BackStackEntry里面有对应的获取函数)
*/
public abstract FragmentTransaction setBreadCrumbShortTitle(@StringRes int res);
/**
* 设置面包屑导航栏的短标题(可以看FragmentManager.BackStackEntry里面有对应的获取函数)
*/
public abstract FragmentTransaction setBreadCrumbShortTitle(CharSequence text);
/**
* 设置是否优化事务操作的执行,去掉一些冗余的操作,比如这么个情况,两个事务同时执行,一个事务是添加A Fragment,
* 另一个事务是用B 去 替换掉A。如果做优化,会跳过A直接添加B.
*/
public abstract FragmentTransaction setReorderingAllowed(boolean reorderingAllowed);
/**
* 当事务commit的时候,执行指定的Runnable
*/
public abstract FragmentTransaction runOnCommit(Runnable runnable);
/**
* 提交事务(commit并不会立即执行的,系统会在适当的时候执行)
*/
public abstract int commit();
/**
* 允许保存在存储状态之后 提交事务(Activity 的onSaveInstanceState()只会还可以提交事务)
*/
public abstract int commitAllowingStateLoss();
/**
* 立即提交事务(用这个函数提交事务,不能添加到回退栈)
*/
public abstract void commitNow();
/**
* 允许保存在存储状态之后 ,立即提交事务(用这个函数提交事务,不能添加到回退栈)
*/
public abstract void commitNowAllowingStateLoss();
- FragmentManager.BackStackEntry:可以认为是Fragment back stack中堆栈的一个记录,在我们自己去操作Fragment回退栈的时候会经常用到。
FragmentManager.BackStackEntry相关API函数解释如下:
/**
* 回退栈对应的id(自动分配)
*/
public int getId();
/**
* 回退栈对应名字 FragmentTransaction addToBackStack(String)的时候设置
*/
public String getName();
/**
* 面包屑对应的名字的资源id,和FragmentTransaction setBreadCrumbTitle(@StringRes int res) 对应
* FragmentTransaction addToBackStack(String) 之前调用FragmentTransaction setBreadCrumbTitle(@StringRes int res)
*/
@StringRes
public int getBreadCrumbTitleRes();
/**
* 面包屑对应的名字的资源id,和FragmentTransaction setBreadCrumbShortTitle(@StringRes int res) 对应
*/
@StringRes
public int getBreadCrumbShortTitleRes();
/**
* 面包屑对应的名字的资源id,和FragmentTransaction setBreadCrumbTitle(CharSequence text) 对应
*/
public CharSequence getBreadCrumbTitle();
/**
* 面包屑对应的名字的资源id,和FragmentTransaction setBreadCrumbShortTitle(CharSequence text) 对应
*/
public CharSequence getBreadCrumbShortTitle();
4.1 静态添加Fragment
静态Fragment使用,这个是最简单的了,就布局文件里面fragment标签,android:name就指定Fragment的路径。<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/fragment_a"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.tuacy.example.statics.StaticFragmentA"/>
<fragment
android:id="@+id/fragment_b"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.tuacy.example.statics.StaticFragmentB"/>
</LinearLayout>
4.2 动态添加Fragment
通过FragmentManager和FragmentTransaction里面的一些函数来动态增删改Fragment。动态Fragment使用的时候,要有一个概念,Fragment要显示肯定是要依托于指定的id(容器)。可以简单的理解为每个id容器都对应一个数组,这个数组里面放的就是对这个id容器上所有的Fragment(FragmentTransaction类的add、show、hide、replace、remove、attach、detach就是对这个数组做相应的增删操作)。决定这个id容器展示给用户的是哪个Fragment就看这个数组最从后往前哪个Fragment是显示(show)的状态。 通过一个实例来瞧一瞧动态Fragment使用(FragmentTransaction类里面的add、show、hide、replace、remove、attach、detach函数的使用)。 add():就相当于往id容器里面添加Fragment,注意一点就够add进去的默认都是shw的状态。比如我们用如下的代码依次添加三个Fragmetn,A、B、C FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.layout_content_fragment, mFragmentA);
transaction.add(R.id.layout_content_fragment, mFragmentB);
transaction.add(R.id.layout_content_fragment, mFragmentC);
transaction.commit();
这个时候id容器展示给用户的是C,最上面第一个显示状态的是C。如图所示: ![Fragment add](https://img-blog.csdn.net/20171202090149234?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) hide()、show():就是去改变对应Fragment的显示状态(show or hide)。 ![hide b](https://img-blog.csdn.net/20171202090317822?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ![hide c](https://img-blog.csdn.net/20171202090401197?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) remove():相当于从数组里面把指定的Fragment给移除掉了。 ![remove](https://img-blog.csdn.net/20171202090434134?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) replace():就是把指定id容器响应的数组清空掉,然后在把replace要放入的Fragment放入进去。
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.layout_content_fragment, mFragmentD);
transaction.commitNow();
![replace](https://img-blog.csdn.net/20171202090502797?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) attach()、detach():detach会从容器数组中把Fragment删除,但是还会记录这个Fragment的一些状态,比如你先把这个Fragment hide掉,然后detach掉了,然后你又attach进来 这个Fragment的状态还是hide的状态。 ![attach detach](https://img-blog.csdn.net/20171202090537607?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V5dXhpbmcyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
FragmentTransaction中每个操作调用对应Fragment的生命周期里面的哪些函数:
- add():onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()。
- remove():onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()。
- show(): onHiddenChanged() - >false。
- hide(): onHiddenChanged() - >true。
- attach(): 已经add的前提下detach掉在attach进来的情况 onCreateView()->onActivityCreated()->onStart()->onResume()。
- detach(): onPause()->onStop()->onDestroyView()。
- replace():相当于把之前所有的remove掉,在add进来。对应remove和add对Fragment的生命周期的影响。
动态Fragment在使用过程中还要几个要注意的点:
- 同一个Fragment的实例对象只能add一次。就算是add到同一个activity里面的不同的id容器里面也只能一次。
- detach掉的Fragment会保存它detach之前的状态,下次attach进来还是之前的状态。比如你detach之前把Fragment hide隐藏掉了,attach进来还是隐藏的状态。
- 没有add过的Fragment直接调用attach,和add产生的效果一致。
4.3 ViewPager + Fragment
ViewPager + Fragment指的是每个pager都是一个Fraagment。 关于ViewPager + Fragment使用过程中Adapter:FragmentPagerAdapter、FragmentStatePagerAdapter的区别:- FragmentPagerAdapter:会将每个生成的Fragment对象一直存留在内存中,所以当有大量的pager显示页时不太适用。
- FragmentStatePagerAdapter:会销毁setOffscreenPageLimit()函数设置的limit之外的Fragment对象。
FragmentPagerAdapter、FragmentStatePagerAdapter的选择要根据实际的情况来定,如果pager只有三四页那咱们完全可以使用FragmentPagerAdapter避免每次重新加载的问题。如果pager比较多(比如实现无限加载)咱就选用FragmentStatePagerAdapter。
关于ViewPager + Fragment使用过程中的Fragment,涉及到Fragment懒加载的问题。懒加载说白了就是延时加载,把加载延时到Fragment对用户可见的时候。增强用户的体验。举一个很直白的例子,有三个pager对应三个Fragment,并且这三个Fragment都要做网络请求。如果不做懒加载的时候三个Fragement就要同时去请求数据。体验就不是那么的好了。懒加载就很好的规避了这个问题,当切换到哪一页的是那个Fragment才去做网络的请求加载数据。
Fragment懒加载:对懒加载的封装,就是对ragment周期函数的灵活使用。最主要的是围绕setUserVisibleHint()函数做各种变通。
网上很多大神都给出了对Fragment加载的封装:这里列出一个经常用的封装,在里面给加了一些注释,方便大家的。
/**
* 懒加载Fragment
*/
public abstract class LazyFragment extends Fragment
protected Context mContext = null;
/**
* 判断是不是第一次resume
*/
private boolean isFirstResume = true;
/**
* 判断是不是第一次可见(只会在setUserVisibleHint中判断和改变)
*/
private boolean isFirstVisible = true;
/**
* 判断是不是第一次不可见(只会在setUserVisibleHint中判断和改变)
*/
private boolean isFirstInvisible = true;
/**
* 标记是否准备加载数据,因为我们不能在setUserVisibleHint马上去加载数据
* setUserVisibleHint调用的只会,可能视图都还没有加载出来。
*/
private boolean isPrepared = false;
@Override
public void onAttach(Context context)
super.onAttach(context);
mContext = context;
@Override
public void onActivityCreated(Bundle savedInstanceState)
super.onActivityCreated(savedInstanceState);
initPrepare();
@Override
public void onResume()
super.onResume();
if (isFirstResume)
isFirstResume = false;
return;
/**
* 这里的情况是为了避免,比如你锁屏之后再解锁,这个时候也是用户可见的情况
* 并且这种情况是不会调用setUserVisibleHint()函数的
*/
if (getUserVisibleHint())
onUserVisible();
@Override
public void onPause()
super.onPause();
/**
* 这里的情况是为了避免,比如你锁屏之后载解锁
*/
if (getUserVisibleHint())
onUserInvisible();
/**
* setUserVisibleHint 函数第一次调用肯定给的是false,第二次才是true
*/
@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();
private synchronized void initPrepare()
if (isPrepared)
onFirstUserVisible();
else
isPrepared = true;
/**
* 第一次对用户可见的时候调用,在这里懒加载数据
*/
protected abstract void onFirstUserVisible();
/**
* 第二次包括第二次对用户可见的时候调用
*/
protected void onUserVisible()
/**
* 第一次对用户不可见的时候调用
*/
protected void onFirstUserInvisible()
/**
* 第二次包括第二次对用户不可见的时候调用
*/
protected void onUserInvisible()
4.4 Fragment回退栈的使用
Fragment的回退栈,类似与Android系统为Activity维护一个任务栈。我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。
- 添加进回退栈:FragmentTransaction.addToBackStack(String)
- 从回退栈中退出:FragmentManager中的popBackStack()、popBackStackImmediate()、getBackStackEntryCount()、getBackStackEntryAt()函数使用。
/**
* 弹出堆栈中顶部的Fragment并且显示,类似按下返回键的操作(不是立即执行的,
* 它会被发送到主线程的任务队列当中去, 当主线程准备好执行它的时候执行)
*/
public abstract void popBackStack();
/**
* 弹出堆栈中顶部的Fragment并且显示(立即执行)
*/
public abstract boolean popBackStackImmediate();
/**
* name可以为null或者相对应的BackStackEntry 的名字(在FragmentTransaction的addToBackStack()可以设置该名字),flags只有0和1(POP_BACK_STACK_INCLUSIVE)两种情况
* 1. 如果name为null,flags为0时,弹出回退栈中最上层的那个fragment。
* 2. 如果name为null ,flags为1时,弹出回退栈中所有fragment。
* 3. 如果name不为null,flags为0时,那就会找到这个tag所对应的fragment,弹出该fragment以上的Fragment,
* 4. 如果name不为null,flag为是1,弹出该fragment(包括该fragment)以上的fragment。
*/
public abstract void popBackStack(String name, int flags);
/**
* 同上(唯一的区别就是会立即执行)
*/
public abstract boolean popBackStackImmediate(String name, int flags);
/**
* 与popBackStack(String name, int flags)类似,id是BackStackEntry对应的id
*/
public abstract void popBackStack(int id, int flags);
/**
* 同上
*/
public abstract boolean popBackStackImmediate(int id, int flags);
/**
* 得到回退栈中BackStackEntry的数量
*/
public abstract int getBackStackEntryCount();
/**
* 根据序号返回后台堆栈中的BackStackEntry对象(按照添加回退栈的顺序)
*/
public abstract FragmentManager.BackStackEntry getBackStackEntryAt(int index);
/**
* 为添加回退堆栈添加一个监听器,用于监听堆栈的改变情况
*/
public abstract void addOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener);
/**
* 移除监听堆栈的监听器
*/
public abstract void removeOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener);
为了方便大家的理解Fragment的回退栈管理,我们给出一个全面的例子。参考下文给的实例中的,回退栈管理。
五、Fragment参数传递
在开发过程中经常会向Fragment传递参数,在最初的时候我是通过构造函数传递参数,或者通过放出一个set方法出来传递参数,其实这两种方式是不对的,因为,当一个Fragment一旦由于什么原因(横竖屏切换)导致你的Fragment重新创建的时候,系统会把之前通过set方法设置的参数全部清掉,并且再次调用的是 Fragment中的默认构造函数(是默认构造函数,是没有参数的那个构造函数)根本就不会走到带参数的构造函数。 最正确的方式是通过 setArguments()、getArguments()来进行参数的传递和获取。
六、Fragment状态保存和恢复
讲到Fragment的状态保存和恢复,咱就得简单的来理一理Android里面的状态保存和恢复了。
Android中为什么会存在状态的保存和恢复一说。因为,Android系统中每个应用的内存都比较紧张的,Android展示都是通过Activity(里面有Fragment、View),当Activity退到后台的时候(onStop状态),且长时间不用或前台Activity需要更多资源导致系统必须杀死后台进程回收内存时,系统会销毁你的Activity。这个时候虽然Activity的实例是消失了,但系统会记录它的存在,如果用户下次返回到这个Activity,系统会使用该Activity销毁时保存的状态数据重建一个新的Activity实例。这个系统保存的用来恢复到之前状态的数据叫做“实例状态”,它以键值对的形式保存在Bundle对象中。
Android中的销毁分两种情况:
- 正常销毁。正常销毁是指我们用户主动销毁Activity比如back返回,finish的调用,这时该实例(Activity、Fragment、View)将永久的消失,因为这些行为表示该实例不再被需要。
- 非正常销毁。非正常销毁是指当Activity处于stopped状态且长时间不用时或前台Activity需要更多资源导致系统必须杀死后台进程回收内存时,系统会销毁你的Activity。
只有非正常销毁的时候才会用到状态的恢复,非正常销毁实际上Activity的实例是消失了,但系统会记录它的存在,这是如果用户回到它,系统会使用该Activity销毁时保存的状态数据重建一个新的Activity实例。这个系统保存的用来恢复到之前状态的数据叫做“实例状态”,它以键值对的形式保存在Bundle对象中。
Android中的状态的保存和恢复。我们分为两个部分:一个是状态的保存、一个是状态的恢复:
- 状态保存:状态的保存在退到后台的时候就会触发(不管是正常的退到后台还是非正常的退到后台)。
- 状态的恢复:状态的恢复一定是在非正常退出的在回来的时候会触发。所以状态保存的次数一定是大于等于状态恢复的次数。
Android中的状态保存和恢复我们分为三大块:Activity状态保存和恢复、Fragment状态保存和恢复、View状态保存和恢复。其实Activity的状态保存会触发Fragment和View去调用状态保存和恢复。
注:屏幕旋转被经常用来测试状态的保存和恢复
6.1 Activity状态保存恢复
Activity中onSaveInstanceState()用于保存Activity的状态、onRestoreInstanceState()用于恢复Activity保存的状态。当然也可以通过onCreate()函数来恢复保存的状态,onCreate()里面状态恢复的时候必须判断Bundle是否为null,如果为null,则是创建新的Activity,否则重建恢复Activity。如果使用onRestoreInstanceState()方法恢复状态,则不再需要对其中的Bundle进行判null,因为仅当有状态需要被恢复时,该方法才会被回调,且该方法在onStart()方法之后调用。
Activity状态的保存和恢复着重点在我们自定义的一些数据上。哪些是要保存和恢复心里有个底,比如,你的Activity有一个播放的功能,那视频播放的进度就是要保存和恢复的了。特别,特别要注意。在重写onSaveInstanceState()、onRestoreInstanceState()的时候一定要记得调用super方法。因为,在Android中当Activity的onSaveInstanceState调用的时候,因为在super中Activity会自动收集View层级中每个View的状态。请注意只有在内部实现了View的保存/恢复状态方法的View才会被收集到。一旦onRestoreInstanceState方法被调用,Activity会把收集的数据发送回给View结构树中具有相同android:id配置的View。同时也会触发Activity关联的Fragment的状态的保存和恢复。
onSaveInstanceState()会在Activity的onPause()方法之后调用、onRestoreInstanceState()方法会在onStart()之后调用。
Bundle中保存的数据一定要经过序列化,只能保存1M以下的数据。
6.2 Fragment状态保存和恢复
Fragment的状态保存和恢复和Activity产生的时机是一样的,Activity的状态保存和恢复会触发该Activity关联的Fragment的状态保存和恢复。Fragment中在onSaveInstanceState()中保存状态,在onCreateView()、onActivityCreated()、onViewCreated()任何一个函数中恢复数据都可以。
Fragment的使用稍稍复杂一点,Fragment要依托容器来显示,可能这个容器里面放置了好几个Fragment,这些Fragment的状态保存和恢复都是一样的。在Activity有状态保存和恢复的时候,都是会触发这个容器里面所有的Fragment的状态保存和恢复。
当Fragment的使用过程中使用了addToBackStack()的时候,在状态保存和恢复的时候这个也是会完全恢复的。屏幕旋转之后返回的时候还是能返回上一次保存的Fragment状态的。
setRetainInstance()函数的作用,将该Fragment转变为“持久化Fragment”,这意味着只有该Fragment的GUI(从onViewCreated()方法返回的View)会被销毁重建(就是Frament第一次创建之后,当由于内存不足要重复创建的时候不会调用onCreate()和onDestroy()函数。其他的函数还是会调用的)而所有其他的引用变量仍然保持。
Dialog弹窗的时候也是推荐使用DialogFragment来替换,你想下比如有这么一种情况,当你弹出了Dialog的时候这个时候进行屏幕的切换,我肯定希望Dialog还存在吧而且Dialog中的输入框里面之前输入的字符还存在。DialogFragment就能完美的解决这一问题。
6.3 View状态保存和恢复
在开发过程中自定义View是我们经常干的事情。在自定义View的时候 就要时刻注意View状态保存恢复,Android中原生的View已经帮我们处理好了,我们不用管。我们就就管我们自定义的View。
注意:为了让Activity在状态保存和恢复的过程中能触发调用View的状态保存和恢复,我们一定要在Activity的布局文件中给这个View id android:id,否则View的状态保存和恢复函数不会调用
下面给出一个通用自定义View状态保存恢复的模板。
@Nullable
@Override
protected Parcelable onSaveInstanceState()
Bundle bundle = new Bundle();
bundle.putParcelable(BUNDLE_SUPER, super.onSaveInstanceState());
/**
* 这里通过bundle放置自定义的要保存的一些状态
*/
return bundle;
@Override
protected void onRestoreInstanceState(Parcelable state)
Bundle bundle = (Bundle) state;
super.onRestoreInstanceState(bundle.getParcelable(BUNDLE_SUPER));
/**
* 这里通过bundle得到我们要恢复的一些状态
*/
本文涉及到的所有相关实例(可能参考价值不是特别的大):实例DEMO
如发现文章的错误欢迎大家指正提出
以上是关于Fragment简单介绍的主要内容,如果未能解决你的问题,请参考以下文章
将Sqlite数据放入两个不同Fragment内的两个ListView
当 Fragment 被替换并放入回栈(或删除)时,它是不是保留在内存中?