一.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组件前,先要添加以下依赖:
代码语言:javascript复制 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的容器,代码如下所示:
代码语言:javascript复制<?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为我们自动生成的代码如下:
代码语言:javascript复制<?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面板,可以看到生成了以下代码:
代码语言:javascript复制<?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,用于页面的跳转。有两种方式可以实现页面的跳转,下面分别给出:
方法一:
代码语言:javascript复制@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;
}
方法二:
代码语言:javascript复制 btn_jump=view.findViewById(R.id.btn_jump);
btn_jump.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_mainFragment_to_secondFragment));
运行应用程序,然后点击按钮,可以看到页面跳转到了secondFragment,但是切换没有动画效果,显得很生硬,下面我们添加一个淡入淡出效果:
6.添加动画效果
首先,在res目录下新建一个anim文件夹,然后在这个文件夹下添加淡入淡出动画文件,代码如下:
代码语言:javascript复制//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文件,需要添加的代码如下:
代码语言:javascript复制<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文件中添加以下代码:
代码语言:javascript复制buildscript {
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2"
}
}
然后,在app下的build.gradle文件中引用这个插件,需要添加的代码如下:
代码语言:javascript复制plugins {
id 'com.android.application'
id 'androidx.navigation.safeargs'
}
接下来,就可以使用这个插件来传递参数了。有两种方式,一种是代码的方式,一种是直接通过design面板来添加。
单击Arguments右边的加号就可以添加参数了,添加之后自动生成的代码如下:
代码语言:javascript复制<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之间进行参数的传递了,代码如下:
代码语言:javascript复制//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);
}
代码语言:javascript复制//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文件,内容如下:
代码语言:javascript复制<?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中实例化该菜单,代码如下:
代码语言:javascript复制 @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_settings,menu);
return super.onCreateOptionsMenu(menu);
}
将App bar和NavController绑定起来,代码如下:
代码语言:javascript复制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);
}
处理菜单项点击事件:
代码语言:javascript复制 @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return NavigationUI.onNavDestinationSelected(item,navController)||super.onOptionsItemSelected(item);
}
处理从SecondFragment到MainFragment的返回事件:
代码语言:javascript复制@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController,appBarConfiguration)||super.onSupportNavigateUp();
}
六.深层链接DeepLink
DeepLink的常见应用场景如下:当应用程序收到某个通知推送,你希望用户在单击该通知后,能够跳转到展示该通知内容的页面。接下来,我们使用PendingIntent DeepLink来实现这个功能,代码如下:
代码语言:javascript复制@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页面。