【问题标题】:Troubles setting up BottomNavigationView + Navigation Componenent设置 BottomNavigationView + 导航组件的问题
【发布时间】:2020-08-06 14:07:50
【问题描述】:

我正在将我的应用程序转换为使用一个活动并添加了底部导航视图,以防止在片段之间导航时重新创建片段,进行不必要的 api 调用。但是我不能让它工作:

  • 没有显示片段
  • 图标不切换
  • 触摸底部菜单项不会切换片段
  • 触摸所选项目会使应用程序崩溃并出现 TypeCastException: kotlin.TypeCastException: null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment at com.example.testapp.NavigationExtensionsKt$setupItemReselected$1.onNavigationItemReselected

Activity的布局:

<androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/nav_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="0dp"
        android:layout_marginStart="0dp"
        app:menu="@menu/bottom_nav_menu" />

底部导航菜单:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_1"
        android:icon="@drawable/ic_1"
        android:title="@string/title_1" />

    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_2"
        android:title="@string/home" />

    <item
        android:id="@+id/navigation_3"
        android:icon="@drawable/ic_3"
        android:title="@string/title_3" />
</menu>

在 MainActivity 中:

    class MainActivity : DaggerAppCompatActivity() {

    private var currentNavController: LiveData<NavController>? = null

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

        if(savedInstanceState == null){
            setupBottomNavigationBar()
        } // Else, need to wait for onRestoreInstanceState

    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        // Now that BottomNavigationBar has restored its instance state
        // and its selectedItemId, we can proceed with setting up the
        // BottomNavigationBar with Navigation
        setupBottomNavigationBar()
    }

    private fun setupBottomNavigationBar() {
        val bottomNavigationView = findViewById<BottomNavigationView>(R.id.nav_view)

        val navGraphIds = listOf(R.navigation.mobile_navigation, R.navigation.settings)
        val menuItemsIds = listOf(R.id.navigation_1, R.id.homeFragment, R.id.navigation_3)
        val defaultIconsIds = listOf(R.drawable.ic_1,  R.drawable.ic_2, R.drawable.ic_3)
        val selectedIconsIds = listOf(R.drawable.ic_1a, R.drawable.ic_2a, R.drawable.ic_3a)

        // Setup the bottom navigation view with a list of navigation graphs (custom extension)
        val controller = bottomNavigationView.setupWithNavController(
            navGraphIds = navGraphIds,
            menuItemsIds = menuItemsIds,
            defaultIconsIds = defaultIconsIds,
            selectedIconsIds = selectedIconsIds,
            fragmentManager = supportFragmentManager,
            containerId = R.id.nav_host_container,
            intent = intent
        )

        currentNavController = controller

        // updating icons
        nav_view.menu.iterator().forEach {
            if (it.isChecked) {
                it.setIcon(selectedIconsIds[menuItemsIds.indexOf(it.itemId)])
            } else {
                it.setIcon(defaultIconsIds[menuItemsIds.indexOf(it.itemId)])
            }
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        return currentNavController?.value?.navigateUp() ?: false
    }
}

导航扩展:

   //.....
    //....

    private fun BottomNavigationView.setupItemReselected(
    menuItemsIds: List<Int>,
    defaultIconsIds: List<Int>,
    selectedIconsIds: List<Int>,
    graphIdToTagMap: SparseArray<String>,
    fragmentManager: FragmentManager) {
    setOnNavigationItemReselectedListener { item ->

        this.menu.iterator().forEach { it ->
            it.setIcon(defaultIconsIds[menuItemsIds.indexOf(it.itemId)])
        }

        item.setIcon(selectedIconsIds[menuItemsIds.indexOf(item.itemId)])

        val newlySelectedItemTag = graphIdToTagMap[item.itemId]
        val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment //TypeCastException happens here
        val navController = selectedFragment.navController
        // Pop the back stack to the start destination of the current navController graph
        navController.popBackStack(
            navController.graph.startDestination, false
        )
    }
}

屏幕是空白的,没有渲染任何片段视图。

谁能帮我解决这个问题?我错过了什么吗?

