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生命周期函数

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可以做那些事情:

  1. 通过 findFragmentById()(对于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段。
  2. 通过 popBackStack()(模拟用户发出的返回命令)将片段从返回栈中弹出。
  3. 通过 addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。
  4. 开启一个事务。

      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的操作大致可以分为三类:

  1. 显示操作:add()、 replace()、 show()、 attach()。
  2. 隐藏操作:remove() 、hide() 、detach()。
  3. 添加回退栈: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

如发现文章的错误欢迎大家指正提出
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页