如何获取当前显示的片段?

536

我正在Android中使用片段。

我知道可以使用以下代码更改片段:

FragmentManager fragMgr = getSupportFragmentManager();
FragmentTransaction fragTrans = fragMgr.beginTransaction();

MyFragment myFragment = new MyFragment(); //my custom fragment

fragTrans.replace(android.R.id.content, myFragment);
fragTrans.addToBackStack(null);
fragTrans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragTrans.commit();

我的问题是,在Java文件中,如何获取当前显示的Fragment实例?


相关 - https://dev59.com/vGoy5IYBdhLWcg3wFqFT#21104084 - Jay Wick
1
可能是获取当前片段对象的重复问题。 - Sruit A.Suk
58个回答

509

当您在事务中添加片段时,应该使用标记。

fragTrans.replace(android.R.id.content, myFragment, "MY_FRAGMENT");

...然后如果您想要检查片段是否可见:

MyFragment myFragment = (MyFragment)getSupportFragmentManager().findFragmentByTag("MY_FRAGMENT");
if (myFragment != null && myFragment.isVisible()) {
   // add your code here
}

请参考 http://developer.android.com/reference/android/app/Fragment.html


85
好的,但我需要一种立即获取当前显示的 Fragment 实例的方法,而不是迭代检查所有片段,然后决定哪个片段现在显示在屏幕上。我认为你的答案需要我的代码迭代地检查每一个片段,并找出可见的那一个... - Leem.fin
39
是的,但是可能会同时出现多个片段可见。因此,并不存在“唯一活动的片段”这样的东西。 - ramdroid
6
即使有多个片段被显示,我仍然需要一种获取它们的方法...而且需要比迭代检查每个片段更好的方法。我关注于“获取它们”的方法,例如getDisplayedFragment(),但不确定在Android中是否有这样的方法。 - Leem.fin
3
我不知道为什么这对你来说是个问题... 通常一个activity中只有很少的fragment,检查它们是否可见不应该成为问题。 - ramdroid
25
为什么每个人都在给这个答案点赞?它确实是一段好的代码,但并没有回答如何通过标签获取特定的片段的问题。但是问题提出者想要当前显示的片段,这意味着他不知道显示的是哪个片段。是的,可能会有多个片段,但根据问题描述只能推断出界面上显示了一个片段...抱歉我必须给它点踩,因为它没有回答问题的背景。 - angryITguy
显示剩余7条评论

453

我知道这是一篇旧文章,但之前也遇到过类似的问题。找到了一个解决方案,就是在 onBackStackChanged() 监听函数中进行操作。

  @Override
    public void onBackPressed() {
        super.onBackPressed();

         Fragment f = getActivity().getFragmentManager().findFragmentById(R.id.fragment_container);
      if(f instanceof CustomFragmentClass) 
        // do something with f
        ((CustomFragmentClass) f).doSomething();

    }

这对我很有用,因为我不想遍历每一个片段来找到一个可见的。


14
对于在导航抽屉使用onBackPressed()后屏幕旋转的情况,这正是我所需要的。感谢你回来分享这个方法。 - ShortFuse
1
findFragmentById 不是在内部迭代所有片段吗? :) - Andrew Senner
49
如果.getFragmentManager报告不兼容类型,就像我的情况一样,那是因为我正在使用android.support.app.v4.Fragment,在这种情况下,正确的方法是使用getSupportFragmentManager - Răzvan Barbu
3
如果在 fragmentManager.beginTransaction() 中添加 frag 时提供了标签 fragmentTag,那么您可以使用 findFragmentByTag() 替换 findFragmentById() - DeltaCap019
@AndrewSenner,他的意思是他不想明确地这样做 :) - Farid

184

这是我觉得在低片段场景下非常方便的解决方案。

public Fragment getVisibleFragment(){
    FragmentManager fragmentManager = MainActivity.this.getSupportFragmentManager();
    List<Fragment> fragments = fragmentManager.getFragments();
    if(fragments != null){
        for(Fragment fragment : fragments){
            if(fragment != null && fragment.isVisible())
                return fragment;
        }
    }
    return null;
}

