如何使用新的导航架构组件实现ViewPager?

Mak*_*nia 23 android android-architecture-components

我有一个BottomNavigationView和一个应用程序ViewPager.如何使用新的"导航架构组件"实现它?

什么是最佳做法?

非常感谢

Mar*_*rat 15

BottomNavigationView使用Navigation Arch Component的默认实现对我来说没有用.单击选项卡时,它会根据导航图从头开始.

我需要在屏幕底部有5个选项卡,并为每个选项卡都有一个单独的backstack.这意味着当在标签之间切换时,您将始终返回与离开之前完全相同的状态(如在Instagram中).

我的方法如下:

  1. 放入ViewPagerBottomNavigationView放入activity_main.xml
  2. 设置OnNavigationItemSelectedListenerBottomNavigationViewMainActivity.kt
  3. 为每个选项卡创建单独的Container片段(它们将是每个选项卡的起点)
  4. 包含NavHostFragmentContainer片段的xml内部.
  5. 在每个Container片段中实现Navigation Arch Component的必要代码.
  6. 为每个选项卡创建一个图表

注意:每个图表都可以相互交互.

这里重点是我们将工具栏放在活动中但不在 Container片段中.然后我们调用setupWithNavController()工具栏本身而不设置它supportActionBar.这样工具栏标题将自动更新,并自动管理后退/上移按钮.

结果:

  • ViewPager存储每个选项卡的状态.
  • 不用担心片段交易.
  • SafeArgs并按DeepLinking预期工作.
  • 我们可以完全控制BottomNavigationManagerViewPager(即我们可以实现OnNavigationItemReselectedListener并决定在弹出backstack之前将当前选项卡中的列表滚动到顶部).

码:

activity_main.xml中

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/main_view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/main_bottom_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation" />

</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

MainActivity.kt

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private lateinit var viewPagerAdapter: ViewPagerAdapter

    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.navigation_tab_1 -> {
                main_view_pager.currentItem = 0
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_tab_2 -> {
                main_view_pager.currentItem = 1
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
        main_view_pager.adapter = viewPagerAdapter

        main_bottom_navigation_view.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
    }
}
Run Code Online (Sandbox Code Playgroud)

ViewPagerAdapter.kt

class ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {

    override fun getItem(position: Int): Fragment {
        return when (position) {
            0 -> Tab1ContainerFragment()
            else -> Tab2ContainerFragment()
        }
    }

    override fun getCount(): Int {
        return 2
    }
}
Run Code Online (Sandbox Code Playgroud)

fragment_tab_1_container.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Tab1ContainerFragment">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tab_1_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark" />

    <fragment
        android:id="@+id/tab_1_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation_graph_tab_1" />

</RelativeLayout>
Run Code Online (Sandbox Code Playgroud)

Tab1ContainerFragment.kt

class Tab1ContainerFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_tab_1_container, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val toolbar = view.findViewById<Toolbar>(R.id.tab_1_toolbar)

        val navHostFragment = childFragmentManager.findFragmentById(R.id.tab_1_nav_host_fragment) as NavHostFragment? ?: return

        val navController = navHostFragment.navController

        val appBarConfig = AppBarConfiguration(navController.graph)

        toolbar.setupWithNavController(navController, appBarConfig)
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以根据需要创建任意数量的导航图:

导航图

但是我们需要为每个标签设置一个单独的图表:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_graph_tab_1"
    app:startDestination="@id/tab1StartFragment">

    <fragment
        android:id="@+id/tab1StartFragment"
        android:name="com.marat.android.bottomnavigationtutorial.Tab1StartFragment"
        android:label="fragment_tab_1_start"
        tools:layout="@layout/fragment_tab_1_start">
        <action
            android:id="@+id/action_tab_1_to_content"
            app:destination="@id/navigation_graph_content" />
    </fragment>

    <include app:graph="@navigation/navigation_graph_content" />
</navigation>
Run Code Online (Sandbox Code Playgroud)

这里,起始目标片段是您希望在选项卡中显示为第一个屏幕的任何片段.


