FragmentTransaction详解

FragmentTransaction API文档(需要翻墙)

常用方法详解

1. add(int containerViewId, Fragment fragment, String tag)、 remove(Fragment fragment)

API文档说明:

add(int containerViewId, Fragment fragment, String tag) : 向Activity state中添加一个Fragment。

remove(Fragment fragment) : 移除一个已经存在的Fragment.

补充:

  1. add方法 : 参数containerViewId一般会传Activity中某个视图容器的id。如果containerViewId传0,则这个Fragment不会被放置在一个容器中(不要认为Fragment没添加进来,只是我们添加了一个没有视图的Fragment,这个Fragment可以用来做一些类似于service的后台工作)。

  2. remove方法 : Fragment被remove后,Fragment的生命周期会一直执行完onDetach,之后Fragment的实例也会从FragmentManager中移除。

  3. 下面看一个使用add方法添加Fragment的坑,相信很多人都遇到过 -> “Fragment被重复添加的问题”

Fragment被重复添加的问题:现在,我们承接上一篇Fragment进阶 - 基本用法中“Fragment动态加载”的事例,给布局容器(fl_main)附加一个点击事件,Toast当前Activity中的所有Fragment信息...

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();

        findViewById(R.id.fl_main).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, getSupportFragmentManager().getFragments().toString(), Toast.LENGTH_LONG).show();
            }
        });
    }
}

接下来运行一下程序,我们进行几次横竖屏切换(模拟Activity的异常重启),并且每次切换都点击下布局容器,Toast下信息...

add_fragment_bug.gif

(PS:因录像软件原因,手机横竖屏切换显得有点别扭,各位见谅,Toast看清就Ok)

现象:我们发现Activity里的Fragment数量在不断的增加。

原因:其实,当我们Activty被异常销毁时,Activity会对自身状态进行保存(这里面包含了我们添加的Fragment)。在Activity被重新创建时,又对我们之前保存的Fragment进行了恢复,但是我们在onCreate时却又添加了1次Fragment,所以我们的Fragment数量在不断增加...

添加Fragment的正确姿势:(2种解决方法)

解决方法1(推荐):添加Fragment前检查是否有保存的Activity状态。如果没有状态保存,说明Acitvity是第1次被创建,我们添加Fragment。如果有状态保存,说明Activity刚刚出现过异常被销毁过,现在正在恢复,我们不再添加Fragment。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
        }

        findViewById(R.id.fl_main).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, getSupportFragmentManager().getFragments().toString(), Toast.LENGTH_LONG).show();
            }
        });
    }
}

运行下程序,查看下效果:

add_fragment.gif

解决方法2(不推荐):Activity在每次异常退出前,移除当前所有的Fragment。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();

        findViewById(R.id.fl_main).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, getSupportFragmentManager().getFragments().toString(), Toast.LENGTH_LONG).show();
            }
        });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if(getSupportFragmentManager().findFragmentById(R.id.fl_main) != null) {
            getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.fl_main)).commit();
        }
        super.onSaveInstanceState(outState);
    }
}

第2种解决方法的运行效果和第1种一样,就不再上图了...

对比两种解决方法:可能我们感觉方法2更加合理,但是我还是推荐第1种方法。

原因1:第2种方法很坑,移除操作只能放在super.onSaveInstanceState(outState)之前,之后移除需要使用commitAllowingStateLoss来替换commit,但是此时的移除操作会在恢复时丢失。

原因2:其实第2种方法还有个Bug,把手机灭屏再打开,会发现Fragment消失了,因为手机灭屏会调用onSaveInstanceState这个方法。

加入第2种方法是为了说明remove的用法,以及Fragment手动移除的坑。总之,Fragment的移除操作需要谨慎。

最后补充一点: add操作可以执行多次,然后一并提交(举个例子,代码如下)。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fl_main, new ContentFragment(), null)
                    .add(R.id.fl_main, new ContentFragment(), null)
                    .add(R.id.fl_main, new ContentFragment(), null)
                    .commit();
        }
    }
}

最后运行下程序,我们用Hierarchy Viewer看下视图的层次结构。

viewer4.png

和我们想象的一样,我们的Activity视图容器(fl_main)里包含了3个Fragment的视图。


2. replace(int containerViewId, Fragment fragment)、replace(int containerViewId, Fragment fragment, String tag)#####

API文档说明:替换一个已被添加进视图容器的Fragment。

