Android Jetpack コンポーネントのナビゲーション

アプリのインターフェイスを構築する方法 (フラグメント、アクティビティ、またはその他のコンポーネントを使用) に関係なく、画面間のナビゲーション用にアプリを設計します。次に、Naviagtion を試してみましょう。

基本的な使い方

プロジェクトの作成

Android Studio で直接作成し
ここに画像の説明を挿入します
、次のように実行できます。
ここに画像の説明を挿入します

使用法コードを遵守する

これがディレクトリ構造です
ここに画像の説明を挿入します
1つのメインアクティビティ、3つのフラグメント
メインアクティビティのレイアウトコード 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>
ボトムナビゲーションコントロール

この部分: 下部の切り替えタブ、BottomNavigationView、および下部のナビゲーション コントロールです。

  <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" />

表示されるコンテンツはどこで制御されますか?

app:menu=“@menu/bottom_nav_menu”

ここにはメニュー属性があり、表示内容はここで制御されます。たとえば、上の図にはホーム、ダッシュボード、通知が表示されています。
このファイルを開いて見てみましょう。

<?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>

ここまで来たら、下部にいくつかのタブを表示しても問題ありません。しかし、それを上部のフラグメントとリンクするにはどうすればよいでしょうか?

フラグメントコード
<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" />

実際にはここにフラグメントがありますが、複数のフラグメントを表示し、それらを切り替えるにはどうすればよいですか?
実際、このフラグメントは管理対象フラグメント、つまりコンテナです。
構成ファイルを介して他の構成ファイルをこのコンテナに埋め込んでいきます。

app:navGraph=”@navigation/mobile_navigation”

これは別の設定ファイルです。この設定ファイルは、この hostFragment によって管理されるフラグメントを設定するためのものです。コードは次のとおりです。

<?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>

3 つのフラグメントがあります。最初のフラグメントを取りましょう

   <fragment
        android:id="@+id/navigation_home"
        android:name="com.sunofbeaches.navigationdemo.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

id 属性と name 属性はフラグメントを指し、label はラベル、layout はレイアウトを指します。

app:startDestination="@+id/navigation_home"

デフォルトで表示されるページを示します。これは、navigation_home を指します。そのため、起動すると、デフォルトで HomeFragment が表示されます。

戻ってホームページのレイアウトのコードを見てください。

<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" />

ここに一つあります

 app:defaultNavHost="true"

これにより、実際にはリターン キー イベントが処理のために NavHostFragment に転送されます。管理対象フラグメント/アクティビティ/ダイログの戻りを制御します。
それで、二人はどのように結びつくのでしょうか?
下部のタブをクリックすると上部のフラグメントが切り替わります

下部のNavigationViewと上部のフラグメント間のリンク

私たちのオブジェクト指向の考え方、最も単純な考え方は、BottomNavigationView の選択の変化を監視し、次に上部のフラグメントを切り替えることです。
これらは Android の公式パッケージなので、Google も併用してほしいと考えています。
組み合わせて使いたいので、この切り替え動作はすでに実装されており、それを関連付けるだけです。

        //找到底部的导航控件
        val navView: BottomNavigationView = findViewById(R.id.nav_view)
        //找到hostFragment
        val navController = findNavController(R.id.nav_host_fragment)
        //关联起来
        navView.setupWithNavController(navController)

このように、それらは関連しています。下部のタブが切り替わり、上のフラグメントが切り替わります。

ナビゲーションビューエディター

/res/navigation/mobile_navigation.xml ファイルを開き
、デザイン ビュー インターフェイスに切り替えます。右上隅では、
ここに画像の説明を挿入します
ここに画像の説明を挿入します
左側のビューから構造を確認できます。
入り口の前で述べたように、ナビゲーションは管理するだけでなく、フラグメントだけでなくアクティビティもあります。fragmentDialog
のログイン アクティビティを作成しましょう
ここに画像の説明を挿入します
。マニフェスト ファイルに忘れずに登録してください。
次に、ビューの左上隅にある追加ボタンをクリックします
ここに画像の説明を挿入します
。追加後:
ここに画像の説明を挿入します
対応する XML ファイルにも追加のアクティビティが追加されます
。それでは、どうやってジャンプするのでしょうか?

ページジャンプ

たとえば、homepage-jump-login アクティビティに移動したいとします。

ホームフラグメントを選択し、アイコンをクリックしてアクションを追加します。
ここに画像の説明を挿入します
図のようにアニメーションを追加することもできます

ここに画像の説明を挿入します

対応する XML ファイルには次の内容が含まれます。

<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>

