如何使用新的导航体系结构组件实现ViewPager?

时间:2018-10-03 15:12:01

标签: android android-architecture-components

我有一个带有BottomNavigationViewViewPager的应用程序。 如何使用新的“导航架构组件”实现它?

最佳做法是什么?

非常感谢

9 个答案:

答案 0 :(得分:9)

BottomNavigationView与导航拱形组件的默认实现对我来说不可行。单击选项卡时,它会根据导航图从头开始。

我需要在屏幕底部拥有5个标签,并且每个标签都有一个单独的后盖。这意味着在标签之间切换时,您将始终返回到离开前的状态(如Instagram)。

我的方法如下:

  1. ViewPagerBottomNavigationView放入activity_main.xml
  2. OnNavigationItemSelectedListener中将BottomNavigationView设置为MainActivity.kt
  3. 为每个标签创建单独的容器片段(它们将成为每个标签的起点)
  4. 在容器片段的xml中
  5. 包括NavHostFragment
  6. 在每个Container片段中为Navigation Arch Component实施必要的代码。
  7. 为每个标签创建图形

注意:每个图都可以互相影响。

重要的一点是,我们将工具栏不是置于活动中,而是置于容器片段中。然后,我们在工具栏本身上调用setupWithNavController(),而未将其设置为supportActionBar。这样,工具栏标题将自动更新,并且 Back / Up 按钮将被自动管理。

结果:

  • ViewPager存储每个选项卡的状态。
  • 不用担心碎片交易。
  • SafeArgsDeepLinking正常工作。
  • 我们对BottomNavigationManagerViewPager拥有完全控制权(即,我们可以实现OnNavigationItemReselectedListener并决定在弹出后退堆栈之前将当前选项卡中的列表滚动到顶部)。

代码:

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>

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)
    }
}

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
    }
}

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>

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)
    }
}

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

navigation graphs

但是我们需要为每个标签创建一个单独的图形:

<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>

此处的起始目标片段是您要在标签中的第一个屏幕上显示的任何片段。

答案 1 :(得分:2)

我创建了一个在Activity上具有工具栏的示例,您也可以创建具有自己的工具栏的ViewPager片段。它使用OnBackPressedCallback进行向后导航,使用ViewModel设置具有NavController或嵌套片段的当前NavHostFragmentchildFragmentManager,并使用viewLifeCycleOwner尊重生命周期并在其上禁用回调暂停并启用onResume。

BottomNavigationView with ViewPager and Navigation Architecture

导航和布局架构

     MainActivity(Appbar + Toolbar  + ViewPager2 + BottomNavigationView)
       |
       |- HomeNavHostFragment
       |  |- HF1 -> HF2 -> HF3
       |
       |- DashboardNavHostFragment
       |  |- DF1 -> DF2 -> DF3
       |
       |- NotificationHostFragment
          |- NF1 -> NF2 -> NF3

首先,为ViewPager2的每个标签或片段创建导航图

nav_graph_home.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_dashboard"
    app:startDestination="@id/dashboardFragment1">


    <fragment
        android:id="@+id/dashboardFragment1"
        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment1"
        android:label="DashboardFragment1"
        tools:layout="@layout/fragment_dashboard1">
        <action
            android:id="@+id/action_dashboardFragment1_to_dashboardFragment2"
            app:destination="@id/dashboardFragment2" />
    </fragment>

    <fragment
        android:id="@+id/dashboardFragment2"
        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment2"
        android:label="DashboardFragment2"
        tools:layout="@layout/fragment_dashboard2">
        <action
            android:id="@+id/action_dashboardFragment2_to_dashboardFragment3"
            app:destination="@id/dashboardFragment3" />
    </fragment>
    <fragment
        android:id="@+id/dashboardFragment3"
        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment3"
        android:label="DashboardFragment3"
        tools:layout="@layout/fragment_dashboard3" >
        <action
            android:id="@+id/action_dashboardFragment3_to_dashboardFragment1"
            app:destination="@id/dashboardFragment1"
            app:popUpTo="@id/dashboardFragment1"
            app:popUpToInclusive="true" />
    </fragment>

</navigation>

其他导航图与此图相同

