Navigation
是一种导航的概念,即把Activity
和fragment
当成一个个的目的地Destination
,各目的地形成一张导航图NavGraph
,由导航控制器NavController
来统一调度跳转,本文会先简单分析下AS自带的示例代码。
Jetpack笔记代码
本文源码基于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的复用方案。
本文使用 mdnice 排版