补充:

  1. 方法作用:类似于先remove掉视图容器所有的Fragment,再add一个想要添加的Fragment。

(举个简单的例子 - 代码如下,我们add三次,replace一次)

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fl_main, new ContentFragment(), null)
                .add(R.id.fl_main, new ContentFragment(), null)
                .add(R.id.fl_main, new ContentFragment(), null)
                .replace(R.id.fl_main, new ContentFragment(), null)
                .commit();
    }
}

用Hierarchy Viewer看下视图的层次结构:

 

viewer6.png

我们发现只有一个Fragment的视图,说明之前add的Fragment都在replace时被视图容器移除了。


3. addToBackStack(String name)

API文档说明:将此事务添加到后台堆栈。这意味着该事务被提交后将会被记住后,回退操作后,事务会从堆栈中弹出。

补充:

  1. 方法作用:记录已提交的事务(transation),可用于回退操作。

  2. 参数说明:name是这次回退操作的一个名称(或标识),不需要可以传null。

下面还是以add方法举例:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fl_main, new ContentFragment(), null)
                    .addToBackStack(null)
                    .commit();
        }
    }

    @Override
    public void onBackPressed() {
        Toast.makeText(this, "onBackPressed", Toast.LENGTH_SHORT).show();
        super.onBackPressed();
    }
}

我们如果按下手机的back键,Activity会回调onBackPressed()方法...接下来看下运行效果...

addbackstack.gif

  • 第1次按下手机的back键,程序没有退出而是变成了白板,其实是回退了之前的add操作,相当于没有添加Fragment。

  • 第2次按下手机的back键,因为当前回退栈已空,程序就直接退出了。

这里是以add方法为例,当然addToBackStack也适用于remove、hide、show、attach、detach等操作。


4. show(Fragment fragment)、hide(Fragment fragment)

API文档说明:

  • hide(Fragment fragment) : 隐藏一个存在的Fragment

  • show(Fragment fragment) : 显示一个以前被隐藏过的Fragment

补充:

  1. Fragment被hide/show,仅仅是隐藏/显示Fragment的视图,不会有任何生命周期方法的调用。

  2. 在Fragment中重写onHiddenChanged方法可以对Fragment的hide和show状态进行监听。


5. attach(Fragment fragment)、detach(Fragment fragment)

API文档说明:

  • detach(Fragment fragment) : 分离指定Fragment的UI视图

  • attach(Fragment fragment) : 重新关联一个Fragment(当这个Fragment的detach执行之后)

补充:

  1. 当Fragment被detach后,Fragment的生命周期执行完onDestroyView就终止了,这意味着Fragment的实例并没有被销毁,只是UI界面被移除了(注意和remove是有区别的)。

  2. 当Fragment被detach后,执行attach操作,会让Fragment从onCreateView开始执行,一直执行到onResume。

  3. attach无法像add一样单独使用,单独使用会抛异常。方法存在的意义是对detach后的Fragment进行界面恢复。


6. setCustomAnimations(int enter, int exit)、setCustomAnimations(int enter, int exit, int popEnter, int popExit)

API文档说明:为Fragment的进入/退出设置指定的动画资源。

补充:

  1. 该方法用于给Fragment设置自定义切换动画。

  2. 四个参数方法的后两个参数也是传入指定动画资源,在某个事务被addToBackStack或回退时起动画效果。

  3. getSupportFragmentManager不支持属性动画,仅支持补间动画。getFragmentManager支持属性动画。

  4. 使用setCustomAnimations一定要放在add、remove...等操作之前。

举个简单的使用例子:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void add(View view){
        getSupportFragmentManager().beginTransaction()
                .setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim)
                .add(R.id.fl_main, new ContentFragment(), null)
                .commit();
    }

    public void remove(View view){
        if(getSupportFragmentManager().findFragmentById(R.id.fl_main) != null) {
            getSupportFragmentManager().beginTransaction()
                    .setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim)
                    .remove(getSupportFragmentManager().findFragmentById(R.id.fl_main))
                    .commit();
        }else{
            Toast.makeText(this, "未找到可移除的指定id的Fragment",Toast.LENGTH_SHORT).show();
        }
    }
}

我们在布局文件中添加了两个按钮,一个用于添加Fragment,一个用于移除Fragment...运行效果如下图:

animation.gif

7. addSharedElement(View sharedElement, String name)

