Android-Jetpack笔记-Navigation之Fragment使用

Navigation是一种导航的概念,即把Activityfragment当成一个个的目的地Destination,各目的地形成一张导航图NavGraph,由导航控制器NavController来统一调度跳转,本文会先简单分析下AS自带的示例代码。

Jetpack笔记代码

本文源码基于SDK 29,IDE是Android studio 3.5.3

使用

创建工程,引入依赖,

1
2
implementation 'androidx.navigation:navigation-fragment:2.2.2'
implementation 'androidx.navigation:navigation-ui:2.2.2'

然后new activity,选中bottom navigation activity,IDE会创建出3个fragment和viewModel,1个activity和布局文件,1个菜单文件bottom_nav_menu,1个导航图文件mobile_navigation,运行如下:

先看下布局文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<androidx.constraintlayout.widget.ConstraintLayout>
<!-- 底部的导航view,菜单文件里定义了3个item -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
app:menu="@menu/bottom_nav_menu" />

<!-- fragment作为页面容器,navGraph指定了导航图的结构 -->
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

来到导航图文件mobile_navigation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home">

<fragment
android:id="@+id/navigation_home"
android:name="com.holiday.jetpackstudy.navigation.ui.home.HomeFragment"
tools:layout="@layout/fragment_home" />

<fragment
android:id="@+id/navigation_dashboard"
android:name="com.holiday.jetpackstudy.navigation.ui.dashboard.DashboardFragment"
tools:layout="@layout/fragment_dashboard" />

<fragment
android:id="@+id/navigation_notifications"
android:name="com.holiday.jetpackstudy.navigation.ui.notifications.NotificationsFragment"
tools:layout="@layout/fragment_notifications" />
</navigation>

这里列出了所有目的地,其中startDestination指定了导航图的起点即首页HomeFragment,把AS切换成design视图,

这样可以用可视化的方式管理导航图结构,然后来看activity,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NavigationActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_navigation);
BottomNavigationView navView = findViewById(R.id.nav_view);
//用3个目的地fragment构建配置类
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
//用fragment容器构建导航控制器
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
//为导航控制器设置配置类
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
//关联NavigationView和导航控制器
NavigationUI.setupWithNavController(navView, navController);
}

}

代码大致就这些,接下来看看内部实现。

原理

AppBarConfiguration.Builder将目的地(以下目的地均指页面)存储起来,

1
2
3
4
5
6
//AppBarConfiguration.java
Builder(int... topLevelDestinationIds) {
for (int destinationId : topLevelDestinationIds) {
mTopLevelDestinations.add(destinationId);
}
}

NavigationUI.setupActionBarWithNavController也是简单的设置参数,

1
2
3
4
5
6
//NavigationUI.java
void setupActionBarWithNavController(AppCompatActivity activity,NavController navController,
AppBarConfiguration configuration) {
navController.addOnDestinationChangedListener(
new ActionBarOnDestinationChangedListener(activity, configuration));
}

然后NavigationUI.setupWithNavController关联了NavigationView和导航控制器,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//NavigationUI.java
void setupWithNavController(final BottomNavigationView bottomNavigationView,
final NavController navController) {
//设置底部导航的点击事件
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
//底部导航切换按钮时
return onNavDestinationSelected(item, navController);
}
});
//在目的地发生切换的时候,更新底部导航的选中状态,先不看
navController.addOnDestinationChangedListener(xxx)
}

boolean onNavDestinationSelected(MenuItem item,NavController navController) {
//导航
navController.navigate(item.getItemId(), null, options);
}

来到NavController

1
2
3
4
5
6
7
//NavController.java
//省略调用链来到
void navigate(NavDestination node, Bundle args,
NavOptions navOptions, Navigator.Extras navigatorExtras) {
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
}

点进navigator.navigate,会发现有多个实现类,

这里我们使用的是FragmentNavigator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//FragmentNavigator.java
NavDestination navigate(Destination destination, Bundle args,
NavOptions navOptions, Navigator.Extras navigatorExtras) {
//获取fragment类名
String className = destination.getClassName();
//反射创建fragment
Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
//熟悉的fragment事务
FragmentTransaction ft = mFragmentManager.beginTransaction();
//用replace的方式展示fragment
ft.replace(mContainerId, frag);
//提交事务
ft.commit();
}

这里可以看出一个问题,每次切换目的地,fragment是反复销毁重建的,按照谷歌推荐的1个APP只需1个activity的思路开发,这样是没问题的,但是这里的fragment是作为首页的3个常驻页面,我们是希望能够保存起来的,毕竟,销毁重建需要重新请求网络数据,重新初始化view,严重影响用户体验,笔者将在下篇文章探讨fragment的复用方案。

参考文章