16
在某些情况下,比如当你从后退栈中弹出时,fragment可能为空。因此最好使用 if (fragment != null && fragment.isVisible()) - cprcrack
10
方法不完整,因为可能不止一个片段可见。 - letroll
@ShivarajPatil,在ActionBar中,如果有多个片段可见,你找到解决方案了吗? - user1324887
@user1324887 没有,但你可以将片段添加到列表中,例如可见片段列表,而不是在循环内返回单个片段,然后返回该列表并逐个遍历它们。这不是完美的解决方案,我也没有测试过。 - Shivaraj Patil
6
从技术角度来看,这应该被标记为根据问题回答。提问者想要当前显示的片段,但不知道是哪个片段。其他答案通过标签获取片段。 - angryITguy
显示剩余2条评论

83
每次显示片段时,您必须将其标记放入后堆栈中:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_ENTER_MASK);       
ft.add(R.id.primaryLayout, fragment, tag);
ft.addToBackStack(tag);
ft.commit();        

然后,当您需要获取当前片段时,可以使用此方法:

public BaseFragment getActiveFragment() {
    if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
        return null;
    }
    String tag = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1).getName();
    return (BaseFragment) getSupportFragmentManager().findFragmentByTag(tag);
}

5
我相当确定这种技术对于与ViewPager一起使用的Fragment不起作用,因为它们不是通过标签添加的。只是随口一提。 - loeschg
4
仅当您调用addToBackStack(tag)时,此方法似乎有效,但如果您不想将片段添加到后退栈中怎么办? - cprcrack
1
只有当您的标签恰好与后退堆栈名称相同时,此方法才有效,这似乎很不寻常。 - user153275
如果在同一交易中添加了5个片段会发生什么? - Kevin Lam
也许可以这样说,如果您对某个不需要名称的片段调用addToBackStack(null),则getName()方法可能返回null并导致NPE。@ dpk,文档中说:获取创建此条目时传递给FragmentTransaction.addToBackStack(String)的名称。此外,如果EntryCount为0且您尝试接收顶部条目,请添加空检查。 - JacksOnF1re

41

Kotlin方式;

val currentFragment = supportFragmentManager.fragments.last()

仅适用于API 26及以上版本 - ibit
1
@ibit,您说的“仅适用于API 26及以上版本”的意思是什么?我在模拟器API 19上尝试了一下,运行良好。 - HendraWD
@HendraWD 你说得对!我指的是框架中的 getFragments() 函数,它是 API 26 及以上版本才有的。 - ibit
2
请注意,如果您之前没有任何片段堆栈,它可能会导致空异常! - Jetwiz
这个不行?java.lang.ClassCastException: androidx.navigation.fragment.NavHostFragment 无法转换为 Fragment。 - coolcool1994
8
正确的写法是:supportFragmentManager.fragments.last()?.getChildFragmentManager()?.getFragments()?.get(0)。该代码用于在安卓应用程序中获取最后一个Fragment的第一个子Fragment。 - coolcool1994

23

我用以下代码来查找当前显示的片段。这很简单,并且目前对我有效。它在持有片段的活动中运行。

    FragmentManager fragManager = this.getSupportFragmentManager();
    int count = this.getSupportFragmentManager().getBackStackEntryCount();
    Fragment frag = fragManager.getFragments().get(count>0?count-1:count);

4
请确认 count > 0,以确保 get(count-1) 不会抛出 IndexOutofBoundsException 异常。 - kehers
@tainy 这不会提供被替换但未添加到返回栈的片段,对吗? 如果是的话,那我能用标签参数获取它吗? - cafebabe1991
调用getFragments方法后,如果您在其上调用popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)并添加更多片段,则不会按添加顺序返回片段。 - LEHO
如果一个活动中有多个可见的Fragment,那么你的方法是错误的。例如:带有Fragment的ViewPager。据我所知,getBackStackEntryCount()返回的是事务的数量,而不是片段的数量。 - HungNM2
你应该检查 fragManager.getFragments().size() 而不是 getBackStackEntryCount(),因为它们不一定相等(这是我的情况)。否则你可能会遇到 IndexOutOfBoundsException 异常。 - RustamG

