Android-Jetpack笔记-Navigation之Fragment使用
Navigation
是一种导航的概念,即把Activity
和fragment
当成一个个的目的地Destination
,各目的地形成一张导航图NavGraph
,由导航控制器NavController
来统一调度跳转,本文会先简单分析下AS自带的示例代码。
本文源码基于SDK 29
,IDE是Android studio 3.5.3
使用
创建工程,引入依赖,
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
,运行如下:
先看下布局文件,
<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
,
<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,
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
将目的地(以下目的地均指页面)存储起来,
//AppBarConfiguration.java
Builder(int... topLevelDestinationIds) {
for (int destinationId : topLevelDestinationIds) {
mTopLevelDestinations.add(destinationId);
}
}
NavigationUI.setupActionBarWithNavController
也是简单的设置参数,
//NavigationUI.java
void setupActionBarWithNavController(AppCompatActivity activity,NavController navController,
AppBarConfiguration configuration) {
navController.addOnDestinationChangedListener(
new ActionBarOnDestinationChangedListener(activity, configuration));
}
然后NavigationUI.setupWithNavController
关联了NavigationView和导航控制器,
//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
,
//NavController.java
//省略调用链来到
void navigate(NavDestination node, Bundle args,
NavOptions navOptions, Navigator.Extras navigatorExtras) {
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
}
点进navigator.navigate
,会发现有多个实现类,
这里我们使用的是FragmentNavigator
,
//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的复用方案。