Regardless of how you build your app's interface (using Fragments, Activities, or other components), design your app for navigation between screens. Then you can try Naviagtion.
Article directory
Basic usage
Create project
You can create it directly in Android Studio
and run it like this:
Observe usage code
This is the directory structure.
One main Activity, three Fragment
main Activity layout codes main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
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/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Bottom navigation control
This part: It is our bottom switching Tab, BottomNavigationView, and bottom navigation control
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
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/bottom_nav_menu" />
Where is the content it displays controlled?
app:menu=“@menu/bottom_nav_menu”
There is a menu attribute here, and the displayed content is controlled here. For example, in the picture above we see Home, Dashboard, and Notifications.
Let’s open this file and take a look:
<?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_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
Once you get here, it's okay to display a few tabs at the bottom. But how to link it with the Fragment at the top?
fragment code
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
There is actually a fragment here, but how do you display multiple fragments and switch between them?
In fact, this fragment is a managed fragment, which is a container.
We fill other configuration files into this container through the configuration file. If you observe carefully, you will find that
app:navGraph=”@navigation/mobile_navigation”
This is another configuration file. This configuration file is to configure the fragment to be managed by this hostFragment. The code is as follows:
<?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/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="com.example.navigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_dashboard"
android:name="com.example.navigationdemo.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/navigation_notifications"
android:name="com.example.navigationdemo.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
</navigation>
There are three Fragments in it, let’s take the first one
<fragment
android:id="@+id/navigation_home"
android:name="com.sunofbeaches.navigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
The id and name attributes point to our fragment, label is the label, and layout is the layout.
app:startDestination="@+id/navigation_home"
Indicates the page to be displayed by default, which points to navigation_home, so when we start it, the HomeFragment is displayed by default.
Go back and look at the code for the homepage layout
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
here is one
app:defaultNavHost="true"
This actually transfers the return key event to NavHostFragment for processing. Controls the return of managed fragment/activity/dailog.
So how do the two of them connect?
Clicking the tab at the bottom will switch the fragment at the top
Linkage between bottom navigationView and top fragment
Our object-oriented thinking, the simplest thinking is to monitor the selection change of BottomNavigationView, and then switch the top fragment.
These are official packages for Android, so Google also wants us to use them together.
Since they want us to use it in combination, they have already implemented this switching action and simply associate it.
//找到底部的导航控件
val navView: BottomNavigationView = findViewById(R.id.nav_view)
//找到hostFragment
val navController = findNavController(R.id.nav_host_fragment)
//关联起来
navView.setupWithNavController(navController)
In this way, they are related. The tabs at the bottom are switched, and the fragments above are switched.
Navigation view editor
Open our /res/navigation/mobile_navigation.xml file
and then switch to the design view interface. In the upper right corner,
you can see the structure from the left view, and
as we said before the entrance, our navigation can not only manage fragments, but also have activities.
Let's create a login activity for fragmentDialog
. Remember to register it in the manifest file.
Then click the add button in the upper left corner of the view
. After adding:
the corresponding xml file will also have an additional activity
. So how to jump?
Page jump
For example, we want to go to the homepage-jump-login activity
We select the home fragment, and then click the icon to add action.
You can also add animations, as shown in the figure, etc.
After adding
The corresponding xml file will have:
<fragment
android:id="@+id/navigation_home"
android:name="com.sunofbeaches.navigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/to_login_activity"
app:destination="@id/loginActivity"
app:enterAnim="@anim/fragment_fade_enter"
app:exitAnim="@anim/fragment_close_exit" />
</fragment>
At this point, it's not working yet. We just declared the jump relationship. If we really want to jump, we have to add simple code.
Add a jump button to our HomeFragment
val root = inflater.inflate(R.layout.fragment_home, container, false)
val loginBtn = root.findViewById<Button>(R.id.toLoginPage)
loginBtn.setOnClickListener {
Navigation.findNavController(root).navigate(R.id.to_login_activity)
}
There is mainly a Navigation.findNavController(root).navigate method.
This method is to jump to a certain place. There are multiple overloaded methods:
you can use deepLink, you can use directions, and you can also pass parameters.
Jump effect:
Jump to pass parameters
For example, for our LoginActivity, you can pass it a phoneNumber.
When jumping, we carry parameters.
loginBtn.setOnClickListener {
val userInfo = Bundle()
userInfo.putString("phoneNumber", "15353979727")
Navigation.findNavController(root).navigate(R.id.to_login_activity, userInfo)
}
So in LoginActivity, how do we get it?
val phoneNum = intent.extras!!.get("phoneNumber")
println(phoneNum)
Navigation source code analysis
How configuration files are loaded
app:navGraph=”@navigation/mobile_navigation”
We see the onCreate method of NavHostFragment
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is called
mNavController.enableOnBackPressed(
mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
mIsPrimaryBeforeOnCreate = null;
mNavController.setViewModelStore(getViewModelStore());
onCreateNavController(mNavController);
Bundle navState = null;
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
}
if (navState != null) {
// Navigation controller state overrides arguments
mNavController.restoreState(navState);
}
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
final Bundle args = getArguments();
final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
final Bundle startDestinationArgs = args != null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
// We purposefully run this last as this will trigger the onCreate() of
// child fragments, which may be relying on having the NavController already
// created and having its state restored by that point.
super.onCreate(savedInstanceState);
}
we can find
mNavController.setGraph(mGraphId);
In this code, if mGraphId is not 0, it will be processed directly. If mGraphId is 0, it will be obtained through other methods.
The method called through the resource ID will go here.
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
look at this
getNavInflater().inflate(graphResId)
The call of this method is to convert xml into bean class
/**
* Inflate a NavGraph from the given XML resource id.
*
* @param graphResId
* @return
*/
@SuppressLint("ResourceType")
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
Resources res = mContext.getResources();
XmlResourceParser parser = res.getXml(graphResId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
String rootElement = parser.getName();
NavDestination destination = inflate(res, parser, attrs, graphResId);
if (!(destination instanceof NavGraph)) {
throw new IllegalArgumentException("Root element <" + rootElement + ">"
+ " did not inflate into a NavGraph");
}
return (NavGraph) destination;
} catch (Exception e) {
throw new RuntimeException("Exception inflating "
+ res.getResourceName(graphResId) + " line "
+ parser.getLineNumber(), e);
} finally {
parser.close();
}
}
That is, the object NavGraph will be returned. Who is this given to?
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
if (mGraph != null) {
// Pop everything from the old graph off the back stack
popBackStackInternal(mGraph.getId(), true);
}
mGraph = graph;
onGraphCreated(startDestinationArgs);
}
Just inside NavController.
How to switch fragments
Did we switch fragments earlier? When we click the tab at the bottom, the fragment above will be switched.
This is our entrance.
//找到底部的导航控件
val navView: BottomNavigationView = findViewById(R.id.nav_view)
//找到hostFragment
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
This is an extension function
fun BottomNavigationView.setupWithNavController(navController: NavController) {
NavigationUI.setupWithNavController(this, navController)
}
Follow up
public static void setupWithNavController(
@NonNull final BottomNavigationView bottomNavigationView,
@NonNull final NavController navController) {
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return onNavDestinationSelected(item, navController);
}
});
final WeakReference<BottomNavigationView> weakReference =
new WeakReference<>(bottomNavigationView);
navController.addOnDestinationChangedListener(
new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
BottomNavigationView view = weakReference.get();
if (view == null) {
navController.removeOnDestinationChangedListener(this);
return;
}
Menu menu = view.getMenu();
for (int h = 0, size = menu.size(); h < size; h++) {
MenuItem item = menu.getItem(h);
if (matchDestination(destination, item.getItemId())) {
item.setChecked(true);
}
}
}
});
}
Monitor the selection of NavigationItem in bottomNavigationView and return the onNavDestinationSelected method
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true);
if (navController.getCurrentDestination().getParent().findNode(item.getItemId())
instanceof ActivityNavigator.Destination) {
builder.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
} else {
builder.setEnterAnim(R.animator.nav_default_enter_anim)
.setExitAnim(R.animator.nav_default_exit_anim)
.setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
.setPopExitAnim(R.animator.nav_default_pop_exit_anim);
}
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
NavOptions options = builder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.getItemId(), null, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
There is such a line
navController.navigate(item.getItemId(), null, options);
Keep walking until you get here
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
The type of navigator depends on what is taken out. What we are studying here is Fragment, so what we take out should be
So when calling navigate, what we call is actually the navigate method in FragmentNavitor, which is this
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
As you can see, the replacement method is used to switch
. Therefore, when we switch fragments, life cycle changes will be frequently destroyed and created.
Modify the way NavFragmentHost switches fragments
From the previous source code analysis, we know that FragmentNavigator is responsible for switching.
If we directly inherit this class and override the navigation method, some private properties will not be used.
then what should we do?
Since they are all inherited from Navigator, shouldn't we also write our own FragmentNavigator?
Copy the other code of FragmentNavigator and then modify it.
After modification, it looks like this
/**
* {@inheritDoc}
* <p>
* This method should always call
* {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}
* so that the Fragment associated with the new destination can be retrieved with
* {@link FragmentManager#getPrimaryNavigationFragment()}.
* <p>
* Note that the default implementation commits the new Fragment
* asynchronously, so the new Fragment is not instantly available
* after this call completes.
*/
@SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
@Nullable
@Override
public NavDestination navigate(@NonNull FragmentNavigator.Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
String tag = className.substring(className.lastIndexOf(".") + 1);
Fragment frag = mFragmentManager.findFragmentByTag(tag);
//判断是否有添加,如果没有添加,则添加,并且显示
//如果已经添加了,直接显示
if (frag == null) {
System.out.println(" create new fragment..." + tag);
frag = instantiateFragment(mContext, mFragmentManager,
className, args);
}
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
//隐藏上一个显示的内容
for (Fragment fragment : mFragmentManager.getFragments()) {
System.out.println("hide fragment -- > " + fragment.getClass().getName());
ft.hide(fragment);
}
if (!frag.isAdded()) {
System.out.println("add fragment ... " + tag);
ft.add(mContainerId, frag, tag);
}
ft.show(frag);
//ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof HideSwitchFragmentNavigator.Extras) {
HideSwitchFragmentNavigator.Extras extras = (HideSwitchFragmentNavigator.Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
Here is the main code. Replace replace with hide
. Then, we write a class to inherit NavHostFragment and override the method inside.
NavHostFragment creates FragmentNavigator in the following method.
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
So we override the createFragmentNavigator method
class CustomNavHostFragment : NavHostFragment() {
/**
* Create the FragmentNavigator that this NavHostFragment will use. By default, this uses
* [FragmentNavigator], which replaces the entire contents of the NavHostFragment.
*
*
* This is only called once in [.onCreate] and should not be called directly by
* subclasses.
* @return a new instance of a FragmentNavigator
*/
@Deprecated("Use {@link #onCreateNavController(NavController)}")
override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination?> {
return HideSwitchFragmentNavigator(
requireContext(), childFragmentManager,
getContainerId()
)
}
private fun getContainerId(): Int {
val id = id
return if (id != 0 && id != View.NO_ID) {
id
} else R.id.nav_host_fragment_container
// Fallback to using our own ID if this Fragment wasn't added via
// add(containerViewId, Fragment)
}
}
Change it to this
<fragment
android:id="@+id/nav_host_fragment"
android:name="com.example.navigationdemo.view.CustomNavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//找到底部的导航控件
val navView: BottomNavigationView = findViewById(R.id.nav_view)
//找到hostFragment
val navController = findNavController(R.id.nav_host_fragment)
navView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.navigation_home -> {
navController.navigate(R.id.navigation_home)
}
R.id.navigation_dashboard -> {
navController.navigate(R.id.navigation_dashboard)
}
R.id.navigation_notifications -> {
navController.navigate(R.id.navigation_notifications)
}
}
true
}
}