可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Android Jetpack Navigation, BottomNavigationView with auto fragment back stack on back button click?
What I wanted, after choosing multiple tabs one after another by user and user click on back button app must redirect to the last page he/she opened.
I achieved the same using Android ViewPager, by saving the currently selected item in an ArrayList. Is there any auto back stack after Android Jetpack Navigation Release? I want to achieve it using navigation graph
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"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<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="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
also added
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
I got one answer from Levi Moreira
, as follows
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
But by doing this only happening is that last opened fragment's instance creating again.
Providing proper Back Navigation for BottomNavigationView
回答1:
You don't really need a ViewPager
to work with BottomNavigation
and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.
The basic concept is this, you have the main activity that will host the BottomNavigationView
and that is the Navigation host for your navigation graph, this is how the xml for it look like:
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"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<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="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
The navigation Menu (tabs menu) for the BottomNavigationView
looks like this:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
All of this is just the BottomNavigationView
setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml
file:
This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment.
There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView
:
You need to add in your onCreate:
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.
To get the same beahvior you have when you use youtube, just add this:
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.
回答2:
You have to set host navigation like below xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<fragment
android:id="@+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/color_state_list"
app:itemTextColor="@drawable/color_state_list"
app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>
Setup With Navigation Controller :
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
menu_bottom_navigation.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/tab1" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab1" />
<item
android:id="@id/tab2" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab2" />
<item
android:id="@id/tab3" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab3" />
</menu>
nav_graph.xml :
<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/tab1">
<fragment
android:id="@+id/tab1"
android:name="com.navigationsample.Tab1Fragment"
android:label="@string/tab1"
tools:layout="@layout/fragment_tab_1" />
<fragment
android:id="@+id/tab2"
android:name="com.navigationsample.Tab2Fragment"
android:label="@string/tab2"
tools:layout="@layout/fragment_tab_2"/>
<fragment
android:id="@+id/tab3"
android:name="com.simform.navigationsample.Tab3Fragment"
android:label="@string/tab3"
tools:layout="@layout/fragment_tab_3"/>
</navigation>
By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.
You can handle back action using popUpTo
property in action
tag.
回答3:
You can have a viewpager setup with bottom navigation view. Each fragment in the viewpager will be a container fragment, it will have child fragments with its own backstack. You can maintain backstack for each tab in viewpager this way
回答4:
I have made an app like this (still not published on PlayStore) that has the same navigation, maybe its implementation is different from what Google does in their apps, but the functionality is the same.
the structure involves I have Main Activity that I switch the content of it by showing/hiding fragments using:
public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {
// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
return;
}
// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
fragmentTransaction.hide(currentShowingFragment);
}
// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
// Since its already added before we just set it as primary navigation and show it again
fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
fragmentTransaction.show(alreadyAddedFragment);
} else {
// We add the new fragment and then show it
fragmentTransaction.add(containerId, fragment, tag);
fragmentTransaction.show(fragment);
// We set it as the primary navigation to support back stack and back navigation
fragmentTransaction.setPrimaryNavigationFragment(fragment);
}
fragmentTransaction.commit();
}
回答5:
If you have a bottomNavigationView
with 3 items corresponding to 3 Fragment
s: FragmentA
, FragmentB
and FragmentC
where FragmentA
is the startDestination
in your navigation graph, then when you're on FragmentB
or FragmentC
and you click back, you're going to be redirected to FragmentA
, that's the behavior recommended by Google and that's implemented by default.
If however you wish to alter this behavior, you'll need to either use a ViewPager
as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.