首先Navigation是一个架构组件,因为切换Activity是一个Binder通信的过程,所以Activity是属于比较重的组件。而Fragment的切换其实只是View的切换,比较轻量级。因此单Activity加Fragment切换成为了比较常见的架构方式。
为什么要用Navigation:1、使得Fragment的管理简单,可读性高。2、传递数据方式简单。3、标准的栈管理。4、支持Deeplink、URL Link定位到Fragment。5、处理动画更加方便。下面也主要围绕这几点进行讲解。
使用Navigation的重要三部分:
Navigation Graph:用于对Fragment进行配置的配置文件,需要在res/navigation/下创建的xml文件
FragmentContainerView/NavHostFragment:一系列Fragment的容器,用于承载Fragment
NavController:用于处理Fragment路由跳转
def nav_version = "2.5.0" //最新版本
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Feature module Support(navigation V2.3.0加入)
//支持模块化跳转时的支持包(国内基本不用,因为需要它需要Google Play Store 支持)
//优点:按需加入模块 类似于插件化
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation(2.3.0后加入的)
//提供一个TestNavHostController 来单独测试fragment与其NavController之间的交互(生命周期状态、调度器状态)
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
// Jetpack Compose Integration
//用于在使用 Jetpack Compose 构建的屏幕中实现一致而惯用的导航方式
implementation "androidx.navigation:navigation-compose:$nav_version
1、android:name=“androidx.navigation.fragment.NavHostFragment”:代表这个容器就是用来管理Fragment的容器
2、app:navGraph要导入我们控制的Fragment设计视图
3、app:defaultNavHost=“true” 代表可以拦截系统的返回键,托管给路由(不然会走系统的返回)。
<androidx.fragment.app.FragmentContainerView
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:navGraph="@navigation/my_navigation"/>
创建navigation文件夹 -> new xml文件 -> 选择navigation
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_navigation"
app:startDestination="@id/OneFragment"> <!--开始的第一个界面-->
<fragment
android:id="@+id/TwoFragment"
android:name="com.example.navigtion.TwoFragment"
android:label="fragment_second"
tools:layout="@layout/two_fragment" > <!--Fragment对应的界面视图-->
<argument
android:name="content"
app:argType="string"
app:nullable="true"
android:defaultValue="Three_Fragment" /> <!--传值标签-->
<action
android:id="@+id/action_twoFragment_to_threeFragment"
app:destination="@id/ThreeFragment"
app:enterAnim="@android:anim/fade_in"
app:exitAnim="@android:anim/fade_out"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" /> <!--跳转标签+动画-->
</fragment>
</navigation>
返回控制两种:
popBackStack:可以返回上一级或者可以指定路由返回的action进行当前页面的退栈跳转
navigateUp:返回当前页面堆栈的栈顶页面(内部就是popBackStack进行实现的)
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button viewById = inflate.findViewById(R.id.button_view);
viewById.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//跳转控制器
NavController navController = Navigation.findNavController(inflate);
//Kotlin中直接使用view.findNavController就可以获取到 (扩展了一个函数)
Bundle bundle = new Bundle();
bundle.putString("content", "第一个Fragment的值");
//传值方式很多种官方更推荐倒入Safe Args方式更加安全的传值
navController.navigate(R.id.action_oneFragment_to_twoFragment, bundle);
//navController.popBackStack(); //路由的栈管理(回到指定页面)
//Navigation.findNavController(it).navigateUp()
}
});
}
上面的是返回控制,那么如果我想A跳转B,B跳转C后只保留C页面的话。那么就需要以下两个action标签进行配合:
popUpTo:指的是在当前路由中,一直将页面出栈,直到指定的页面为止。
popUpToInclusive:则是代表包含关系,是否包含指定的页面
举例:在到达目的地 C 之后,返回堆栈包含每个目的地(A、B 和 C)的一个实例。当返回到目的地 A 时,我们也
popUpTo A,也就是说我们会在导航过程中从堆栈中移除 B 和 C。利用
app:popUpToInclusive=“true”,我们还会将第一个 A 从堆栈上弹出,从而有效地清除它。请注意,如果您不使用
app:popUpToInclusive,则返回堆栈会包含目的地 A 的两个实例。
<fragment
android:id="@+id/registerFragment"
android:name="com.example.navigation.RegisterFragment"
android:label="Register">
<action
android:id="@+id/action_registerFragment_to_mainListFragment"
app:destination="@id/mainListFragment"
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true" />
</fragment>
实际上和动态Inflate布局再添加布局到容器的场景非常类似,Navigation动态加载也是将navGraph从xml中创建好之后设置给navigation。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//动态加载
FragmentManager supportFragmentManager = getSupportFragmentManager();
NavHostFragment navHostFragment = (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
navController.setGraph(R.navigation.my_navigation);
}
<!--超链接:<a href="http://aa.bb.com/frag2"> -->
<Fragment>
...
<deepLink
android:id="@+id/deepLink"
android:autoVerify="true"
app:uri="http://aa.bb.com/frag2"
/>
<!--注意如果不加frag2(路径)的话只会跳转到导航图的开始页-->
<!--所以在跳转的deepLink也要加上/frag2(路径)-->
</Fragment>
<nav-graph android:value="@navigation/my_navigation"/>
注意:deepLink跳转会冲新走你Fragment导航图的路径。
例:导航图A -> 导航图B -> 导航图C ->导航图A。 你跳转到FragmentB。实际上是先跳转FragmentA再到FragmentB。所以此时你按返回键会回到FragmentA。
超链接:<a href="http://aa.bb.com/tom/24"> <!--name:tom age:24-->
<Fragment>
...
<argument
android:name="name"
app:argType="string"
app:nullable="true"/> <!--传值标签-->
<argument
android:name="age"
app:argType="string"
app:nullable="true"/> <!--传值标签-->
<deepLink
android:id="@+id/deepLink"
android:autoVerify="true"
app:uri="http://aa.bb.com/frag2/{name}/{age}"
/>
<!--注意如果不加frag2(路径)的话只会跳转到导航图的开始页-->
<!--所以在跳转的deepLink也要加上/frag2(路径)-->
</Fragment>
还可以使用NavDeepLinkRequest创建deepLink链接在应用内跳转。主要注意的是在应用内使用时只能导航到NavHost导航图以内的目的地,或者可以通过include将其他模块成为你目前导航图的子视图。
Fragment的切换,除了Fragment页面本身的切换,通常还伴有App bar的变化。为了方便统一管理,Navigation组件引入了NavigationUI类。帮助我们处理App bar的切换逻辑。
NavigationUI对三种类型的App bar提供了支持:
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph()).build();
2、将NavController和AppBarConfiguration进行绑定
NavigationUI.setupActionBarWithNavController(this,
navController, appBarConfiguration);
3、将需要交互的App barUI与NavController和AppBarConfiguration进行绑定
NavigationUI.setupWithNavController(toolbar,
navController,appBarConfiguration);
所有的NavigationUI绑定都是如此:下面示例代码是与BottomNavigationView的绑定
//获取BottomNavigationView
BottomNavigationView bottomNavigationView = findViewById(R.id.nav_bottom_view);
//获取AppBar配置AppBarConfiguration
AppBarConfiguration appBarConfig =
new AppBarConfiguration.Builder(R.id.OneFragment, R.id.TwoFragment,
R.id.ThreeFragment).build();
//进行绑定
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfig);
NavigationUI.setupWithNavController(bottomNavigationView, navController);
并且NavigationUI为我们提供了切换的监听OnDestinationChangedListener( )对Destination切换事件进行监听。
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener()
{
@Override
public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments)
{
Log.e(">>>>>", "onDestinationChanged: " + destination.getDisplayName());
}
});
按照我们使用的顺序去解析源码
初始化及启动第一个Fragment的过程
1、首先是在我们的布局中添加容器NavHostFragment,并设置导航navGraph。
2、解析NavGraph及跳转前夕
3、跳转
通过navigate进行页面跳转以及栈的管理(FragmentA 跳转至 FragmentB)
(FragmentB返回FragmentA)