Thr*_*ian 8

我创建了一个在 Activity 上具有工具栏的示例,您还可以创建具有自己的工具栏的 ViewPager 片段。它OnBackPressedCallback用于后退导航,ViewModel用于设置当前片段NavControllerNavHostFragment嵌套childFragmentManager片段,并使用 viewLifeCycleOwner 尊重生命周期,并在暂停时禁用回调并启用 onResume。

\n

带有 ViewPager 和导航架构的 BottomNavigationView

\n

导航和布局架构

\n
     MainActivity(Appbar + Toolbar  + ViewPager2 + BottomNavigationView)\n       |\n       |- HomeNavHostFragment\n       |  |- HF1 -> HF2 -> HF3\n       |\n       |- DashboardNavHostFragment\n       |  |- DF1 -> DF2 -> DF3\n       |\n       |- NotificationHostFragment\n          |- NF1 -> NF2 -> NF3\n
Run Code Online (Sandbox Code Playgroud)\n

首先,为每个选项卡或片段创建一个导航图ViewPager2

\n

nav_graph_home.xml

\n
<?xml version="1.0" encoding="utf-8"?>\n<navigation xmlns:android="http://schemas.android.com/apk/res/android"\n    xmlns:app="http://schemas.android.com/apk/res-auto"\n    xmlns:tools="http://schemas.android.com/tools"\n    android:id="@+id/nav_graph_dashboard"\n    app:startDestination="@id/dashboardFragment1">\n\n\n    <fragment\n        android:id="@+id/dashboardFragment1"\n        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment1"\n        android:label="DashboardFragment1"\n        tools:layout="@layout/fragment_dashboard1">\n        <action\n            android:id="@+id/action_dashboardFragment1_to_dashboardFragment2"\n            app:destination="@id/dashboardFragment2" />\n    </fragment>\n\n    <fragment\n        android:id="@+id/dashboardFragment2"\n        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment2"\n        android:label="DashboardFragment2"\n        tools:layout="@layout/fragment_dashboard2">\n        <action\n            android:id="@+id/action_dashboardFragment2_to_dashboardFragment3"\n            app:destination="@id/dashboardFragment3" />\n    </fragment>\n    <fragment\n        android:id="@+id/dashboardFragment3"\n        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment3"\n        android:label="DashboardFragment3"\n        tools:layout="@layout/fragment_dashboard3" >\n        <action\n            android:id="@+id/action_dashboardFragment3_to_dashboardFragment1"\n            app:destination="@id/dashboardFragment1"\n            app:popUpTo="@id/dashboardFragment1"\n            app:popUpToInclusive="true" />\n    </fragment>\n\n</navigation>\n
Run Code Online (Sandbox Code Playgroud)\n

其他导航图与此相同

\n

BottomNavigationView 的菜单

\n

menu_bottom_nav.xml

\n
<?xml version="1.0" encoding="utf-8"?>\n<menu xmlns:android="http://schemas.android.com/apk/res/android">\n\n    <item\n            android:id="@+id/nav_graph_home"\n            android:icon="@drawable/ic_baseline_home_24"\n            android:title="Home"/>\n    <item\n            android:id="@+id/nav_graph_dashboard"\n            android:icon="@drawable/ic_baseline_dashboard_24"\n            android:title="Dashboard"/>\n    <item\n            android:id="@+id/nav_graph_notification"\n            android:icon="@drawable/ic_baseline_notifications_24"\n            android:title="Notification"/>\n</menu>\n
Run Code Online (Sandbox Code Playgroud)\n

ViewPager2 适配器

