Navigation的用法
一.Navigation的诞生
单个Activity嵌套多个Fragment的UI架构模式,已经被大多数的Android工程师所接受和采用。但是,对Fragment的管理一直是一件比较麻烦的事情。我们需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。页面的切换通常还包括对应用程序App bar的管理,Fragment之间的切换动画以及Fragment之间的参数传递。纯代码的方式使用起来不是特别友好,并且Fragment和App bar在管理和使用的过程中显得很混乱。
为此,Jetpack提供了一个名为Navigation的组件,旨在方便我们管理页面和App bar。它具有以下优势:
1.可视化的页面导航图,便于我们理清页面间的关系
2.通过destination和action完成页面间的导航
3.方便添加页面的切换动画
4.页面间类型安全的参数传递
5.通过NavigationUI类,对菜单,底部导航,抽屉菜单导航进行统一的管理
6.支持深层链接DeepLink
二.Navigation的主要元素
在正式学习Navigation之前,我们先要对Navigation中的主要元素有一个大致的了解。
Navigation Graph:导航图,包括应用程序所有的页面以及页面间的关系
NavHostFragment:这是一个特殊的Fragment,你可以认为它是其他Fragment的容器,Navigation Graph中的Fragment正是通过NavHostFragment进行展示的
NavController:导航控制器,用于在代码中完成Navigation Graph中具体的页面切换动作
它们三者之间的关系可以通过下面的这段话来理解:当你想要切换Fragment时,使用NavController对象,告诉它你想要去Navigation Graph中的哪个Fragment,NavController会将你想去的Fragment展示在NavHostFragment中。
三.如何使用Navigation
使用Navigation组件前,先要添加以下依赖:
implementation "androidx.navigation:navigation-fragment:2.5.2"
implementation "androidx.navigation:navigation-ui:2.5.2"
1.创建Navigation Graph
新建一个项目,然后在res文件夹下新建一个navigation资源目录,如下图所示:
然后在navigation目录下新建一个Navigation Resource File,名字任取,如下图所示:
2.添加NavHostFragment
NavHostFragment是一个特殊的Fragment,我们需要将它添加到Activity的布局文件中,作为其他Fragment的容器,代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <fragment android:id="@+id/nav_host_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:name="androidx.navigation.fragment.NavHostFragment" //指定这个Fragment是一个特殊的Fragment,是其他Fragment的容器 app:defaultNavHost="true" //该Fragment会自动处理系统返回键,当用户按下返回键时,系统自动将当前所展示的Fragment退出 app:navGraph="@navigation/nav_graph"/> //用于设置该容器对应的导航图 </RelativeLayout>
此时,打开nav_graph.xml的design面板,可以看到下面的内容:
3.创建destination
单击上图中的加号按钮,然后再点击create new destination即可创建新的Fragment,destination代表目的地,就是你想去的页面。这里我们创建了MainFragment,还有对应的布局文件fragment_main.xml,此时可以看到AS为我们自动生成的代码如下:
<?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/nav_graph" app:startDestination="@id/mainFragment"> //这句代码表示默认展示的页面是MainFragment <fragment android:id="@+id/mainFragment" android:name="com.example.navigation.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main" > </fragment> </navigation>
4.完成Fragment页面的切换
我们需要再次创建一个Fragment来完成这个动作,这里我创建了SecondFragment,方式和之前创建MainFragment一样。此时,我们可以看到design面板如下所示:
我们需要拖动鼠标从mainFragment到secondFragment,之后会生成如图所示的箭头,然后切换到Code面板,可以看到生成了以下代码:
<?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/nav_graph" app:startDestination="@id/mainFragment"> <fragment android:id="@+id/mainFragment" android:name="com.example.navigation.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main" > <action android:id="@+id/action_mainFragment_to_secondFragment" app:destination="@id/secondFragment" /> </fragment> <fragment android:id="@+id/secondFragment" android:name="com.example.navigation.SecondFragment" android:label="fragment_second" tools:layout="@layout/fragment_second" /> </navigation>
可以看到mainFragment处生成了一个action标签,表示mainFragment的目的地是secondFragment。
5.使用NavController完成导航
经过以上的步骤后,我们还需要通过NavController对象,在代码中完成具体的页面跳转工作,我们需要在MainFragment的布局文件中添加一个Button,用于页面的跳转。有两种方式可以实现页面的跳转,下面分别给出:
方法一:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view=inflater.inflate(R.layout.fragment_main,container,false); btn_jump=view.findViewById(R.id.btn_jump); btn_jump.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_secondFragment);//从mainFragment到secondFragment } }); return view; }
方法二:
btn_jump=view.findViewById(R.id.btn_jump);
btn_jump.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_mainFragment_to_secondFragment));
运行应用程序,然后点击按钮,可以看到页面跳转到了secondFragment,但是切换没有动画效果,显得很生硬,下面我们添加一个淡入淡出效果:
6.添加动画效果
首先,在res目录下新建一个anim文件夹,然后在这个文件夹下添加淡入淡出动画文件,代码如下:
//fade_in.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="3000" /> </set> //fade_out.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="3000" /> </set>
然后,修改nav_graph.xml文件,需要添加的代码如下:
<action android:id="@+id/action_mainFragment_to_secondFragment" app:destination="@id/secondFragment" app:enterAnim="@anim/fade_in" app:exitAnim="@anim/fade_out" app:popEnterAnim="@anim/fade_in" app:popExitAnim="@anim/fade_out" />
重新运行项目,就可以看到跳转和返回都有了淡入淡出效果。
四.使用safe args插件传递参数
在使用这个插件前,需要在project下的build.gragle文件中添加以下代码:
buildscript { dependencies { classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2" } }
然后,在app下的build.gradle文件中引用这个插件,需要添加的代码如下:
plugins { id 'com.android.application' id 'androidx.navigation.safeargs' }
接下来,就可以使用这个插件来传递参数了。有两种方式,一种是代码的方式,一种是直接通过design面板来添加。
单击Arguments右边的加号就可以添加参数了,添加之后自动生成的代码如下:
<fragment android:id="@+id/mainFragment" android:name="com.example.navigation.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main" > <action android:id="@+id/action_mainFragment_to_secondFragment" app:destination="@id/secondFragment" app:enterAnim="@anim/fade_in" app:exitAnim="@anim/fade_out" app:popEnterAnim="@anim/fade_in" app:popExitAnim="@anim/fade_out" /> <argument android:name="username" app:argType="string" android:defaultValue="unknown" /> <argument android:name="age" app:argType="integer" android:defaultValue="0" /> <deepLink app:uri="http://test.com"/> </fragment>
做完以上操作后,就可以看到safe args插件给我们生成的代码文件了,如下图所示:
可以看到,为我们生成了MainFragmentArgs.java和MainFragmentDirections.java文件。如果没有的话,可以重新编译一下项目。
然后,我们就可以利用所生成的代码文件,在Fragment之间进行参数的传递了,代码如下:
//MainFragment @Override public void onClick(View view) { Bundle bundle=new MainFragmentArgs.Builder() .setUsername("jack") .setAge(25) .build() .toBundle(); Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_secondFragment,bundle); }
//SecondFragment @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle=getArguments(); if(bundle!=null){ String username = MainFragmentArgs.fromBundle(bundle).getUsername(); int age = MainFragmentArgs.fromBundle(bundle).getAge(); Log.i("username",username); Log.i("age",age+""); } }
Navigation 组件具有一个名为 Safe Args 的 Gradle 插件,该插件可以生成简单的 object 和 builder 类,以便以类型安全的方式浏览和访问任何关联的参数。我们强烈建议您将 Safe Args 用于导航和数据传递,因为它可以确保类型安全。这是Android Studio官网的原话。
五.NavigationUI的使用方法
在页面的切换过程中,通常还伴随着App bar中menu菜单的变化,对于不同的页面,App bar中的menu菜单很可能是不一样的。App bar中各种按钮和菜单,同样承担着页面切换的工作。例如,当ActionBar左边的返回按钮被单击时,我们需要响应该事件,返回到上一个页面。既然Navigation和App bar都需要处理页面切换事件,那么为了方便管理,Jetpack引入了NavigationUI组件,使App bar中的按钮和菜单能够与导航图中的页面关联起来。
假设,我们有两个页面:MainFragment和SecondFragment,这两个页面同属于MainActivity。我们希望MainFragment的ActionBar右边有一个按钮,通过该按钮可以跳转到SecondFragment。而在SecondFragment的ActionBar左侧有一个返回按钮,通过该按钮,可以返回MainFragment。我们可以通过下面的方式实现:
我们在res下新建一个menu菜单,然后添加一个menu_settings.xml文件,内容如下:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/secondFragment" android:title="第二页面"/> </menu>
需要注意的是,item标签中的id需要和导航图nav_graph.xml中SecondFragment的id一样,这表示,当该item被单击时,将会跳转到该id所对应的Fragment页面中。
在MainActivity中实例化该菜单,代码如下:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_settings,menu); return super.onCreateOptionsMenu(menu); }
将App bar和NavController绑定起来,代码如下:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); NavController navController= Navigation.findNavController(this,R.id.nav_host_fragment); navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {//处理页面切换事件 @Override public void onDestinationChanged(@NonNull NavController navController, @NonNull NavDestination navDestination, @Nullable Bundle bundle) { switch(navDestination.getId()){ case R.id.mainFragment: Toast.makeText(MainActivity.this, "main", Toast.LENGTH_SHORT).show(); break; case R.id.secondFragment: Toast.makeText(MainActivity.this, "second", Toast.LENGTH_SHORT).show(); break; } } }); AppBarConfiguration appBarConfiguration=new AppBarConfiguration.Builder(navController.getGraph()).build(); NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration); }
处理菜单项点击事件:
@Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { return NavigationUI.onNavDestinationSelected(item,navController)||super.onOptionsItemSelected(item); }
处理从SecondFragment到MainFragment的返回事件:
@Override public boolean onSupportNavigateUp() { return NavigationUI.navigateUp(navController,appBarConfiguration)||super.onSupportNavigateUp(); }
六.深层链接DeepLink
DeepLink的常见应用场景如下:当应用程序收到某个通知推送,你希望用户在单击该通知后,能够跳转到展示该通知内容的页面。接下来,我们使用PendingIntent+DeepLink来实现这个功能,代码如下:
@RequiresApi(api = Build.VERSION_CODES.O) public void sendNotification(){ NotificationManager notificationManager= (NotificationManager)getActivity().getSystemService(NOTIFICATION_SERVICE); NotificationChannel channel=new NotificationChannel("MainFragment","跳转",NotificationManager.IMPORTANCE_HIGH); notificationManager.createNotificationChannel(channel); Notification.Builder builder=new Notification.Builder(getActivity(),"MainFragment"); builder.setAutoCancel(true); builder.setSmallIcon(R.mipmap.ic_launcher_round); builder.setContentTitle("设置"); builder.setContentText("点击查看详情"); builder.setContentIntent(getPendingIntent()); Notification notification = builder.build(); notificationManager.notify(1,notification); } public PendingIntent getPendingIntent(){ Bundle bundle=new Bundle(); bundle.putString("username","jack"); return Navigation.findNavController(getActivity(),R.id.nav_host_fragment).createDeepLink() .setGraph(R.navigation.nav_graph) .setDestination(R.id.secondFragment) .setArguments(bundle) .createPendingIntent(); } @RequiresApi(api = Build.VERSION_CODES.O) @Override public void onStart() { super.onStart(); sendNotification(); }
当点击通知是,会自动跳转到我们在PendingIntent中设置好的目的地,也就是SecondFragment页面。