Проблемы с настройкой BottomNavigationView + Navigation Componenent - PullRequest
0 голосов
/ 06 августа 2020

Я конвертирую свое приложение для использования одного действия и добавляю BottomnavigationView, а также пытаюсь предотвратить воссоздание фрагментов, делая ненужные вызовы api при навигации между фрагментами. Однако я не могу заставить его работать:

  • Фрагмент не отображается
  • Иконки не переключаются
  • Касание пунктов нижнего меню не переключает фрагменты
  • Прикосновение к выбранному элементу приводит к сбою приложения с исключением TypeCastException: kotlin.TypeCastException: null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment at com.example.testapp.NavigationExtensionsKt$setupItemReselected$1.onNavigationItemReselected

Макет действия:

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

Меню BottomNav:

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

NavigationExtensions:

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

    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: Код взят из приложения с открытым исходным кодом, которое можно найти здесь

[Edit] Добавление NavGraph:

  1. мобильная_навигация

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

Настройки навигации:

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

1 Ответ

1 голос
/ 06 августа 2020

Настройка BottomNavigationView может быть немного сложной для настройки с помощью компонентов навигации, но я думаю, что код, который вы используете в качестве примера, немного перестроен и / или служит более сложным целям, чем исходный код.

Я бы спросил в комментарии (недостаточно репутации), но можете ли вы также опубликовать NavGraph? Настроить контроллер навигации очень просто, если у вас правильно настроены NavGraph, меню и макет, но все это должно быть тщательно синхронизировано, а документация в этой области на самом деле не так хороша.

Пока вы не ответите с помощью navgraph я постараюсь помочь с решением:

Сначала измените свой

<androidx.fragment.app.FragmentContainerView

на

<fragment

, но добавьте следующие теги

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

затем убедитесь, что идентификаторы в вашем меню. xml соответствуют тому же идентификатору для пункта назначения, который вы хотите, как описано в вашем Nav Graph, например, у вас должно быть 3 пункта назначения с именем

android:id="@+id/navigation_1"

android:id="@+id/homeFragment"

android:id="@+id/navigation_3"

Вам не нужно настраивать никаких действий.

После этого просто выполните это в действии с BottomNavigationView:

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

После этого навигация должна работать правильно, без необходимости настраивать все в этом методе «setupBottomNavigationView ()» или в расширениях навигации, которые вы также разместили.

Таким образом, навигация обрабатывает все, начиная с стек, чтобы выделить нужную кнопку в BottomNavigationView.

...