BottomNavigationView的菜单

menu_bottom_nav.xml

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

    <item
            android:id="@+id/nav_graph_home"
            android:icon="@drawable/ic_baseline_home_24"
            android:title="Home"/>
    <item
            android:id="@+id/nav_graph_dashboard"
            android:icon="@drawable/ic_baseline_dashboard_24"
            android:title="Dashboard"/>
    <item
            android:id="@+id/nav_graph_notification"
            android:icon="@drawable/ic_baseline_notifications_24"
            android:title="Notification"/>
</menu>

ViewPager2适配器

class ActivityFragmentStateAdapter(fragmentActivity: FragmentActivity) :
    FragmentStateAdapter(fragmentActivity) {
    
    override fun getItemCount(): Int = 3

    override fun createFragment(position: Int): Fragment {

        return when (position) {
            0 -> HomeNavHostFragment()
            1 -> DashBoardNavHostFragment()
            else -> NotificationHostFragment()
        }
    }
}

主要活动的布局

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

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" />

        </com.google.android.material.appbar.AppBarLayout>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/viewPager"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintBottom_toTopOf="@id/bottom_nav"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />


            <com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/bottom_nav"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:menu="@menu/menu_bottom_nav" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

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

class MainActivity : AppCompatActivity() {

//    private val appbarViewModel by viewModels<AppbarViewModel>()<AppbarViewModel>()

    private val appbarViewModel:AppbarViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)

        val viewPager2 = dataBinding.viewPager
        val bottomNavigationView = dataBinding.bottomNav

        // Cancel ViewPager swipe
        viewPager2.isUserInputEnabled = false

        // Set viewpager adapter
        viewPager2.adapter = ActivityFragmentStateAdapter(this)
        
        // Listen bottom navigation tabs change
        bottomNavigationView.setOnNavigationItemSelectedListener {

            when (it.itemId) {
                R.id.nav_graph_home -> {
                    viewPager2.setCurrentItem(0, false)
                    return@setOnNavigationItemSelectedListener true

                }
                R.id.nav_graph_dashboard -> {
                    viewPager2.setCurrentItem(1, false)
                    return@setOnNavigationItemSelectedListener true
                }
                R.id.nav_graph_notification -> {
                    viewPager2.setCurrentItem(2, false)
                    return@setOnNavigationItemSelectedListener true
                }
            }
            false
        }

        appbarViewModel.currentNavController.observe(this, Observer { navController ->
            navController?.let {
                val appBarConfig = AppBarConfiguration(it.graph)
                dataBinding.toolbar.setupWithNavController(it, appBarConfig)
            }
        })

    }
}

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

class AppbarViewModel : ViewModel() {
    val currentNavController = MutableLiveData<NavController?>()
}

具有FragmentContainerView的NavHost的布局,当我将工具栏放入这些片段并使用FragmentContainerView时出现错误,如果将appBar与导航一起使用,则使用fragment

fragment_navhost_home.xml

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nested_nav_host_fragment_home"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"

            app:defaultNavHost="false"
            app:navGraph="@navigation/nav_graph_home"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

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

class HomeNavHostFragment : BaseDataBindingFragment<FragmentNavhostHomeBinding>() {
    override fun getLayoutRes(): Int = R.layout.fragment_navhost_home

    private val appbarViewModel by activityViewModels<AppbarViewModel>()

    private var navController: NavController? = null

    private val nestedNavHostFragmentId = R.id.nested_nav_host_fragment_home

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

        val nestedNavHostFragment =
            childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment
        navController = nestedNavHostFragment?.navController


