如何处理片段和视图页面之间的后退栈?

5

我正在使用一个包含两个页面的ViewPager。ViewPager的第一个页面是MainFragment。MainFragment有一个作为bottomNavigationView的ViewPager。每个页面都有一个FragmentContainerView,默认情况下它们分别包含HomeFragment、CommunityFragment和NotificationFragment。

这是该项目的源代码,这是APK。因此,您可以轻松地测试和改进它。

现在,如果我在HomeFragment中,点击一个个人资料按钮,它就会转到ProfileFragment,然后再到设置等。点击返回按钮时,它会逐一完美地返回。但是其他FragmentContainerView不会发生同样的情况。即使它们直接返回到父片段。总体而言,我无法处理不同ViewPager和片段之间的回退堆栈。

为了避免FragmentContainers的混淆,我像这样进行了转换

val containerId = (view?.parent as ViewGroup).id
activity?.supportFragmentManager?.beginTransaction()?.add(containerId, profileFragment)?.addToBackStack(null)?.commit()

现在,MainActivity中BackPressed()的处理方式如下:

if (view_pager_main_activity?.currentItem == 0)
{
    if (view_pager_main_fragment?.currentItem == 0)
    {
        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view_home)
        val appbarHome = findViewById<AppBarLayout>(R.id.appbar_home)
        val layoutManager = recyclerView?.layoutManager as LinearLayoutManager
        when {
            layoutManager.findFirstCompletelyVisibleItemPosition() == 0 -> {
                super.onBackPressed()
            }
            supportFragmentManager.backStackEntryCount != 0 -> {
                supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
            }
            else -> {
                layoutManager.scrollToPositionWithOffset(0, 0)
                appbarHome.setExpanded(true)
                //recyclerView.smoothScrollToPosition(0)
            }
        }
    }
    else
    {
        // Otherwise, select the previous step.
        view_pager_main_fragment?.setCurrentItem(view_pager_main_fragment.currentItem - 1, false)
    }
}
else
{
    // Otherwise, select the previous step.
    view_pager_main_activity?.currentItem = view_pager_main_activity.currentItem - 1
}

看看这个 https://stackoverflow.com/q/55780523/6071729,我提供了一个不错的解决方案,虽然它可能并不完全适用于你的具体问题。你可以试一下。 - A Farmanbar
我看了你在那里的回答,但它看起来非常不同,这里我们也有2个viewpager。我观察到当viewpager的位置在第一页时,它运行得很完美。这意味着问题出现在viewpager的backstack中。因此,我们必须为每个片段和vp位置创建一个精心计划的后退事件,以解决这个问题。 - Stackoverflower
你的项目中使用NavigationUI吗?因为它有可能找到一个“NavController”(管理单个“NavHostFragment”内的帧栈),然后你可以使用“navigateTo()”和“navigateUp()”方法进行导航或返回上一页。 - emandt
我从未使用过那个,也不知道如何操作。 - Stackoverflower
1个回答

1
我查看了你的代码,问题在于使用方式不正确。
supportFragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE)

如果您阅读标志 POP_BACK_STACK_INCLUSIVE 的描述,它会说:
如果设置,并且已提供了后退堆栈条目的名称或 ID,则将消耗所有匹配的条目,直到找到一个不匹配的条目或达到堆栈底部。否则,将删除该条目之前的所有条目,但不包括该条目。
所以它可以一次性删除多个条目。
它在主屏幕上工作的原因是它从未到达那种情况。它总是进入第一种情况:
layoutManager.findFirstCompletelyVisibleItemPosition() == 0 -> {
                        super.onBackPressed()
                    }  

对于你的情况,你需要调用super.onBackPressed()来一次只弹出一个片段。因此,你的最终代码如下:

在你的MainActivity的onBackPressed()

override fun onBackPressed() {
        if (view_pager_main_activity?.currentItem == 0) {
            if (view_pager_main_fragment?.currentItem == 0) {
                val recyclerView = findViewById<RecyclerView>(R.id.listRecyclerView)
                val appbarHome = findViewById<AppBarLayout>(R.id.appbar_home)
                val layoutManager = recyclerView?.layoutManager as LinearLayoutManager
                when {
                    layoutManager.findFirstCompletelyVisibleItemPosition() == 0 -> {
                        super.onBackPressed()
                    }
                    //This is never reached. It can be removed
                    supportFragmentManager.backStackEntryCount != 0 -> {
                        supportFragmentManager.popBackStack(
                            null,
                            FragmentManager.POP_BACK_STACK_INCLUSIVE
                        )
                    }
                    else -> {
                        layoutManager.scrollToPositionWithOffset(0, 0)
                        appbarHome.setExpanded(true)
                        //recyclerView.smoothScrollToPosition(0)
                    }
                }
            } else if (supportFragmentManager.backStackEntryCount != 0) {
                super.onBackPressed()
            } else {
                // Otherwise, select the previous step.
                view_pager_main_fragment?.setCurrentItem(
                    view_pager_main_fragment.currentItem - 1,
                    false
                )
            }
        } else if (supportFragmentManager.backStackEntryCount != 0) {
            super.onBackPressed()
        } else {
            // Otherwise, select the previous step.
            view_pager_main_activity?.currentItem = view_pager_main_activity.currentItem - 1
        }
    } 

编辑 1:来自评论

我想在底部导航选项更改时清除返回栈。

如果您想要在更改底部导航选择时清除选择,则必须在您的 MainFragment 中设置一个监听器并清除返回栈,如下所示:

view?.bottomNavView?.setOnNavigationItemSelectedListener { item ->
            //check if item is being re-selected then just return and don't do anything
            if (bottomNavView.selectedItemId == item.itemId){
                return@setOnNavigationItemSelectedListener false
            }
            //if the bottom nav item selection changes then clear the back stack of previous tab
            parentFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)

            when (item.itemId) {
                R.id.homeFragment -> viewPager2.setCurrentItem(0, false)
                R.id.searchFragment -> viewPager2.setCurrentItem(1, false)
                R.id.notificationsFragment -> viewPager2.setCurrentItem(2, false)
            }
            true
        }  

其他部分可以保持不变。尝试一下并让我知道。


太棒了!非常感谢您提供这个惊人的答案。我从未想过那一行代码。现在它运行得非常好,但仍然存在一个问题:假设“我现在打开应用程序,导航到通知片段,然后我打开个人资料片段或其他片段。然后我导航到主页片段,现在如果我点击返回按钮,它会删除后台中的片段,当所有片段都被删除时,它最终被销毁。请帮忙! - Stackoverflower
@Stackoverflower,请查看我的编辑。如果可以的话请告诉我。 - Mayur Gajra
实际上,你需要将这个代码粘贴到MainFragmentview?.bottomNavView?.setOnNavigationItemSelectedListener中,而不是主活动中。你试过了吗? - Mayur Gajra
现在还有一个问题,如果我在ViewPager中使用viewPager.currentItem - 1来进行后退堆栈,则也会转到那些尚未创建的片段,例如“我打开应用程序,导航到通知片段,然后按返回键,它通过通知>搜索>主页。这样,搜索片段就会被创建,这对于应用程序来说不是很好。 - Stackoverflower
让我们在聊天中继续这个讨论 - Mayur Gajra
显示剩余4条评论

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