• Android jetpack : Navigation 导航 路由 、 单个Activity嵌套多个Fragment的UI架构方式


    Android Navigation 如何动态的更换StartDestination &&保存Fragment状态

    Navigation(一)基础入门

    google 官网 : Navigation 导航 路由

    讨论了两年的 Navigation 保存 Fragment 状态问题居然被关闭了

    Navigation是一种导航的概念,即把Activityfragment当成一个个的目的地Destination,各目的地形成一张导航图NavGraph,由导航控制器NavController来统一调度跳转

    单个Activity嵌套多个Fragment的UI架构方式,已被大多数Android工程师所接受和采用。但是,对Fragment的管理一直是一个比较麻烦的事情,工程师需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。这其中还包括了对应用程序的App bar的管理,Fragment间的切换动画,Fragment间的参数传递,总之,使用起来不是特别友好。

    为此,Android Jetpack提供的一个名为Navigation的UI架构组件。旨在方便我们管理Fragment页面。它具体有以下优势:

    • 可视化的页面导航图,类似xcode中的StoryBoard,便于我们看清页面之间的关系
    • 通过destination和action来完成页面间的导航
    • 方便的页面切换动画
    • 页面间类型安全的参数传递
    • 通过NavigationUI类,对菜单,底部导航,抽屉菜单导航进行方便统一的管理
    • 深层链接

    注意:在Android Studio3.2及以上版本才能支持Navigation特性。
    本文所说的“页面”包括了Fragment和Activity,但主要是Fragment,因为Navigation组件的主要目地就是方便我们在一个Activity中对多个Fragment进行管理。
    首先,我们需要先对Navigation有一个大致的了解。

    Navigation Graph

    这是一种新型的XML资源文件,里面包含了应用程序所有的页面及页面之间的关系

    NavHostFragment

    这是一个特殊的布局文件,Navigation Graph中的页面通过该Fragment展示

    NavController

    这是一个Java/Kotlin对象,用于在代码中完成Navigation Graph中具体的页面切换

    当你想要切换页面的时候,使用NavController对象,告诉它你想要去Navigation Graph中的哪个页面,NavController会将相关的页面展示在NavHostFragment中。

    创建工程,引入依赖,

    android {
        compileSdkVersion 30
        buildToolsVersion "30.0.3"
    
        defaultConfig {
            applicationId "com.xq.mybottomnavigation"
            minSdkVersion 29
            targetSdkVersion 30
            versionCode 1
            versionName "1.0"
        }
    
        buildFeatures {
            viewBinding true
        }
    }
    
    dependencies {
        implementation 'androidx.appcompat:appcompat:1.2.0'
        implementation 'com.google.android.material:material:1.2.1'
        implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    
        implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
        implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
    
        implementation 'androidx.navigation:navigation-fragment:2.0.0'
        implementation 'androidx.navigation:navigation-ui:2.0.0'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    MainActivity和布局 :

    import android.os.Bundle;
    
    import com.google.android.material.bottomnavigation.BottomNavigationView;
    
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.navigation.NavController;
    import androidx.navigation.Navigation;
    import androidx.navigation.ui.AppBarConfiguration;
    import androidx.navigation.ui.NavigationUI;
    
    import com.xq.mybottomnavigation.databinding.ActivityMainBinding;
    
    public class MainActivity extends AppCompatActivity {
    
        private ActivityMainBinding binding;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            binding = ActivityMainBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
    
            BottomNavigationView navView = findViewById(R.id.nav_view);
            // Passing each menu ID as a set of Ids because each
            // menu should be considered as top level destinations.
            //获取App bar配置:AppBarConfiguration
            AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                    R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                    .build();
            NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
            //将NavController和AppBarConfiguration进行绑定
            NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
            //将需要交互的App barUI与NavController和AppBarConfiguration进行绑定
            NavigationUI.setupWithNavController(binding.navView, navController);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • navigation pop 和 push的时候 对Fragment的 操作是 replace,所以会导致生命周期重新走一遍

    • 其实Navigation使用很简单,navigation和activity(确切的说是Fragment)绑定之后,使用两个方法就行,一个是navigate,就是跳转,一个是navigateUp,就是返回。

    如果想要跳转到新页面时,在Fragment中使用:

    NavHostFragment.findNavController(this).navigate(destinationID, bundle);
    NavHostFragment.findNavController(this).navigateUp();
    
    • 1
    • 2

    布局:

    
    <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_activity_main"
            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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    NavHostFragment:

    android:name="androidx.navigation.fragment.NavHostFragment"
    
    • 1

    这句话是在告诉系统,这是一个特殊的Fragment 。

    app:defaultNavHost=“true”:

    app:defaultNavHost="true"
    
    • 1

    将defaultNavHost属性设置为true,则该Fragment会自动处理系统返回键,即,当用户按下手机的返回按钮时,系统能自动将当前的Fragment推出。

    app:navGraph=“@navigation/nav_graph”:

    app:navGraph="@navigation/nav_graph"
    
    • 1

    设置该Fragment对应的导航图 。

    导航图文件 :@navigation/mobile_navigation

    <?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.xq.mybottomnavigation.ui.home.HomeFragment"
            android:label="@string/title_home"
            tools:layout="@layout/fragment_home" />
    
        <fragment
            android:id="@+id/navigation_dashboard"
            android:name="com.xq.mybottomnavigation.ui.dashboard.DashboardFragment"
            android:label="@string/title_dashboard"
            tools:layout="@layout/fragment_dashboard" />
    
        <fragment
            android:id="@+id/navigation_notifications"
            android:name="com.xq.mybottomnavigation.ui.notifications.NotificationsFragment"
            android:label="@string/title_notifications"
            tools:layout="@layout/fragment_notifications" />
    </navigation>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    fragment

    三个fragment类似

    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    import androidx.fragment.app.Fragment;
    import androidx.lifecycle.Observer;
    import androidx.lifecycle.ViewModelProvider;
    
    import com.xq.mybottomnavigation.R;
    import com.xq.mybottomnavigation.databinding.FragmentHomeBinding;
    
    public class HomeFragment extends Fragment {
    
        private HomeViewModel homeViewModel;
        private FragmentHomeBinding binding;
    
        public View onCreateView(@NonNull LayoutInflater inflater,
                                 ViewGroup container, Bundle savedInstanceState) {
            homeViewModel =
                    new ViewModelProvider(this).get(HomeViewModel.class);
    
            binding = FragmentHomeBinding.inflate(inflater, container, false);
            View root = binding.getRoot();
    
            final TextView textView = binding.textHome;
            homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
                    textView.setText(s);
                }
            });
            return root;
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            binding = null;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    
    <androidx.constraintlayout.widget.ConstraintLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.home.HomeFragment">
    
        <TextView
            android:id="@+id/text_home"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:textAlignment="center"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    androidx.constraintlayout.widget.ConstraintLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    其他

    Activity中导航到指定fragment

    //方式一 、 通过NavController
    NavController controller = Navigation
            .findNavController(this, R.id.nav_host_fragment_activity_main);
    binding.btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            controller.navigate(R.id.navigation_notifications);
        }
    });
    
    
    
    //方式二 、 通过NavHostFragment
    NavHostFragment fragment = (NavHostFragment) getSupportFragmentManager()
            .findFragmentById(R.id.nav_host_fragment_activity_main);
    binding.btn2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (fragment != null) {
                NavHostFragment.findNavController(fragment)
                        .navigate(R.id.navigation_notifications);
            }
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    NotificationsFragment 返回上一级

    textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            NavHostFragment.findNavController(NotificationsFragment.this).navigateUp();
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    动态设置 navGraph,传参数

    MainActivity : 发送数据

    NavController controller = Navigation
            .findNavController(this, R.id.nav_host_fragment_activity_main);
    NavInflater navInflater = controller.getNavInflater();
    NavGraph navGraph = navInflater.inflate(R.navigation.mobile_navigation);
    navGraph.setStartDestination(R.id.navigation_notifications);//初始界面
    
    Bundle args = new Bundle();
    args.putBoolean("6no6", true);//传参
    
    navController.setGraph(navGraph, args);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    或者

    //动态加载setGraph
    FragmentManager manager = getSupportFragmentManager();
    NavHostFragment hostFragment = (NavHostFragment) manager
            .findFragmentById(R.id.nav_host_fragment_activity_main);
    NavController controller = null;
    if (hostFragment != null) {
        controller = hostFragment.getNavController();
    }
    navController.setGraph(R.navigation.mobile_navigation);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    NotificationsFragment : 接收数据

    Bundle arguments = getArguments();
    if (arguments != null) {
        boolean aBoolean = arguments.getBoolean("6no6");
        textView.setTextColor(aBoolean ? Color.RED : Color.BLUE);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    导航监听:

    navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
        @Override
        public void onDestinationChanged(@NonNull @NotNull NavController controller,
                                         @NonNull @NotNull NavDestination destination,
                                         @Nullable Bundle arguments) {
            CharSequence label = destination.getLabel();
            Log.e(TAG, "onDestinationChanged: ===="+label);
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    切换时使Fragment保存状态

    在进行跳转时 直接使用了replace,所以导致当前页面会调用 onDestroyView,即fragment变为 inactive,当进行pop操作时,fragment重新进入 active状态时,会重新调用 onViewCreated 等方法,导致页面重新绘制,
    其实在这种情况下,我们可以直接用ViewModelLiveData对数据进行保存,但是这次想尝试一下新的解决办法。
    在知道原因后就好办了,直接继承FragmentNavigator把方法重写

    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
            
    //      ft.replace(mContainerId, frag);
    
    //      change to  
    
            if(mFragmentManager.getFragments().size()>0){
                ft.hide(mFragmentManager.getFragments().get(mFragmentManager.getFragments().size()-1));
                ft.add(mContainerId, frag);
            }else {
                ft.replace(mContainerId, frag);
            }
            
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    KeepStateFragmentNavigator 使用如下:

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
    navController.getNavigatorProvider()
        .addNavigator(new KeepStateFragmentNavigator(this, 
                                        navHostFragment.getChildFragmentManager(), 
                                        R.id.nav_host_fragment));
    NavHostFragment.findNavController(this).navigate(destinationID, bundle);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    一文教你理解Kafka offset
    Coinbase Ventures团队亲述CV简史及投资版图
    sentinel-1.8.7与nacos-2.3.0实现动态规则配置、双向同步
    计算机网络 | 计算机网络体系结构
    upp(统一流程平台)项目,如果需要项目章程会怎么写
    pandas教程:String Manipulation 字符串处理和正则表达式re
    如何使用SQL系列 之 如何在MySQL中使用存储过程
    HCIP—STP角色选举的实例
    # 智慧社区管理系统-核心业务管理-03投诉信息
    Elementor Pro许可证
  • 原文地址:https://blog.csdn.net/sinat_31057219/article/details/134056344