We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在学习Navigation的时候就已经注意到多返回栈技术了,首先多返回栈技术是非常非常新,最近的更新的feature。但是一时半会没法消化理解,加上一时没真正应用上,所以暂时搁置了。想不到经过一次导航的bug,我开始重新审视“多返回栈技术”。
到底是什么原因那
似乎有点解释无力 因为,我在一级菜单下的页面是会“缓存”下来的,而在一级页面下新开的页面又是会“压栈”操作。 用常规的堆栈模型显然是自相矛盾的!
是的,你想象的没错,这里已经使用的“多堆栈技术”
如果您正在使用 NavigationUI,它是用于连接您的 NavController 到 Material 视图组件的一系列专用助手,您会发现对于菜单项、BottomNavigationView (现在叫 NavigationRailView) 和 NavigationView,多返回栈是默认启用的。这就意味着结合 navigation-fragment 和 navigation-ui 使用就可以。
先上图来理解下多堆栈下的情况
实际的堆栈仍然只有一个当前堆栈,有点像UI层的主线程
saveBackStack和restoreBackStack的api可以实现堆栈的瞬间存储和恢复,产生逻辑上的多堆栈
其实每个一级菜单形成自己的堆栈,可以理解为“堆栈自治”
以后我们的堆栈形成了一个二维平面展开的视图,所以说多堆栈技术实际就是一维升二维的技术,也算是谷歌的一个很重大的一个功能提升
这个bug产生的原因我把两个一级页面的跳转当成了“普通”跳转。 目前点击下面导航按钮是正常的。所以“保守”的思路是,把跳转的行为做成菜单点击“一模一样”
底部导航栏提到了
NavigationUI 也可以处理底部导航。当用户选择某个菜单项时,NavController 会调用 onNavDestinationSelected() 并自动更新底部导航栏中的所选项目。
所以onNavDestinationSelected这个api是关键。 问题: 我在子页面中拿不到菜单项怎么办那,只能通过fragment 通信转一道手了
this.viewModel.getShowSpace().observe(this.getViewLifecycleOwner(), show -> { if (show) { Bundle result = new Bundle(); result.putInt("menu", R.id.spaceFragment ); getParentFragmentManager().setFragmentResult("requestMenuClick", result); } });
所以在子页面跳转部分的实现代码,把自己要跳转的id通过FragmentResult传出去
NavHostFragment navHostFragment = (NavHostFragment) getChildFragmentManager().findFragmentById(R.id.fragmentContainerView); assert navHostFragment != null; NavController navController = navHostFragment.getNavController(); navHostFragment.getChildFragmentManager().setFragmentResultListener("requestMenuClick", this, (requestKey, bundle) -> { Logger.i( "------requestMenuClick-----" ); MenuItem item = bottomNav.getMenu().findItem(bundle.getInt("menu")); Logger.i( "get menu item:%s" , item.getTitle() ); NavigationUI.onNavDestinationSelected( item , navController ); }); bottomNav = view.findViewById(R.id.bottom_nav); NavigationUI.setupWithNavController(bottomNav, navController); navController.addOnDestinationChangedListener((controller, destination, arguments) -> { int id = destination.getId(); if (mainResourceIds.contains(id)) { bottomNav.setVisibility(View.VISIBLE); } else { bottomNav.setVisibility(View.GONE); } });
这里导航的宿主页面,持有了BottomNavigationView和NavHostFragment 只要收到导航中的Fragment的Result,通过findItem找到对应的菜单项,“模拟”点击了一级菜单 结果很完美 这是保底方案实现了,还需要有更好的追求
首先看看1号方案实际做了点什么
@JvmStatic public fun onNavDestinationSelected(item: MenuItem, navController: NavController): Boolean { val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true) if ( navController.currentDestination!!.parent!!.findNode(item.itemId) is ActivityNavigator.Destination ) { builder.setEnterAnim(R.anim.nav_default_enter_anim) .setExitAnim(R.anim.nav_default_exit_anim) .setPopEnterAnim(R.anim.nav_default_pop_enter_anim) .setPopExitAnim(R.anim.nav_default_pop_exit_anim) } else { builder.setEnterAnim(R.animator.nav_default_enter_anim) .setExitAnim(R.animator.nav_default_exit_anim) .setPopEnterAnim(R.animator.nav_default_pop_enter_anim) .setPopExitAnim(R.animator.nav_default_pop_exit_anim) } if (item.order and Menu.CATEGORY_SECONDARY == 0) { builder.setPopUpTo( navController.graph.findStartDestination().id, inclusive = false, saveState = true ) } val options = builder.build() return try { // TODO provide proper API instead of using Exceptions as Control-Flow. navController.navigate(item.itemId, null, options) // Return true only if the destination we've navigated to matches the MenuItem navController.currentDestination?.matchDestination(item.itemId) == true } catch (e: IllegalArgumentException) { false } }
setLaunchSingleTop(true) 防止请求实例重复,保证只有一个,这个比较好理解
setRestoreState(true)和saveState = true 这个和多堆栈相关,支持调用saveBackStack和restoreBackStack魔法
setPopUpTo 退栈操作,把堆栈清理干净,只留下首页
看到这里的魔法,那么我们普通导航也是可以做到的 新增popUpTo,popUpToSaveState,launchSingleTop,restoreState4个属性 把里面的值设成和源码“一模一样”
运行一下,完美
初始状态 从页面1点击普通导航到页面2 这样界面2就不是一个独立的堆栈,而是附属于界面1的堆栈了所以会出现两个诡异的现象
点击下面界面1菜单,显示界面2的内容 因为点击界面1的菜单是恢复了界面1的堆栈,所以显示当前最外层的界面2,从多堆栈逻辑来说是“没毛病”
界面2出现了两个实例,都是分别初始化 界面2就出现了2个实例,一个是下方导航创建的独立堆栈,一个是界面1创建的界面2。很魔幻
Android 多返回栈技术详解 谷歌开发者公众号的推文,也算是准官方吧,里面细节一定要仔细看推敲。
支持多个返回堆栈 官挡参考,只说了api用法,感觉解释的不清不楚的,结合看
导航——支持多个返回堆栈
感觉下来Navigation已经把堆栈的操作处理得越来越好了,其他这个组件也帮我们处理好fragmentmanager的管理,不需要我们对这一层亲自动手。 不过我这里还有一点隐忧
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
在学习Navigation的时候就已经注意到多返回栈技术了,首先多返回栈技术是非常非常新,最近的更新的feature。但是一时半会没法消化理解,加上一时没真正应用上,所以暂时搁置了。想不到经过一次导航的bug,我开始重新审视“多返回栈技术”。
诡异的bug
到底是什么原因那
我想象中的fragment 堆栈
似乎有点解释无力
因为,我在一级菜单下的页面是会“缓存”下来的,而在一级页面下新开的页面又是会“压栈”操作。
用常规的堆栈模型显然是自相矛盾的!
实际中的 堆栈
是的,你想象的没错,这里已经使用的“多堆栈技术”
先上图来理解下多堆栈下的情况
实际的堆栈仍然只有一个当前堆栈,有点像UI层的主线程
saveBackStack和restoreBackStack的api可以实现堆栈的瞬间存储和恢复,产生逻辑上的多堆栈
其实每个一级菜单形成自己的堆栈,可以理解为“堆栈自治”
以后我们的堆栈形成了一个二维平面展开的视图,所以说多堆栈技术实际就是一维升二维的技术,也算是谷歌的一个很重大的一个功能提升
bug修复过程
1号方案
这个bug产生的原因我把两个一级页面的跳转当成了“普通”跳转。
目前点击下面导航按钮是正常的。所以“保守”的思路是,把跳转的行为做成菜单点击“一模一样”
底部导航栏提到了
所以onNavDestinationSelected这个api是关键。
问题:
我在子页面中拿不到菜单项怎么办那,只能通过fragment 通信转一道手了
所以在子页面跳转部分的实现代码,把自己要跳转的id通过FragmentResult传出去
这里导航的宿主页面,持有了BottomNavigationView和NavHostFragment
只要收到导航中的Fragment的Result,通过findItem找到对应的菜单项,“模拟”点击了一级菜单
结果很完美
这是保底方案实现了,还需要有更好的追求
2号方案
首先看看1号方案实际做了点什么
setLaunchSingleTop(true)
防止请求实例重复,保证只有一个,这个比较好理解
setRestoreState(true)和saveState = true
这个和多堆栈相关,支持调用saveBackStack和restoreBackStack魔法
setPopUpTo
退栈操作,把堆栈清理干净,只留下首页
看到这里的魔法,那么我们普通导航也是可以做到的
新增popUpTo,popUpToSaveState,launchSingleTop,restoreState4个属性
把里面的值设成和源码“一模一样”
运行一下,完美
复盘下bug
初始状态
从页面1点击普通导航到页面2
这样界面2就不是一个独立的堆栈,而是附属于界面1的堆栈了所以会出现两个诡异的现象
点击下面界面1菜单,显示界面2的内容
因为点击界面1的菜单是恢复了界面1的堆栈,所以显示当前最外层的界面2,从多堆栈逻辑来说是“没毛病”
界面2出现了两个实例,都是分别初始化
界面2就出现了2个实例,一个是下方导航创建的独立堆栈,一个是界面1创建的界面2。很魔幻
重要资料参考
Android 多返回栈技术详解
谷歌开发者公众号的推文,也算是准官方吧,里面细节一定要仔细看推敲。
支持多个返回堆栈
官挡参考,只说了api用法,感觉解释的不清不楚的,结合看
导航——支持多个返回堆栈
总结下
感觉下来Navigation已经把堆栈的操作处理得越来越好了,其他这个组件也帮我们处理好fragmentmanager的管理,不需要我们对这一层亲自动手。
不过我这里还有一点隐忧
The text was updated successfully, but these errors were encountered: