Android Jetpack组件之Navigation

news2024/11/26 18:38:15

无论您如何构建应用界面(使用 Fragment、Activity 还是其他组件),都要为应用设计屏幕间导航的功能。那么可以试试Naviagtion。

文章目录

    • 基本使用
      • 创建项目
      • 观察使用代码
        • 底部导航控件
        • fragment代码
        • 底部navigationView与顶部fragment的联动
    • Navigation视图编辑器
      • 页面跳转
      • 跳转传参数
    • Navigation源码分析
      • 配置文件是如何被加载的
      • fragment的切换方式
      • 修改NavFragmentHost切换fragment的方式

基本使用

创建项目

在AndroidStudio可以直接创建
在这里插入图片描述
运行起来这样的效果:
在这里插入图片描述

观察使用代码

这是目录结构
在这里插入图片描述
一个主的Activity,三个Fragment
主Activity的布局代码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>

底部导航控件

这一部分:就是我们的底部切换的Tab,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”

这里有一个menu的属性,所显示的内容,就是由这里控制的。比如上图我们看到Home、Dashboard、Notifications
我们打开这个文件看看:

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

到这里的话,显示出底部的几个tab,是可以的。但是怎么样跟顶部的Fragment进行联动呢?

fragment代码

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

这里竟然是一个fragment,但是上方是怎么显示多个fragment并且切换的呢?
其实这个fragment是管理的fragment,也就是一个容器。
我们通过配置文件,把其他的配置文件填到这个容器里,仔细观察会发现

app:navGraph=“@navigation/mobile_navigation”

这又是一个配置文件,这个配置文件,就是配置这个hostFragment要管理的fragment,代码如下:

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

里面有三个Fragment,拿第一个来讲

   <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属性指向我们的fragment,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处理。控制被管理的fragment/activity/dailog的返回。
那么它们两是怎么联动起来的呢?
点击底部的tab,就会切换顶部的fragment

底部navigationView与顶部fragment的联动

我们面向对象的思想,最简单的思维就是监听BottomNavigationView的选中变化,然后切换顶部的fragment。
这些都是Android的官方套件,所以google也希望我们一起使用。
既然希望我们组合使用的话,所以他们把这个切换的动作已经实现了,简单地关联上就行了。

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

这样子,就关联起来了。底部的tab切换,上方的fragment则会切换了。

Navigation视图编辑器

打开我们的/res/navigation/mobile_navigation.xml文件
然后切换到设计视图界面,右上角
在这里插入图片描述
在这里插入图片描述
从左侧视图可以看出结构,以及入口
前面我们说了,我们的navigation不仅可以管理fragment,也可以有activity,fragmentDialog
我们创建一个登录的activity吧
在这里插入图片描述
记得要在清单文件里注册一下。
然后点击视图左上角的添加按钮
在这里插入图片描述
添加完以后:
在这里插入图片描述
对应的xml文件也会多了一个activity
那又如何进行跳转呢?

页面跳转

比如说,我们要在首页-跳转-登录的activity上

我们选中home fragment,然后点击添加action的图标
在这里插入图片描述
还可以添加动画,如图等
添加完以后
在这里插入图片描述

对应的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,可以使用directions,也可以传参。
跳转效果:
在这里插入图片描述

跳转传参数

比如说我们的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)

Navigation源码分析

配置文件是如何被加载的

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里头。

fragment的切换方式

前面我们是不是有切换fragment的动作呀?我们点击底部的tab,会切换上方的fragment。
这就是我们的入口了。

       //找到底部的导航控件
        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);

这个navigator是什么类型,还得看拿出来的是什么东西。我们这里研究的是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的方式进行切换的
在这里插入图片描述
所以,当我们切换fragment的时候,生命周期的变化会频繁销毁和创建。

修改NavFragmentHost切换fragment的方式

从前面的源码分析,我们知道了,切换是由FragmentNavigator去负责的。
如果我们直接继承这个类,去覆写navigate的方法,某些私有的属性用不了。
那怎么办呢?
既然都是继承自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
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/615931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python基础知识(八):字典