\n
class ActivityFragmentStateAdapter(fragmentActivity: FragmentActivity) :\n    FragmentStateAdapter(fragmentActivity) {\n    \n    override fun getItemCount(): Int = 3\n\n    override fun createFragment(position: Int): Fragment {\n\n        return when (position) {\n            0 -> HomeNavHostFragment()\n            1 -> DashBoardNavHostFragment()\n            else -> NotificationHostFragment()\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

主要活动布局

\n
<?xml version="1.0" encoding="utf-8"?>\n<layout xmlns:android="http://schemas.android.com/apk/res/android"\n    xmlns:app="http://schemas.android.com/apk/res-auto">\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width="match_parent"\n        android:layout_height="match_parent">\n\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:id="@+id/appbar"\n            android:layout_width="match_parent"\n            android:layout_height="wrap_content"\n            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">\n\n            <androidx.appcompat.widget.Toolbar\n                android:id="@+id/toolbar"\n                android:layout_width="match_parent"\n                android:layout_height="?attr/actionBarSize"\n                app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" />\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width="match_parent"\n            android:layout_height="match_parent"\n            app:layout_behavior="@string/appbar_scrolling_view_behavior">\n\n            <androidx.viewpager2.widget.ViewPager2\n                android:id="@+id/viewPager"\n                android:layout_width="match_parent"\n                android:layout_height="0dp"\n                app:layout_constraintBottom_toBottomOf="parent"\n                app:layout_constraintBottom_toTopOf="@id/bottom_nav"\n                app:layout_constraintEnd_toEndOf="parent"\n                app:layout_constraintStart_toStartOf="parent"\n                app:layout_constraintTop_toTopOf="parent" />\n\n\n            <com.google.android.material.bottomnavigation.BottomNavigationView\n                android:id="@+id/bottom_nav"\n                android:layout_width="match_parent"\n                android:layout_height="wrap_content"\n                app:layout_constraintBottom_toBottomOf="parent"\n                app:menu="@menu/menu_bottom_nav" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>\n
Run Code Online (Sandbox Code Playgroud)\n

当我们更改选项卡时,MainActivity 会监听BottomNavigationView项目的更改和当前的更改NavController,因为我们必须Appbar为每个选项卡设置导航。

\n
class MainActivity : AppCompatActivity() {\n\n//    private val appbarViewModel by viewModels<AppbarViewModel>()<AppbarViewModel>()\n\n    private val appbarViewModel:AppbarViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val dataBinding: ActivityMainBinding =\n            DataBindingUtil.setContentView(this, R.layout.activity_main)\n\n        val viewPager2 = dataBinding.viewPager\n        val bottomNavigationView = dataBinding.bottomNav\n\n        // Cancel ViewPager swipe\n        viewPager2.isUserInputEnabled = false\n\n        // Set viewpager adapter\n        viewPager2.adapter = ActivityFragmentStateAdapter(this)\n        \n        // Listen bottom navigation tabs change\n        bottomNavigationView.setOnNavigationItemSelectedListener {\n\n            when (it.itemId) {\n                R.id.nav_graph_home -> {\n                    viewPager2.setCurrentItem(0, false)\n                    return@setOnNavigationItemSelectedListener true\n\n                }\n                R.id.nav_graph_dashboard -> {\n                    viewPager2.setCurrentItem(1, false)\n                    return@setOnNavigationItemSelectedListener true\n                }\n                R.id.nav_graph_notification -> {\n                    viewPager2.setCurrentItem(2, false)\n                    return@setOnNavigationItemSelectedListener true\n                }\n            }\n            false\n        }\n\n        appbarViewModel.currentNavController.observe(this, Observer { navController ->\n            navController?.let {\n                val appBarConfig = AppBarConfiguration(it.graph)\n                dataBinding.toolbar.setupWithNavController(it, appBarConfig)\n            }\n        })\n\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

AppbarViewModel只有一个MutableLiveData可以设置 current NavController。使用 ViewModel 在 NavHost 片段中设置 ViewModel 并能够在 Activity 或其他片段中获取它的目的。

\n
class AppbarViewModel : ViewModel() {\n    val currentNavController = MutableLiveData<NavController?>()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

具有 FragmentContainerView 的 NavHost 的布局,当我将 Toolbar 放入这些片段并使用时,FragmentContainerView我会收到错误,fragment如果您将 appBar 与导航一起使用,请使用。

\n

片段_navhost_home.xml

\n
<?xml version="1.0" encoding="utf-8"?>\n<layout xmlns:android="http://schemas.android.com/apk/res/android"\n    xmlns:app="http://schemas.android.com/apk/res-auto">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width="match_parent"\n        android:layout_height="match_parent">\n\n        <androidx.fragment.app.FragmentContainerView\n            android:id="@+id/nested_nav_host_fragment_home"\n            android:name="androidx.navigation.fragment.NavHostFragment"\n            android:layout_width="0dp"\n            android:layout_height="0dp"\n            app:layout_constraintBottom_toBottomOf="parent"\n            app:layout_constraintLeft_toLeftOf="parent"\n            app:layout_constraintRight_toRightOf="parent"\n            app:layout_constraintTop_toTopOf="parent"\n\n            app:defaultNavHost="false"\n            app:navGraph="@navigation/nav_graph_home"/>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</layout>\n
Run Code Online (Sandbox Code Playgroud)\n

包含子片段和 NavController 的 NavHost Fragment,其中 3 个是相同的,所以我只放了一个

\n
class HomeNavHostFragment : BaseDataBindingFragment<FragmentNavhostHomeBinding>() {\n    override fun getLayoutRes(): Int = R.layout.fragment_navhost_home\n\n    private val appbarViewModel by activityViewModels<AppbarViewModel>()\n\n    private var navController: NavController? = null\n\n    private val nestedNavHostFragmentId = R.id.nested_nav_host_fragment_home\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        val nestedNavHostFragment =\n            childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment\n        navController = nestedNavHostFragment?.navController\n\n\n        // Listen on back press\n        listenOnBackPressed()\n\n    }\n\n    private fun listenOnBackPressed() {\n        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)\n    }\n\n\n    override fun onResume() {\n        super.onResume()\n        callback.isEnabled = true\n\n        // Set this navController as ViewModel\'s navController\n        appbarViewModel.currentNavController.value = navController\n\n    }\n\n    override fun onPause() {\n        super.onPause()\n        callback.isEnabled = false\n    }\n\n    /**\n     * This callback should be created with Disabled because on rotation ViewPager creates\n     * NavHost fragments that are not on screen, destroys them afterwards but it might take\n     * up to 5 seconds.\n     *\n     * ### Note: During that interval touching back button sometimes call incorrect [OnBackPressedCallback.handleOnBackPressed] instead of this one if callback is **ENABLED**\n     */\n    val callback = object : OnBackPressedCallback(false) {\n\n        override fun handleOnBackPressed() {\n\n\n            // Check if it\'s the root of nested fragments in this navhost\n            if (navController?.currentDestination?.id == navController?.graph?.startDestination) {\n\n                Toast.makeText(requireContext(), "AT START DESTINATION ", Toast.LENGTH_SHORT)\n                    .show()\n\n                /*\n                    Disable this callback because calls OnBackPressedDispatcher\n                     gets invoked  calls this callback  gets stuck in a loop\n                 */\n                isEnabled = false\n                requireActivity().onBackPressed()\n                isEnabled = true\n\n            } else {\n                navController?.navigateUp()\n            }\n\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

嵌套导航需要注意的重要事项是

\n
    \n
  1. 按下后退按钮时能够正确导航
  2. \n
  3. 仅从可见片段导航,如果未正确实现,则调用其他片段回调回按
  4. \n
  5. 旋转后仅将可见片段的后按设置为活动状态
  6. \n
\n

首先,您需要检查您是否是图表的起始目的地,因为您需要调用requireActivity().onBackPressed()回调 Activity ,否则您会卡在 HomeFragment1 处

\n

如果您在调用之前不禁用回调,requireActivity().onBackPressed()则会陷入循环,因为 onBackPressed 也会调用 Active 回调

\n

callback.isEnabled = false如果当前 Fragment 不可见时不禁用,则每个回调都会被调用

\n

最后,我认为最重要的是如果你旋转你的设备

\n

其他选项卡中的片段也会由 viewPager 创建,然后在 3 到 5 后销毁,但它们的 onResume 不会被调用,这会导致其他回调在创建对象时调用 handleBackPressed : OnBackPressedCallback( true ),使用

\n
object : OnBackPressedCallback(false)\n
Run Code Online (Sandbox Code Playgroud)\n

例如,如果回调处于活动状态,并且您在HomeFragment3打开时旋转设备,并且在回调处于活动状态时触摸后退按钮

\n
2020-06-28 13:23:42.722 I:  HomeNavHostFragment #208670033  onCreate()\n2020-06-28 13:23:42.729 I: \xe2\x8f\xb0 NotificationHostFragment #19727909  onCreate()\n2020-06-28 13:23:42.826 I:  HomeNavHostFragment #208670033  onViewCreated()\n2020-06-28 13:23:42.947 I: \xe2\x8f\xb0 NotificationHostFragment #19727909  onViewCreated()\n2020-06-28 13:23:42.987 I:  HomeNavHostFragment #208670033  onResume()\n2020-06-28 13:23:44.092 I: \xe2\x8f\xb0 NotificationHostFragment #19727909 handleOnBackPressed()\n2020-06-28 13:23:44.851 I: \xe2\x8f\xb0 NotificationHostFragment #19727909 handleOnBackPressed()\n2020-06-28 13:23:53.011 I: \xe2\x8f\xb0 NotificationHostFragment #19727909  onDestroyView()\n2020-06-28 13:23:53.023 I: \xe2\x8f\xb0 NotificationHostFragment #19727909  onDestroy()\n
Run Code Online (Sandbox Code Playgroud)\n

即使我在 HomeFragment3 可见时按后退按钮两次,\xe2\x8f\xb0 NotificationHostFragment #19727909 handleOnBackPressed()也会被调用,因为 ViewPager 创建也不可见的片段并在之后销毁它们。我的例子花了10秒,你也可以尝试一下。

\n

编辑onBackPressedDispatcher:建议使用下面的片段,而不是在 ViewPager 2 的每个片段中,其中FragmentStateAdapter将屏幕上的活动片段设置为主要导航片段

\n
/**\n * FragmentStateAdapter to add ability to set primary navigation fragment\n * which lets fragment visible to be navigable when back button is pressed using\n * [FragmentStateAdapter.FragmentTransactionCallback] in [ViewPager2].\n *\n * *  Create FragmentStateAdapter with viewLifeCycleOwner instead of Fragment to make sure\n * that it lives between [Fragment.onCreateView] and [Fragment.onDestroyView] while [View] is alive\n *\n * * /sf/ask/4324584351/\n */\nabstract class NavigableFragmentStateAdapter(\n    fragmentManager: FragmentManager,\n    lifecycle: Lifecycle\n) : FragmentStateAdapter(fragmentManager, lifecycle) {\n\n    private val fragmentTransactionCallback =\n        object : FragmentStateAdapter.FragmentTransactionCallback() {\n            override fun onFragmentMaxLifecyclePreUpdated(\n                fragment: Fragment,\n                maxLifecycleState: Lifecycle.State\n            ) = if (maxLifecycleState == Lifecycle.State.RESUMED) {\n\n                // This fragment is becoming the active Fragment - set it to\n                // the primary navigation fragment in the OnPostEventListener\n                OnPostEventListener {\n                    fragment.parentFragmentManager.commitNow {\n                        setPrimaryNavigationFragment(fragment)\n                    }\n                }\n            } else {\n                super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)\n            }\n        }\n\n    init {\n        // Add a FragmentTransactionCallback to handle changing\n        // the primary navigation fragment\n        registerFragmentTransactionCallback()\n    }\n\n    fun registerFragmentTransactionCallback() {\n        registerFragmentTransactionCallback(fragmentTransactionCallback)\n    }\n\n    fun unregisterFragmentTransactionCallback() {\n        unregisterFragmentTransactionCallback(fragmentTransactionCallback)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是完整示例的链接。您还可以将 Toolbar 放置到每个 navHost 片段中,这更简单一些。

\n

您可以使用工具栏在 NavHost 片段中调用它

\n
val appBarConfig = AppBarConfiguration(navController!!.graph)\ndataBinding.toolbar.setupWithNavController(navController!!, appBarConfig)\n
Run Code Online (Sandbox Code Playgroud)\n

  • 当然,更新了[此示例的链接](https://github.com/SmartToolFactory/NavigationComponents-Tutorials/tree/master/Tutorial7-1BNV-ViewPager2-NestedNavigation)。对于包含其他示例的[整个存储库](https://github.com/SmartToolFactory/NavigationComponents-Tutorials) (3认同)

Ant*_*hon 7

我的一个解决方案是将 ViewPager 中的片段留在导航之外,并直接在页面片段上设置操作,就好像这些页面是主机一样。为了更好地解释它:

假设您在具有 Fragment B 的 ViewPager 的 Fragment A 中,并且您尝试从 B 导航到 C

在片段 B 中,使用 ADirections 类和从 A 到 C 的操作。 findNavHost().navigateTo(ADirections.ActionFromAtoC)


小智 -2

我们可以使用底部导航组件和NavigationGraph轻松实现。

您应该为每个底部导航菜单创建相应的片段

nav_graph.xml

 <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/nav_graph"
        app:startDestination="@id/actionHome">

        <fragment
            android:id="@+id/actionHome"
            android:name="com.sample.demo.fragments.Home"
            android:label="fragment_home"
            tools:layout="@layout/fragment_home">
            <action
                android:id="@+id/toExplore"
                app:destination="@id/actionExplore" />
        </fragment>
        <fragment
            android:id="@+id/actionExplore"
            android:name="com.sample.demo.fragments.Explore"
            android:label="fragment_explore"
            tools:layout="@layout/fragment_explore" />
        <fragment
            android:id="@+id/actionBusiness"
            android:name="com.sample.demo.fragments.Business"
            android:label="fragment_business"
            tools:layout="@layout/fragment_business" />
        <fragment
            android:id="@+id/actionProfile"
            android:name="com.sample.demo.fragments.Profile"
            android:label="fragment_profile"
            tools:layout="@layout/fragment_profile" />

    </navigation>
Run Code Online (Sandbox Code Playgroud)

每个导航片段 ID 和底部导航菜单项 ID 应该相同。例如这里

 <fragment
  android:id="@+id/actionBusiness"
 android:name="com.sample.demo.fragments.Business"
                android:label="fragment_business"
                tools:layout="@layout/fragment_business" />
Run Code Online (Sandbox Code Playgroud)

底部导航菜单navigation.xml下方

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/actionExplore"
        android:icon="@drawable/ic_search_24dp"
        android:title="@string/explore" />

    <item
        android:id="@+id/actionBusiness"
        android:icon="@drawable/ic_business_24dp"
        android:title="@string/business" />

    <item
        android:id="@+id/actionProfile"
        android:icon="@drawable/ic_profile_24dp"
        android:title="@string/profile" />


</menu>
Run Code Online (Sandbox Code Playgroud)

将 nav_graph.xml 设置为 Activity_main.xml 中的 palceholder 片段

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/gradient_bg"
    android:focusable="true"
    android:focusableInTouchMode="true"
    tools:context=".MainActivity"
    tools:layout_editor_absoluteY="25dp">

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="@color/semi_grey"
        app:itemIconTint="@drawable/bottom_bar_nav_item"
        app:itemTextColor="@drawable/bottom_bar_nav_item"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/navigation" />

    <include
        android:id="@+id/appBarLayout"
        layout="@layout/app_bar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <fragment
        android:id="@+id/mainNavigationFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:paddingBottom="@dimen/activity_horizontal_margin"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
        app:navGraph="@navigation/nav_graph" />

</android.support.constraint.ConstraintLayout>
Run Code Online (Sandbox Code Playgroud)

将导航图映射到此处的片段app:navGraph="@navigation/nav_graph"

之后在MainActivity.java中实现导航图和bottomNavigation组件

 BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        NavController navController = Navigation.findNavController(this, R.id.mainNavigationFragment);
        NavigationUI.setupWithNavController(navigation, navController); 
Run Code Online (Sandbox Code Playgroud)

干杯!!!