Fragment进阶-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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容