Navigation of Android Jetpack component

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.

Basic usage

Create project

You can create it directly in Android Studio
Insert image description here
and run it like this:
Insert image description here

Observe usage code

This is the directory structure.
Insert image description here
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,
Insert image description here
Insert image description here
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
Insert image description here
. Remember to register it in the manifest file.
Then click the add button in the upper left corner of the view
Insert image description here
. After adding:
Insert image description here
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.
Insert image description here
You can also add animations, as shown in the figure, etc.
After adding
Insert image description here

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:
Insert image description here
you can use deepLink, you can use directions, and you can also pass parameters.
Jump effect:
Insert image description here

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
Insert image description here

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
Insert image description here
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
Insert image description here
. 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
        }
    }

Guess you like

Origin blog.csdn.net/ChenYiRan123456/article/details/130855077
Recommended