如何在NavHostFragment中获取当前片段?

60

我尝试在新的导航组件中找到相关方法,但是没有发现相关内容。

目前我已经获取到了当前的目标位置:

mainHostFragment.findNavController().currentDestination

但我找不到任何关于显示碎片的参考。


很抱歉,您必须手动处理它。 - guisantogui
你想做什么? - ianhanniballake
@ianhanniballake 我的宿主 Activity 需要引用来与其片段进行通信。 - BenjaminBihr
10个回答

62

显示的片段引用(AndroidX):

java

public Fragment getForegroundFragment(){
    Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
    return navHostFragment == null ? null : navHostFragment.getChildFragmentManager().getFragments().get(0);
}

kotlin

val navHostFragment: Fragment? =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
navHostFragment?.childFragmentManager?.fragments?.get(0)

这里的nav_host_fragment是你的activity_main.xmlfragment标签的ID,带有android:name="androidx.navigation.fragment.NavHostFragment"


1
在我的情况下,?.childFragmentManager?.fragments?.last()会给我当前的Fragment。 - Milad Faridnia
哇,这是一个不太正规的答案。childFragmentManager.findFragmentById 存在。 - John Glen
1
如果他们想要检查当前的片段,childFragmentManager.findFragmentById会如何帮助呢? - Jakoss

50

导航不提供任何机制来获取当前目标的实现(即Fragment本身)。

根据为活动创建事件回调,您应该通过以下方式之一与Fragment通信:

  • 在其onAttach方法中注册回调,将Activity转换为您提供的接口的实例
  • 使用共享的ViewModel,让您的Activity和Fragment进行通信。

8
所有链接都展示了如何在片段之间进行通信。但是,如果导航不提供获取当前目标实现的机制,我们如何从活动中调用片段方法? - Sever
8
请参考这个问题链接(https://issuetracker.google.com/issues/119800853),使用`getPrimaryNavigationFragment()`函数获取当前的Fragment。 - ianhanniballake
5
非常感谢,它起作用了。val navHostFragment = supportFragmentManager.primaryNavigationFragment as NavHostFragmentval yourFragment = currentFragmentClassName.childFragmentManager.primaryNavigationFragment as YourFragment - Sever
@ianhanniballake:抱歉,我不是很明白现在是否可以通过getPrimaryNavigationFragment()获取前台片段,或者他们仅会在未来提供这样的机会!? - isabsent
1
嗯,要在两个片段之间共享ViewModel,其中一个必须是其所有者。另一个必须找到该所有者以获取该ViewModel。所以...永远不要使用Navigation,它很愚蠢。 - Miha_x64
不理解为什么导航不维护片段列表,例如当MainActivity想要调用子片段中的方法时需要。因此,您需要使用“老旧的方式”,那么引入导航系统的意义何在?我不明白。 - carl

30

你可以像这样做:

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        val navHostFragment = supportFragmentManager.fragments.first() as? NavHostFragment
        if(navHostFragment != null) {
            val childFragments = navHostFragment.childFragmentManager.fragments
            childFragments.forEach { fragment ->
                fragment.onActivityResult(requestCode, resultCode, data)
            }
        }
    }

但对于更高级的通信,在Fragment.onAttach()中注册具有回调方法的听众Fragment -> Activity是单向通信)以及SharedViewModel双向),重要的是拥有ViewModelProviders和Lifecycle owner,其作用域为getActivity()


你可以移除 as? NavHostFragment 并将 if 语句改为 if (navHostFragment is NavHostFragment) - Shalbert

14

基于其他回答

Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);

((Your Fragment Class) fragment).(public method inside the fragment)

对我很有效


9

使用Michal的答案,我编写了以下扩展函数进行测试:

@Suppress("UNCHECKED_CAST")
fun <F : Fragment> AppCompatActivity.getFragment(fragmentClass: Class<F>): F? {
    val navHostFragment = this.supportFragmentManager.fragments.first() as NavHostFragment

    navHostFragment.childFragmentManager.fragments.forEach {
        if (fragmentClass.isAssignableFrom(it.javaClass)) {
            return it as F
        }
    }

    return null
}