目录 1. 字典及其创建2. 字典元素的获取3. 字典元素的删除与清空4. 字典元素的增加5. 字典元素的修改6. 字典的键、值和键值对的获取7. 字典元素的遍历8. 字典的特点9. 字典生成式 1. 字典及其创建 在Python中&#xff0c;字典是一系列键—值对。每个键都与一个值相关联&#…

【深度学习】跌倒识别(带数据集和源码)从0到1

文章目录 前言1. 数据集1.1 数据初探1.2 数据处理1.2 训练前验证图片1.4 翻车教训和进阶知识 2. 训练3.效果展示 前言 又要到做跌倒识别了。 主流方案有两种&#xff1a; 1.基于关键点的识别&#xff0c;然后做业务判断&#xff0c;判断跌倒&#xff0c;用openpose可以做到。…

node,yarn ,cnpm安装

1.解决npm warn config global --global, --local are deprecated. use --locationglobal instead 在安装配置node时&#xff0c;cmd检查node没问题 但是检查npm就出现**npm warn config global –global, –local are deprecated. use –locationglobal instead.**的报警 问…

ClickHouse 使用介绍

1 CK 介绍 1.1 clickhouse简介 ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的用于在线分析处理查询&#xff08;OLAP :Online Analytical Processing&#xff09;MPP架构的列式存储数据库&#xff08;DBMS&#xff1a;Database Management System&#xff09;&#xff0c;能够…

SpringBoot整合第三方技术 -- SpringBoot快速入门保姆级教程(三)

文章目录 前言三、SpringBoot整合第三方技术1.SpringBoot整合junit2.Spring整合junit入门案例3.SpringBoot整合Mybatis4.SpringBoot整合Mybatis入门案例5.SpringBoot整合Mybatis更换默认数据源 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博…

2023陕西省赛-部分Misc复现

目录 管道 zsteg使用&#xff1a; 可是雪啊飘进双眼 snow加密&#xff1a; 管道 用工具zsteg查看各通道的lsb&#xff0c;使用指令&#xff0c;拿到flag zsteg -a 文件 zsteg使用&#xff1a; 介绍&#xff1a;用来检测PNG和BMP中隐藏数据的工具&#xff0c;可以快速提取隐…

初学SpringBoot—01

注明&#xff1a;本人学习来源SpringForAll社区资料 我看到的资料中的springboot和idea版本较低&#xff0c;所以我这里在我学习后&#xff0c;理解并掌握的基础上将我用到的IDEA2022版本和Maven3.8.6做一个记录 学习SpringBoot前最好学习一下Maven和SSM框架 一、新建SpringBo…

RocketMQ详解+实战+常见面试题

目录 RocketMQ是什么&#xff1f; RocketMQ的主要特点包括 RocketMQ 的消息模型 队列模型/点对点 发布/订阅模型 RocketMQ 的消息模型 RocketMQ中重要的组件 RocketMQ消费模式 RoctetMQ基本架构 springboot实战讲解 基本样例 单向发送 异步发送 同步发送 推模式…

chatgpt赋能python:Python多行换行:如何在Python代码中正确处理多行

Python多行换行&#xff1a;如何在Python代码中正确处理多行 在Python中&#xff0c;代码往往需要按照一定的格式进行排版&#xff0c;以使代码更易于阅读和维护。在编写长代码时经常会遇到一个问题&#xff1a;如何正确处理多行&#xff1f;本文将介绍Python中多行换行的方法…

嵌入式系统中SPI 子系统基本原理实现

1、SPI hardware SPI&#xff1a;Serial Perripheral Interface&#xff0c;串行外围设备接口&#xff0c;由 Motorola 公司提出&#xff0c;是一种高速、全双工、同步通信总线。SPI 以主从方式工作&#xff0c;通常是有一个主设备和一个或多个从设备&#xff0c;无应答机制。…

Mendix 创客访谈录 | 低代码开启新世界

