Ответ 1
Реализация по умолчанию BottomNavigationView
с компонентом Arch навигации не сработала для меня. При нажатии на вкладки они начинаются с начала согласно графику навигации.
Мне нужно иметь 5 вкладок в нижней части экрана и иметь отдельный backstack для каждой из вкладок. Это означает, что при переключении между вкладками вы всегда будете возвращаться в то же состояние, в котором оно находилось до выхода (как в Instagram).
Мой подход заключается в следующем:
- Поместите
ViewPager
иBottomNavigationView
вactivity_main.xml
- Установите
OnNavigationItemSelectedListener
вBottomNavigationView
вMainActivity.kt
- Создайте отдельные фрагменты Контейнера для каждой вкладки (они будут отправной точкой каждой вкладки)
-
NavHostFragment
в xml фрагментов контейнера. - Реализуйте необходимый код для Компонента Арки Навигации в каждом из фрагментов Контейнера.
- Создать график для каждой из вкладок
Примечание: каждый из графиков может взаимодействовать друг с другом.
Важным моментом здесь является то, что мы помещаем панель инструментов не в активность, а во фрагмент контейнера. Затем мы вызываем setupWithNavController()
на самой панели инструментов, не устанавливая его как supportActionBar
. Таким образом, заголовки панели инструментов будут автоматически обновляться, а кнопка " Назад/Вверх" будет управляться автоматически.
Результаты:
- ViewPager хранит состояния каждой вкладки.
- Не беспокоиться о фрагментных транзакциях.
-
SafeArgs
иDeepLinking
работают как положено. - У нас есть полный контроль над
BottomNavigationManager
иViewPager
(то есть мы можем реализовать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>
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
[email protected] true
}
R.id.navigation_tab_2 -> {
main_view_pager.currentItem = 1
[email protected] 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 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>
Здесь начальным целевым фрагментом является любой фрагмент, который вы хотите отобразить в качестве первого экрана на вкладке.