PS:代码取自一个开源应用程序,可以在here找到

[编辑] 添加导航图:

  1. mobile_navigation

<fragment
    android:id="@+id/homeFragment"
    android:name="com.example.testapp.homeFragment"
    android:label="@string/home"
    tools:layout="@layout/home_fragment" >

    <action
        android:id="@+id/homrFragment_to_detailsFragment"
        app:launchSingleTop="true"
        app:enterAnim="@anim/fragment_open_enter"
        app:exitAnim="@anim/fragment_open_exit"
        app:destination="@id/detailsFragment" />
</fragment>

<fragment
    android:id="@+id/navigation_1"
    android:name="com.example.testapp.Fragment_S"
    android:label="@string/fragment_s"
    tools:layout="@layout/fragment_s" >
    <action
       android:id="@+id/action_navigation_1_to_detailsFragment"
        app:destination="@id/detailsFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_in_left" />
</fragment>

<fragment
    android:id="@+id/navigation_3"
    android:name="com.example.testapp.Fragment3"
    android:label="@string/fragment_ss"
    tools:layout="@layout/fragment_ss" />


<dialog
    android:id="@+id/detailsFragment"
    android:name="com.example.testapp.DetailsFragment"
    android:label="InDetails"
    tools:layout="@layout/layout_details">

    <argument
        android:name="arg1"
        android:defaultValue=""
        app:argType="string"/>

    <argument
        android:name="arg2"
        android:defaultValue=""
        app:argType="string" />
</dialog>
  1. 导航设置:

     <?xml version="1.0" encoding="utf-8"?>
    

【问题讨论】:

    标签: android android-fragments exception bottomnavigationview kotlin-android-extensions


    【解决方案1】:

    使用导航组件设置 BottomNavigationView 可能有点棘手,但我认为您作为示例所遵循的代码有点过度设计和/或用于比初始代码更复杂的用途。

    我会在评论中询问(没有足够的代表),但是您也可以发布 NavGraph 吗?正确设置 NavGraph、菜单和布局后,设置导航控制器非常容易,但所有这些都必须精心同步,并且这方面的文档并不是那么好。

    在您回复导航图之前,我会尽力提供解决方案:

    首先,改变你的

    <androidx.fragment.app.FragmentContainerView
    

    <fragment
    

    但添加以下标签

            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/your_nav_graph.xml"
    

    然后,确保 menu.xml 上的 id 与您想要的目的地的 id 匹配,如导航图中所述,例如,您应该有 3 个目的地被调用

    android:id="@+id/navigation_1"
    
    android:id="@+id/homeFragment"
    
    android:id="@+id/navigation_3"
    

    您无需设置任何操作。

    之后,只需在带有 BottomNavigationView 的 Activity 中执行此操作即可:

    NavigationUI.setupWithNavController(your_bottom_navigation_view, findNavController(R.id.your_navigation_fragment))
    

    在此之后导航应该可以正常工作,无需在“setupBottomNavigationView()”方法或您还发布的导航扩展上设置所有内容。

    这样,导航可以处理所有事情,从 backtstack 到突出显示 BottomNavigationView 上的正确按钮。

    【讨论】:

    • 感谢您的回答。您提供的方法是我在尝试切换到我面临问题的实现之前所拥有的。但是,每次用户切换到片段时,片段都会重新创建触发 api 调用。所以我试图找到一种方式,片段不会重新创建,并且来自链接源的应用程序的工作方式类似于我想要实现的目标
    • 好吧,我猜你还不能发布 cmets... 我很快就会更新导航图。但这是一个没有复杂性的简单图表
    • 显然,我暂时可以回答。我看到了问题。如果这样设置,我认为导航组件不会是最好的方法。完全违背了目的。我建议您自己使用 viewpager 或管理片段事务,
    • 好吧,我尽量避免使用样板代码。走你建议的方式对我来说并没有真正的帮助。我认为我链接的示例可能会有所帮助,因为他们的应用程序的行为与我希望我的行为完全一致
    • 用两个导航图更新了我的问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 2021-06-20
    • 2022-06-17
    相关资源
    最近更新 更多