Fragment管理库:Navigation

时间: 2023-10-04 admin IT培训

Fragment管理库:Navigation

Fragment管理库:Navigation


前言

在以往的Fragment使用中,我们都是使用Fragment的事务进行添加,删除,替换等操作,为了快速开发,我们也会自行封装一个FragmentController。在去年,Google推出了Navigation库,目标是更优雅的管理Fragment。


正文

首先我们回顾一下Fragment的事务:

fragmentManager.beginTransaction().add(xxx)mit();

如果是常见的多Tab切换Fragment,我们会在XML中使用FrameLayout作为Fragment的容器,然后创建Fragment实例,根据不同情况放入FrameLayout中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=""android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><FrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>

假设我们要阅读这份代码,坦白的说,你从这个xml可以得到的信息非常的少,你只能猜测这个页面可能是使用了Fragment仅此而已,然后再去找Java或Kotlin文件,具体查看FrameLayout都使用了哪些功能逻辑。

Navigation

1.什么是Navigation

Navigation是一个可简化的Android导航的库和插件,换句话说,Navigation是用来管理Fragment的切换的,并且是通过可视化的方式来进行管理的。

2.Navigation的优缺点

优点

  • 处理Fragment的切换
  • 默认情况下正确处理Fragment的前进和后退
  • 为过渡和动画提供标准化的资源
  • 可以绑定Toolbar/BottomNavigationView/ActionBar等
  • 数据传递时提供类型安全性(使用SafeArgs)
  • ViewModel支持

缺点

  • fragment切换后底层会调用replace方法导致会被不断销毁,无法保存上一次的状态

3.Navigation的使用

Navigation的使用相对来说比较简答,分为以下几步:
(1)引入依赖
(2)创建多个要调配的Fragment
(3)在res下面创建navigation文件夹,并创建navigation文件
(4)在主Activity里面的XML文件里面引入指定的Fragment
基本上大体步骤就那么几步,现在我们就一个一个来看,完成刚才的多Tab切换逻辑:

MainActivity的xml文件:<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=""xmlns:app=""xmlns:tools=""android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><!-- fragment的集合 --><fragmentandroid:id="@+id/nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:defaultNavHost="true"app:navGraph="@navigation/nav_graph" /></androidx.constraintlayout.widget.ConstraintLayout>
nav_graph文件:<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android=""xmlns:app=""xmlns:tools=""android:id="@+id/nav_graph"app:startDestination="@id/mainFragment"> <!-- 开始的fragment --><fragmentandroid:id="@+id/mainFragment"android:name="com.lzp.navigation.fragment.MainFragment"android:label="main"tools:layout="@layout/fragment_main" /><fragmentandroid:id="@+id/secondFragment"android:name="com.lzp.navigation.fragment.SecondFragment"android:label="second"tools:layout="@layout/fragment_sec" /></navigation>

从代码量上来看,确实是增加了,但是对应的xml中可以查看的信息增加了很多,从Activity的XML中我们把Fragment的使用区域封装成一个Fragment,而这个Fragment绑定了一个@navigation/nav_graph文件,在nav_graph中描述了我们将会使用到哪些Fragment。把Fragment的维护移动到XML中,尽可能简化Fragment的使用复杂度,提高代码的可阅读性和维护性。你可以把Navigation的使用看成是一个高级的Include,只不过他的功能更加丰富和强大。

添加Gradle依赖

 dependencies {def nav_version = "2.1.0"// Javaimplementation "androidx.navigation:navigation-fragment:$nav_version"implementation "androidx.navigation:navigation-ui:$nav_version"// Kotlinimplementation "androidx.navigation:navigation-fragment-ktx:$nav_version"implementation "androidx.navigation:navigation-ui-ktx:$nav_version"}

Google提供了Java和Kotlin两个版本。想要使用Navigation,必须要支持androidX

使用NavHostFragment

<!-- fragment的集合 --><fragmentandroid: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/nav_graph" />

把FrameLayout容器替换成NavHostFragment,app:navGraph="@navigation/nav_graph"是绑定对应的布局文件。@navigation只有在android studio 3.3以上版本才支持。

创建navGraph

在res文件加下创建navigation文件夹,在该文件夹下创建你需要的xml.

我们将会使用两个Fragment,分别为MainFragment和SecondFragment,要为他们设置好id,因为Fragment的切换需要使用id。app:startDestination="@id/mainFragment"必须设置,指定默认添加的Fragment的id,如果不设置会直接崩溃。

切换Fragment

从MainFragment切换到SecondFragment:

val navHostController = Navigation.findNavController(activity, R.id.nav_host_fragment)
// 跳转到secondFragment
navHostController.navigate(R.id.secondFragment)
// 返回上一个Fragment
navHostController.navigateUp()

Navigation的更多理解

Navigation的使用就是这么简单,Fragment的控制几乎都在NavController中。

先简单看一下Navigation框架大致的实现原理。

在容器Activity的布局文件中,我们使用一个<fragment>标签,并且为标签显示的指定了一个android:name属性,里面配置的是一个Fragment的全路径,官方提供的是androidx.navigation.fragment.NavHostFragment,我们都知道,Activity加载布局的时候会根据配置的全路径通过反射获取到Fragment对象,然后attach到该Activity,最终完成Fragment的加载。想要了解Navigation框架,从NavHostFragment入手再合适不过。

public class NavHostFragment extends Fragment implements NavHost {...
}public interface NavHost {@NonNullNavController getNavController();
}

NavHostFragment就是一个Fragment的子类实现了一个简单的接口,能够对外提供获取NavController的方法,该方法的返回值就是NavHostFragment的一个属性mNavController。

mNavController属性的初始化是在onCreate生命周期中完成的。

@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);final Context context = requireContext();mNavController = new NavHostController(context);mNavController.setLifecycleOwner(this);//略...调用一些mNavController方法onCreateNavController(mNavController); //这个方法比较重要,下面会提及//设置导航图IDif (mGraphId != 0) {mNavController.setGraph(mGraphId);} else {//设置一个空导航}
}