        // Listen on back press
        listenOnBackPressed()

    }

    private fun listenOnBackPressed() {
        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
    }


    override fun onResume() {
        super.onResume()
        callback.isEnabled = true

        // Set this navController as ViewModel's navController
        appbarViewModel.currentNavController.value = navController

    }

    override fun onPause() {
        super.onPause()
        callback.isEnabled = false
    }

    /**
     * This callback should be created with Disabled because on rotation ViewPager creates
     * NavHost fragments that are not on screen, destroys them afterwards but it might take
     * up to 5 seconds.
     *
     * ### Note: During that interval touching back button sometimes call incorrect [OnBackPressedCallback.handleOnBackPressed] instead of this one if callback is **ENABLED**
     */
    val callback = object : OnBackPressedCallback(false) {

        override fun handleOnBackPressed() {


            // Check if it's the root of nested fragments in this navhost
            if (navController?.currentDestination?.id == navController?.graph?.startDestination) {

                Toast.makeText(requireContext(), "AT START DESTINATION ", Toast.LENGTH_SHORT)
                    .show()

                /*
                    Disable this callback because calls OnBackPressedDispatcher
                     gets invoked  calls this callback  gets stuck in a loop
                 */
                isEnabled = false
                requireActivity().onBackPressed()
                isEnabled = true

            } else {
                navController?.navigateUp()
            }

        }
    }
}

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

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

首先,您需要检查您是否是图形的起始目标,因为您需要调用requireActivity().onBackPressed()来回叫Activity或例如陷入HomeFragment1的困境

如果在调用requireActivity().onBackPressed()之前未禁用回调,则会陷入循环,因为onBackPressed还会调用活动回调

如果在当前片段不可见时未禁用callback.isEnabled = false,则会调用每个回调

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

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

object : OnBackPressedCallback(false)

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

2020-06-28 13:23:42.722 I: ? HomeNavHostFragment #208670033  onCreate()
2020-06-28 13:23:42.729 I: ⏰ NotificationHostFragment #19727909  onCreate()
2020-06-28 13:23:42.826 I: ? HomeNavHostFragment #208670033  onViewCreated()
2020-06-28 13:23:42.947 I: ⏰ NotificationHostFragment #19727909  onViewCreated()
2020-06-28 13:23:42.987 I: ? HomeNavHostFragment #208670033  onResume()
2020-06-28 13:23:44.092 I: ⏰ NotificationHostFragment #19727909 handleOnBackPressed()
2020-06-28 13:23:44.851 I: ⏰ NotificationHostFragment #19727909 handleOnBackPressed()
2020-06-28 13:23:53.011 I: ⏰ NotificationHostFragment #19727909  onDestroyView()
2020-06-28 13:23:53.023 I: ⏰ NotificationHostFragment #19727909  onDestroy()

即使在HomeFragment3可见时我按了两次返回按钮,也会调用⏰ NotificationHostFragment #19727909 handleOnBackPressed(),因为ViewPager会创建同样不可见的片段并随后销毁它们。在我的实例中花了10秒钟,您也可以尝试一下。

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

/**
 * FragmentStateAdapter to add ability to set primary navigation fragment
 * which lets fragment visible to be navigable when back button is pressed using
 * [FragmentStateAdapter.FragmentTransactionCallback] in [ViewPager2].
 *
 * * ? Create FragmentStateAdapter with viewLifeCycleOwner instead of Fragment to make sure
 * that it lives between [Fragment.onCreateView] and [Fragment.onDestroyView] while [View] is alive
 *
 * * https://stackoverflow.com/questions/61779776/leak-canary-detects-memory-leaks-for-tablayout-with-viewpager2
 */
abstract class NavigableFragmentStateAdapter(
    fragmentManager: FragmentManager,
    lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {

    private val fragmentTransactionCallback =
        object : FragmentStateAdapter.FragmentTransactionCallback() {
            override fun onFragmentMaxLifecyclePreUpdated(
                fragment: Fragment,
                maxLifecycleState: Lifecycle.State
            ) = if (maxLifecycleState == Lifecycle.State.RESUMED) {

                // This fragment is becoming the active Fragment - set it to
                // the primary navigation fragment in the OnPostEventListener
                OnPostEventListener {
                    fragment.parentFragmentManager.commitNow {
                        setPrimaryNavigationFragment(fragment)
                    }
                }
            } else {
                super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
            }
        }

    init {
        // Add a FragmentTransactionCallback to handle changing
        // the primary navigation fragment
        registerFragmentTransactionCallback()
    }

    fun registerFragmentTransactionCallback() {
        registerFragmentTransactionCallback(fragmentTransactionCallback)
    }

    fun unregisterFragmentTransactionCallback() {
        unregisterFragmentTransactionCallback(fragmentTransactionCallback)
    }
}

这里是link for full sample。您还可以将Toolbar放入每个navHost片段,这有点简单。

您使用工具栏调用NavHost片段

val appBarConfig = AppBarConfiguration(navController!!.graph)
dataBinding.toolbar.setupWithNavController(navController!!, appBarConfig)

答案 2 :(得分:0)

我们可以轻松使用底部导航组件和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>

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

 <fragment
  android:id="@+id/actionBusiness"
 android:name="com.sample.demo.fragments.Business"
                android:label="fragment_business"
                tools:layout="@layout/fragment_business" />

在底部导航菜单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>

将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>

在此处将导航图映射为片段 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); 