本期创客 李垚 西门子汽车事业部IOT专家 任职于西门子中国DI事业部&#xff0c;主要负责汽车行业西门子产品业务拓展与支持&#xff0c;服务于中国区汽车生产企业。本人主要负责Siplant、边缘计算&#xff0c;低代码技术等产品的售前、售后技术支持和故障排除, 解决客户在产品…

VSCode+GDB+Qemu调试ARM64 linux内核

俗话说&#xff0c;工欲善其事 必先利其器。linux kernel是一个非常复杂的系统&#xff0c;初学者会很难入门。 如果有一个方便的调试环境&#xff0c;学习效率至少能有5-10倍的提升。 为了学习linux内核&#xff0c;通常有这两个需要 可以摆脱硬件&#xff0c;方便的编译和…

React新版扩展特性

目录 Hooks 三个常用的Hook State Hook Effect Hook Ref Hook Context Router 6 声明式路由 编程式路由导航 Hooks (1) Hook是react 18.8.0版本新增的特性/语法 (2) 可以让我们在函数式组件中使用state以及其他的react特性 三个常用的Hook (1) State Hook: React.useSt…

Redis进阶:缓存穿透|缓存击穿|缓存雪崩问题

Redis应用问题 1. 缓存穿透问题1.1 问题描述1.2 解决方案方法一&#xff1a;空值缓存方法二&#xff1a;设置可访问的名单&#xff08;白名单&#xff09;方法三&#xff1a;采用布隆过滤器方法四&#xff1a;进行实时监控 2. 缓存击穿问题2.1 问题描述2.2 解决方案方法一&…

在金融行业工作有哪些必须熟练掌握的 Excel 公式?

金融中需要掌握的 Excel 公式有很多&#xff0c;以下是一些重要的公式和技巧&#xff1a; 1. SUM、AVERAGE、MIN、MAX 等基本统计函数 2. IF、AND、OR 等逻辑函数 3. VLOOKUP、HLOOKUP、INDEX、MATCH 等查找函数 4. PMT、PV、FV、NPV、IRR 等财务函数 5. CONCATENATE、LEF…

nw.js桌面软件开发系列 第0.1节 HTML5和桌面软件开发的碰撞

第0.1节 HTML5和桌面软件开发的碰撞 当我们谈论桌面软件开发技术的时候&#xff0c;你会想到什么&#xff1f;如果不对技术本身进行更为深入的探讨&#xff0c;在我的世界里&#xff0c;有这么多技术概念可以被罗列出来&#xff08;请原谅我本质上是一个Windows程序员的事实&a…

HarmoneyOS--从简单页面跳转开始2

此处对上个页面跳转适当增加内容&#xff0c;主要为List组件和grid组件的使用&#xff0c;适当熟悉最基本的容器Row和Column的使用 Login.ets // ts-nocheck import router from ohos.router; Entry Component struct TextDemo {State name:string State password:string bui…

vue2 + antd 封装动态表单组件(三)

最近有小伙伴在问动态表单如何再次封装&#xff0c;比如配合模态框或者抽屉封装多一层&#xff0c;这样可以大大提高开发效率&#xff0c;结合之前的写的 vue2 antd 封装动态表单组件&#xff08;一&#xff09; vue2 antd 封装动态表单组件&#xff08;二&#xff09; 做了…

【技术选型】时序数据库选型

文章目录 1、前言2、概述2.1 时序数据库的定义2.2 时序数据库的概念2.3 时序数据库的趋势 3、时序数据库对比3.1 influxdb3.2 Prometheus3.3 TDengine3.4 DolphinDB 4、选型结论 1、前言 时序数据治理是数据治理领域核心、打通IT与OT域数据链路&#xff0c;是工业物联网基石、…

Macos中Automator自动操作实现文本加解密、Json格式化、字数统计等操作

文章目录 一、说明打开Automator效果 二、文本加密三、文本解密四、Json格式化五、字数统计 一、说明 在 Automator 的工作流程中&#xff0c;动作是按照从上到下的顺序执行的&#xff0c;每一个动作的输出默认会成为下一个动作的输入。 打开Automator Command 空格 &#…