この時点では、まだ機能していません。ジャンプ関係を宣言しただけです。本当にジャンプしたい場合は、簡単なコードを追加する必要があります。
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)
        }

主に Navigation.findNavController(root).navigate メソッドがあります。
このメソッドは特定の場所にジャンプするためのもので、多重定義されたメソッドが複数あり、
ここに画像の説明を挿入します
deepLink を使用したり、ルートを使用したり、パラメーターを渡すこともできます。
ジャンプ効果:
ここに画像の説明を挿入します

ジャンプしてパラメータを渡す

たとえば、LoginActivity の場合、phoneNumber を渡すことができ、
ジャンプするときにパラメータを渡します。

loginBtn.setOnClickListener {
    
    
            val userInfo = Bundle()
            userInfo.putString("phoneNumber", "15353979727")
            Navigation.findNavController(root).navigate(R.id.to_login_activity, userInfo)
        }

では、LoginActivity では、どうやってそれを取得するのでしょうか?

  val phoneNum = intent.extras!!.get("phoneNumber")
        println(phoneNum)

ナビゲーションのソースコード分析

設定ファイルのロード方法

app:navGraph=”@navigation/mobile_navigation”

NavHostFragment の onCreate メソッドが表示されます。

  @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);
    }

私たちは見つけることができます
ここに画像の説明を挿入します

mNavController.setGraph(mGraphId);

このコードでは、mGraphId が 0 以外の場合は直接処理され、mGraphId が 0 の場合は他のメソッドで取得され、
リソース ID で呼び出されるメソッドがここに入ります。

    @CallSuper
    public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
    
    
        setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
    }

これを見てください

getNavInflater().inflate(graphResId)

このメソッドの呼び出しは、XML を Bean クラスに変換することです。

 /**
     * 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();
        }
    }

つまり、NavGraph オブジェクトが返されますが、これは誰に与えられるのでしょうか?

    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);
    }

NavController 内にあります。

フラグメントを切り替える方法

フラグメントを以前に切り替えましたか? 下部のタブをクリックすると、上のフラグメントが切り替わります。
ここが私たちの入り口です。

       //找到底部的导航控件
        val navView: BottomNavigationView = findViewById(R.id.nav_view)
        //找到hostFragment
        val navController = findNavController(R.id.nav_host_fragment)
        navView.setupWithNavController(navController)

これは拡張機能です

fun BottomNavigationView.setupWithNavController(navController: NavController) {
    
    
    NavigationUI.setupWithNavController(this, navController)
}

フォローアップ

 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);
                            }
                        }
                    }
                });
    }

BottomNavigationView で NavigationItem の選択を監視し、onNavDestinationSelected メソッドを返します。

  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;
        }
    }

そんな一行あるよ

  navController.navigate(item.getItemId(), null, options);

ここに着くまで歩き続けてください

Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                node.getNavigatorName());
        Bundle finalArgs = node.addInDefaultArgs(args);
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);

ナビゲーターの種類は何を取り出すかによって異なります。ここで研究しているのは Fragment なので、取り出すものは次のようになります
ここに画像の説明を挿入します
。したがって、navigate を呼び出すときに呼び出すのは、実際には FragmentNavitor の navigate メソッドです。これは次のとおりです。

  @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;
        }
    }

ご覧のとおり、 replace メソッドを使用して を切り替えている
ここに画像の説明を挿入します
ため、フラグメントを切り替えると、ライフサイクルの変更が頻繁に破棄され、作成されます。

NavFragmentHost がフラグメントを切り替える方法を変更する

前のソース コード分析から、FragmentNavigator が切り替えを担当していることがわかります。
このクラスを直接継承してナビゲーション メソッドをオーバーライドすると、一部のプライベート プロパティが使用されなくなります。
それではどうすればいいでしょうか?
これらはすべて Navigator から継承されているため、独自の FragmentNavigator も作成すべきではないでしょうか。
FragmentNavigator の他のコードをコピーして修正します。
改造後はこんな感じ

/**
     * {@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;
        }
    }

メインコードは次のとおりです。 replace を Hide に置き換えます
。次に、NavHostFragment を継承するクラスを作成し、内部のメソッドをオーバーライドします
。NavHostFragment は、次のメソッドで FragmentNavigator を作成します。

 protected void onCreateNavController(@NonNull NavController navController) {
    
    
        navController.getNavigatorProvider().addNavigator(
                new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
        navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
    }

したがって、createFragmentNavigator メソッドをオーバーライドします。

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)
    }
}

これに変更してください

 <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
        }
    }

おすすめ

転載: blog.csdn.net/ChenYiRan123456/article/details/130855077