干杯!

答案 3 :(得分:0)

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

假设您位于片段A中,而ViewPager为片段B 然后您尝试从B导航到C

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

答案 4 :(得分:0)

我写了a related article on this regarding view pagers,特别关注标签中的主从片段,但相同的逻辑适用于常规ViewPagers。代码是located here

答案 5 :(得分:0)

我有一个MainFragment,它在viewPager中托管片段A,片段B和片段C。

我想从片段B(由MainFragment内部的viewPager托管)打开片段D。

所以我创建了一个从MainFragment到Fragment D的动作,并从Fragment B调用

val direction = FragmentMainDirections.actionFragmentMainToFragmentD()
findNavController().navigate(direction)

工作。

答案 6 :(得分:0)

除了要让Marat在每个片段中使用后退按钮工作的答案外,您还必须将其添加到onViewCreated的容器片段中:

val callback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (!navHostFragment.navController.popBackStack()) {
                    isEnabled = false
                    activity?.onBackPressed()
                }
            }
        }
activity?.onBackPressedDispatcher?.addCallback(this, callback)

答案 7 :(得分:0)

感谢@Marat-他提供了很好的解决方案。 就我而言,我在第二个ViewPager的视图中具有列表/详细视图导航,并使用 Fullscreeen模式,而没有任何Action \ Tool-bar

enter image description here

想评论一下:

1)我可以在一张页面上使用一个普通图形:

<?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/page2Coordinator"
    app:startDestination="@id/Fragment_2Coordinator">

    <fragment
        android:id="@+id/Fragment_2Coordinator"
        android:name="my.app.Fragment_2Coordinator"
        android:label="Fragment_2Coordinator">
        <action
            android:id="@+id/action_showList_2A"
            app:destination="@id/Fragment_2A" />
    </fragment>

    <fragment
        android:id="@+id/Fragment_2A"
        android:name="my.app.Fragment_2A"
        android:label="Fragment_2A">

        <action
            android:id="@+id/action_goToDetail_2B"
            app:destination="@id/Fragment_2B" />
    </fragment>

    <fragment
        android:id="@+id/Fragment_2B"
        android:name="my.app.Fragment_2B"
        android:label="Fragment_2B">

        <action
            android:id="@+id/action_backToList_2A"
            app:destination="@id/Fragment_2A" />
    </fragment>
</navigation>

2)而不是使用Fragment_2Coordinator.onViewCreated()中的工具栏进行操作,只需在图形中进行操作即可导航(以防不使用系统导航):

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val navHostFragment = childFragmentManager.findFragmentById(R.id. tab_1_nav_host_fragment) as NavHostFragment? ?: return
    val navController = navHostFragment.navController
    navController.navigate(R.id.action_showList_2A)
}

3)要使用电话“返回”按钮从2B返回2A,请转到“活动”:

class MainActivity : AppCompatActivity() {

 .  .  .  .  . 

    override fun onBackPressed() {

        val navController = findNavController(R.id.tab_1_nav_host_fragment)

        when(navController.currentDestination?.id) {
            R.id.Fragment_2B -> {
                navController.navigate(R.id.action_backToList_2A)
            }
            else -> {
                super.onBackPressed()
            }
        }
        println()
    }
}

答案 8 :(得分:-1)

我已经使用viewpager实现了 Android Arch导航。请看一看。任何改进都欢迎。让我们学习聚会。

https://github.com/Maqsood007/AndroidJetpack/tree/master/ArchNavViewPagerImpl