使用方式如下:

val myFragment = activity.getFragment(MyFragment::class.java)

2
你可以将其压缩为 inline fun <reified T : Fragment> FragmentManager.findFragmentByClass(): T? = fragments.firstOrNull { it is T } as T? - drdaanger

5

您可以这样做:

@Override
public void onBackPressed() {
    super.onBackPressed();
   
    NavController navController = Navigation.findNavController(this, R.id.fragment);
    int id=navController.getCurrentDestination().getId();
    if(id == R.id.startGameFragment ){
        selectedPosition(0);
    } else if(id == R.id.gameFragment ){
        selectedPosition(1);
    } else if(id == R.id.endGameFragment){
        selectedPosition(2);
    }
}

private void selectedPosition(int pos){
    for (int i = 0; i >=nav_view.getMenu().size(); i++) {
        nav_view.getMenu().getItem(pos).setChecked(false);

    }
    nav_view.getMenu().getItem(pos).setChecked(true);
}

3

我使用 AndroidX 发布了完整的答案。 注意:在我的情况下,我需要检索其中一个子片段(可能不是第一个)。

MainActivity中,您应该有类似以下内容:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                 R.id.mytest, R.id.nav_help)
                .setDrawerLayout(drawer)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(navigationView, navController);
...

接下来,您需要创建一个检索良好片段的方法。

 private MyTestFragment getMyTestFragment(){
        MyTestFragment resultFragment = null;
        Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        if(navHostFragment != null && navHostFragment.getChildFragmentManager() != null) {
            List<Fragment> fragmentList = navHostFragment.getChildFragmentManager().getFragments();
            for (Fragment fragment : fragmentList) {
                if (fragment instanceof MyTest) {
                    resultFragment = (MyTest) fragment;
                    break;
                }
            }
        }
        return resultFragment;
    }

终于你懂了。


2

从具有NavHostFragment的Activity中,可以使用以下代码片段来检索Active Fragment的实例。

kotlin。

val currentFragment = mNavHostFragment?.childFragmentManager?.primaryNavigationFragment()

java

Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);

对于 Kotlin 的小修正:最后应该是 primaryNavigationFragment,即没有函数调用。 - undefined

0

我认为可能会出现异常,因此您可以使用接口回调,这是一种可靠的方式,从一端到另一端进行通信,只需传递引用检查下面的代码。

Fragment A(){ interface ClickPostItemListener { fun onClicked(position: Int) }

val itemListener = object : ClickPostItemListener{
    override fun onClicked(position: Int) {
        postBackNForth(position)
    }

}  } Fragment B(clickItem:ClickPostItemListener ){ clickItem() }

0

处理这个问题的最佳和正确方法是使用接口。视图模型应该用于在活动和片段之间传递数据。以下是我解决此问题的方法:

创建接口

interface NavigationInterface {
    fun referenceCourseListFragment(fragment: CourseListFragment)
    fun referenceCouseDetailFragment(fragment: CourseDetailInfoFragment)
}

请确保该活动实现了接口

class NotesActivity : AppCompatActivity(), NavigationInterface {}

确保为每个需要引用的片段创建lateinit var,然后

private lateinit var courseListFragment: CourseListFragment
private lateinit var courseDetailInfoFragment: CourseDetailInfoFragment

在每个片段的onCreateView方法中,确保创建接口监听器并将带有接口的片段传回。
private lateinit var navigationInterface: NavigationInterface

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    //establish interface communication
    activity?.let {
        instantiateNavigationInterface(it)
    }
    // Inflate the layout for this fragment
    fragmentView = inflater.inflate(R.layout.fragment_course_list, container, 
    false)
    navigationInterface.referenceCourseListFragment(this)
    return fragmentView
}

现在回到活动中,您应该能够通过接口回调实例化片段对象以进行引用

override fun referenceCourseListFragment(fragment: CourseListFragment) {
    courseListFragment = fragment
}

override fun referenceCouseDetailFragment(fragment: CourseDetailInfoFragment) 
{
    courseDetailInfoFragment = fragment
}

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