15
如果你正在使用AndroidX Navigation,要返回当前的Fragment目标,请使用以下代码:
val currentFragment = findNavController(R.id.your_navhost)?.currentDestination

要返回当前片段的名称,您可以使用以下方法代替:
val currentFragment = supportFragmentManager.findFragmentById(R.id. your_navhost)?.childFragmentManager?.primaryNavigationFragment

关于这个导航组件的更多信息,请参考: https://developer.android.com/guide/navigation/navigation-getting-started

这个无法编译。 - IgorGanapolsky
3
只有在正确设置了AndroidX导航并以某种方式将您的navHost命名为“your_navhost”时,它才会编译通过。 - Cory Roy
1
这将返回目标,而不是片段。如果您需要片段,可以使用: supportFragmentManager.findFragmentById(R.id.your_nav_graph_container).childFragmentManager.primaryNavigationFragment - Torima

13

我的方法基于try/catch,如下所示:

MyFragment viewer = null;
    if(getFragmentManager().findFragmentByTag(MY_TAG_FRAGMENT) instanceOf MyFragment){
    viewer = (MyFragment) getFragmentManager().findFragmentByTag(MY_TAG_FRAGMENT);
}

但是可能有更好的方法...


2
我的问题是如何获取当前显示的片段,这意味着可能有很多片段,我只想获取当前显示的实例,为什么要使用(MyFragment)??我的意思是屏幕上可能显示任何片段...而我想获取当前显示的片段。 - Leem.fin
没问题,使用这段代码:fragmentTransaction.replace(R.id.FL_MyFragment, MyFragment, MY_TAG_FRAGMENT); 可能会顺利进行。 - Thordax
好的,所以如果我为所有片段使用一个标签,一旦我使用findFragmentByTag(tag)找到了它,它就会返回当前显示的片段,是吗? - Leem.fin
小心,如果您尝试按照我在第一篇帖子中所说的方式获取片段,则会出现ClassCastException! - Thordax
2
嗯,我真的不明白你的意思,我的意思是我需要一种立即获取当前显示的Fragment实例的方法,而不是迭代地检查所有片段,然后决定哪个片段现在显示在屏幕上。 - Leem.fin
显示剩余2条评论

13

响应式编程的方式:

Observable.from(getSupportFragmentManager().getFragments())
    .filter(fragment -> fragment.isVisible())
    .subscribe(fragment1 -> {
        // Do something with it
    }, throwable1 -> {
        // 
    });

7
它如何是响应式的?虽然你使用了一个 Observable,但它只会发出一次信号。 - Tim
6
这只是一种过度设计。一个简单的增强型for循环可以达到同样的效果,甚至可能更快。 - Peterstev Uremgba
仅仅为使用流API而使用响应式流并不明智,事实上就像他们所说的那样,这是一种过度使用。如果您想将列表视为流并应用过滤器API,则可以使用Java8的流API。 - AndroidLover

11

嗯,这个问题得到了很多浏览和关注,但是还没有包含我最简单的解决方案 - 使用getFragments()。

            List fragments = getSupportFragmentManager().getFragments();
            mCurrentFragment = fragments.get(fragments.size() - 1);

1
不幸的是,这并不总是正确的, getSupportFragmentManager().getFragments() 会返回一个列表,但其中可能包含空值, 如果最新的片段刚从后退栈中弹出。 例如:如果您在onBackStackChanged()方法内使用您的方式进行检查,并在某个地方调用popBackStack(), 那么mCurrentFragment将为null - Stumi
@Stumi 感谢您提供这些信息。是的,听起来像是 Android 中许多奇怪的生命周期问题之一。 - Nativ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接