mGraphId就是在fragment标签中配置的navGraph属性是在onInflate方法中获取的:

@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,@Nullable Bundle savedInstanceState){ super.onInflate(context, attrs, savedInstanceState);final TypedArray navHost = context.obtainStyledAttributes(attrs,androidx.navigation.R.styleable.NavHost);//通过自定义属性获取navigation导航图final int graphId = navHost.getResourceId(androidx.navigation.R.styleable.NavHost_navGraph, 0);if (graphId != 0) {mGraphId = graphId;}...
}

其实NavHostFragment才是容器Activity加载的第一个Fragment,在mNavController.setGraph方法调用之后,会经过一些列的方法调用,最终替换为在navigation资源文件中配置的startDestination属性中的Fragment。

NavController虽然看起来比较多,但它的功能还是比明确的,就是对外提供设置NavGraph、跳转方法navigate、返回事件控制以及监听Destination的变化。但真正执行视图跳转的逻辑并不是NavController执行的,而是通过mNavigatorProvider分发到了不同的Navigator中,然后执行真正的跳转逻辑:

//NavController中navigate最终的重载
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {//...//根据跳转类型的不同,分发到不同的navigator中执行跳转逻辑Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(node.getNavigatorName());Bundle finalArgs = node.addInDefaultArgs(args);//调用navigator里的navigate方法NavDestination newDest = navigator.navigate(node, finalArgs,navOptions, navigatorExtras);//...更新mBackStack栈
}

抽象类Navigator一共有5个子类:

@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination> {//... 控制Activity的跳转
}@Navigator.Name("dialog")
public final class DialogFragmentNavigator extends Navigator<DialogFragmentNavigator.Destination> {//...控制DialogFragment的跳转
}@Navigator.Name("fragment")
public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {//...控制Fragment的跳转
}@Navigator.Name("navigation")
public class NavGraphNavigator extends Navigator<NavGraph> {//...控制变更NavGraph
}@Navigator.Name("NoOp")
public class NoOpNavigator extends Navigator<NavDestination> {//...忽略不计...
}

NavigatorProvider类负责管理以上五种Navigator,管理的方式非常简单,就是用一个名为mNavigators的HashMap<String,Navigator>把通过addNavigator方法添加的Navigator缓存起来,其中key就是@Navigator.Name("xxx")注解里面给定的xxx,在getNavigator时从缓存中取出来给调用方。

在我们使用NavHostFragment的时候,框架会为我添加前四种Navigator,分别是在上文提到过的NavHostFragment的onCreate方法中的调用的onCreateNavController(mNavController)方法:

@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {//添加DialogFragmentNavigatornavController.getNavigatorProvider().addNavigator(new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));//添加FragmentNavigatornavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}

NavController的构造方法里:

public NavController(@NonNull Context context) {mContext = context;while (context instanceof ContextWrapper) {if (context instanceof Activity) {mActivity = (Activity) context;break;}context = ((ContextWrapper) context).getBaseContext();}//这里mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}

以上就是Navigation框架的大体逻辑,总结一下就是:

NavHostFragment作为容器Activity第一个加载的Fragment,维护了一个NavController的实例,并在NavigatorProvider中添加了4种Navigator用来执行不同的视图跳转逻辑,并在onCreate方法的最后,通过NavController.setGraph方法设置了在fragment标签中配置的nvGraph的id,把NavHostFragment重定向到了navigation.xml里配置的startDestination。NavController的跳转逻辑也通过跳转类型的,通过内部维护的NavigatorProvider分发到了不同的Navigator进行跳转。

情况就很明了了,我们在切换Fragment中调用的跳转方法:

最终会经过一系列的处理分发到FragmentNavigator的navigate方法中去

    @Nullable@Overridepublic NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {//略...final FragmentTransaction ft = mFragmentManager.beginTransaction();ft.replace(mContainerId, frag);ft.setPrimaryNavigationFragment(frag);//略...ft.setReorderingAllowed(true);ftmit();//略...}

看到这里终于恍然大悟,原来Navigation框架还是基于FragmentTransaction的封装!因为在打开新的Fragment的时候,老Fragment直接被replace掉了。