API文档说明:用于将View从一个被删除的或隐藏的Fragment映射到另一个显示或添加的Fragment上。

补充:

  1. 方法作用:设置共享元素(或着说共享View),第二个参数name是这个共享元素的标识。

  2. 博客推荐: 使用SharedElement切换Fragment页面


8. commit()、 commitAllowingStateLoss()、 commitNow()、commitNowAllowingStateLoss()

API文档说明:

  1. commit():安排一个事务的提交。

  2. commitAllowingStateLoss():和commit一样,但是允许Activity的状态保存之后提交。

  3. commitNow():同步的提交这个事务。(API_24添加的)

  4. commitNowAllowingStateLoss():和commitNow()一样,但是允许Activity的状态保存之后提交。(API_24添加的)

补充:

  1. commit():安排一个针对该事务的提交。提交并没有立刻发生,会安排到在主线程下次准备好的时候来执行。

  2. commitNow():同步的提交这个事务。任何被添加的Fragment都将会被初始化,并将他们完全带入他们的生命周期状态。

  3. 使用commitNow()类似于先调用commit()方法,之后调通FragmentManager的
    executePendingTransactions() 方法,但commitNow()比后者的这种操作更好,后者在尝试提交当前事务时会有副作用(具体的副作用是什么,API文档没有给出说明)。

  4. 使用commitNow()时不能进行添加回退栈的操作,如果使用 addToBackStack(String)将会抛出一个 IllegalStateException的异常。

  5. 如何选择正确的提交方式:(博客推荐)

The many flavors of commit()(英文版)

选择正确的 Fragment#commitXXX() 函数(中文版)

后续小编还会写一篇博客:介绍commit的使用细节以及踩坑分析。

FragmentTransaction的全部方法(API 24)

最后我们给出FragmentTransaction提供的全部方法,方便各位快速了解和查阅。

  • add(Fragment fragment, String tag) // 调用add(int, Fragment, String),填入为0的containerViewId.
  • add(int containerViewId, Fragment fragment) // 调用add(int, Fragment, String),填入为null的tag.
  • add(int containerViewId, Fragment fragment, String tag) // 向Activity中添加一个Fragment.
  • addSharedElement(View sharedElement, String name) // 添加共享元素
  • addToBackStack(String name) // 将事务添加到回退栈
  • attach(Fragment fragment) // 重新关联Fragment(当Fragment被detach时)
  • commit() // 提交事务
  • commitAllowingStateLoss() // 类似commit(),但允许在Activity状态保存之后提交(即允许状态丢失)。
  • commitNow() // 同步提交事务
  • commitNowAllowingStateLoss() // 类似commitNow(),但允许在Activity状态保存之后提交(即允许状态丢失)。
  • detach(Fragment fragment) // 将fragment保存的界面从UI中移除
  • disallowAddToBackStack() // 不允许调用addToBackStack(String)操作
  • hide(Fragment fragment) // 隐藏已存在的Fragment
  • isAddToBackStackAllowed() // 是否允许添加到回退栈
  • isEmpty() // 事务是否未包含的任何操作
  • remove(Fragment fragment) // 移除一个已存在的Fragment
  • replace(int containerViewId, Fragment fragment) // 调用replace(int, Fragment, String)填入为null的tag.
  • replace(int containerViewId, Fragment fragment, String tag) // 替换已存在的Fragment
  • setBreadCrumbShortTitle(int res) // 为事务设置一个BreadCrumb短标题
  • setBreadCrumbShortTitle(CharSequence text) // 为事务设置一个BreadCrumb短标题,将会被FragmentBreadCrumbs使用
  • setBreadCrumbTitle(int res) // 为事务设置一个BreadCrumb全标题,将会被FragmentBreadCrumbs使用
  • setBreadCrumbTitle(CharSequence text) // 为事务设置一个BreadCrumb全标题
  • setCustomAnimations(int enter, int exit, int popEnter, int popExit) // 自定义事务进入/退出以及入栈/出栈的动画效果
  • setCustomAnimations(int enter, int exit) // 自定义事务进入/退出的动画效果
  • setTransition(int transit) // 为事务设置一个标准动画
  • setTransitionStyle(int styleRes) // 为事务标准动画设置自定义样式
  • show(Fragment fragment) // 显示一个被隐藏的Fragment
展开阅读全文

没有更多推荐了,返回首页