У меня есть приложение с BottomNavigationView
и ViewPager
. Как это можно реализовать, используя новый "Компонент архитектуры навигации"?
Какова лучшая практика?
Спасибо
У меня есть приложение с BottomNavigationView
и ViewPager
. Как это можно реализовать, используя новый "Компонент архитектуры навигации"?
Какова лучшая практика?
Спасибо
Реализация по умолчанию BottomNavigationView
с компонентом Arch навигации не сработала для меня. При нажатии на вкладки они начинаются с начала согласно графику навигации.
Мне нужно иметь 5 вкладок в нижней части экрана и иметь отдельный backstack для каждой из вкладок. Это означает, что при переключении между вкладками вы всегда будете возвращаться в то же состояние, в котором оно находилось до выхода (как в Instagram).
Мой подход заключается в следующем:
ViewPager
и BottomNavigationView
в activity_main.xml
OnNavigationItemSelectedListener
в BottomNavigationView
в MainActivity.kt
NavHostFragment
в xml фрагментов контейнера.Примечание: каждый из графиков может взаимодействовать друг с другом.
Важным моментом здесь является то, что мы помещаем панель инструментов не в активность, а во фрагмент контейнера. Затем мы вызываем setupWithNavController()
на самой панели инструментов, не устанавливая его как supportActionBar
. Таким образом, заголовки панели инструментов будут автоматически обновляться, а кнопка " Назад/Вверх" будет управляться автоматически.
Результаты:
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>
Здесь начальным целевым фрагментом является любой фрагмент, который вы хотите отобразить в качестве первого экрана на вкладке.
Решение для меня состояло в том, чтобы оставить фрагмент в ViewPager вне навигации и напрямую установить действия на фрагменте страниц, как если бы эти страницы были хостом. Чтобы объяснить это лучше:
Скажем, вы во Фрагменте А с ViewPager Фрагмента B И вы пытаетесь перейти от B к C
Во фрагменте B используйте класс ADirections и действие от A до C. findNavHost(). NavigateTo (ADirections.ActionFromAtoC)
Мы можем реализовать с помощью нижнего компонента навигации и 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>
Каждый идентификатор фрагмента навигации и идентификатор нижнего элемента меню навигации должны быть одинаковыми. Например здесь
<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
<?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"
После этого реализуйте навигационный график и компонент bottomNavigation в MainActivity.java.
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
NavController navController = Navigation.findNavController(this, R.id.mainNavigationFragment);
NavigationUI.setupWithNavController(navigation, navController);
Ура !!!
Я написал соответствующую статью по этому поводу, касающуюся пейджеров представлений, уделяя особое внимание фрагментам Master-Detail, которые имеют вкладки, но та же логика применима к обычным представителям ViewPager. Код находится здесь.
У меня есть MainFragment, который содержит фрагмент A, фрагмент B и фрагмент C внутри viewPager.
И я хочу открыть фрагмент D из фрагмента B (размещенный в viewPager внутри MainFragment).
Поэтому я создал действие из MainFragment во фрагмент D и вызвал из фрагмента B
val direction = FragmentMainDirections.actionFragmentMainToFragmentD()
findNavController().navigate(direction)
Работает.
В дополнение к ответу Марата, чтобы стек задних элементов работал с кнопкой "Назад" в каждом фрагменте, необходимо добавить это к фрагменту контейнера onViewCreated:
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!navHostFragment.navController.popBackStack()) {
isEnabled = false
activity?.onBackPressed()
}
}
}
activity?.onBackPressedDispatcher?.addCallback(this, callback)
Я реализовал Android Arch Navigations с помощью ViewPager. пожалуйста, посмотрите. Любые улучшения приветствуются. Давайте учиться вместе.
https://github.com/Maqsood007/AndroidJetpack/tree/master/ArchNavViewPagerImpl