1.Navigation是什么?
Navigation 是一个框架,用于在 Android 应用中的“目标”之间导航,该框架提供一致的 API,无论目标是作为 Fragment、Activity 还是其他组件实现。
自己的话:
Navigation是管理Fragment之间导航的组件库,特别在实现单个Activity多个Fragment的管理模式更加灵活
其是底部导航栏+Navigation
这里我们使用三个碎片所以先定义三个Fragment
package com.example.myapplication
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
class MainPage1Fragment :Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page1,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//点击事件跳转到第二个fragment
val btn = view.findViewById
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page2)
}
}
}
package com.example.myapplication
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
class MainPage2Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page2,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btn = view.findViewById
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page1)
// Navigation.findNavController(view).navigateUp(); //返回上一个Fragment,
}
val btn2 = view.findViewById
btn2.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page3)
}
}
}
package com.example.myapplication
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
class MainPage3Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page3,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btn = view.findViewById
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page2)
// 回退上一步
// Navigation.findNavController(view).navigateUp();
}
}
}
他们的布局如下
xmlns:app=" http://schemas.android.com/apk/res-auto" xmlns:tools=" http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#00BCD4"> android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="MainPage1Fragment" android:textSize="20sp" android:textStyle="bold" android:textColor="#3F51B5" android:layout_gravity="center" />
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#3F51B5"> android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#3F51B5" android:text="MainPage2Fragment" android:textSize="20sp" android:textStyle="bold" android:layout_gravity="center" />
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#9C27B0"> android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="MainPage3Fragment" android:textSize="20sp" android:textStyle="bold" android:textColor="#3F51B5" android:layout_gravity="center" /> android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="跳到MainPage2Fragment" android:textAllCaps="false" android:layout_gravity="center" />
布局很简单就是几个Button按钮
它的点击事件就是在我们Navigation工作绑定后进行页面的跳转,这个需要我们在MainActivity里进行绑定,先放在这
现在我们先做Navigation的布局
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph_main" app:startDestination="@id/page1Fragment"> android:id="@+id/page1Fragment" android:name="com.example.myapplication.MainPage1Fragment" android:label="fragment_page1" tools:layout="@layout/fragment_main_page1"> android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> android:id="@+id/page2Fragment" android:name="com.example.myapplication.MainPage2Fragment" android:label="fragment_page2" tools:layout="@layout/fragment_main_page2"> android:id="@+id/action_page1" app:destination="@id/page1Fragment" /> android:id="@+id/action_page3" app:destination="@id/page3Fragment" /> android:id="@+id/page3Fragment" android:name="com.example.myapplication.MainPage3Fragment" android:label="fragment_page3" tools:layout="@layout/fragment_main_page3"> android:id="@+id/action_page2" app:destination="@id/page2Fragment" />
通过startDestination设置它的初始fragment,
通过action去绑定跳到destination对应的fragment
因为是底部导航栏tab所以我们需要去设置menu
现在我们看MainActivity和它的布局
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical" > android:id="@+id/my_nav_host_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="9" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph_main" /> android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="wrap_content" app:itemTextColor="#ff0000" app:menu="@menu/menu"/>
class MainActivity : AppCompatActivity() {
var bootomNavigationView :BottomNavigationView?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bootomNavigationView=findViewById(R.id.nav_view)
//让Navigation工作绑定
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
val controller = navHostFragment!!.navController
//底部导航栏tab+Navigation
NavigationUI.setupWithNavController(bootomNavigationView!!,controller)
}
}
现在我们就可以通过点击按钮和点击底部导航栏将他们进行切换
效果如下
分析源码
1.官网上: MainActivity去绑定Navigation(实际上:源码理解 Fragment帖到MainActivity)
我们先记住这个概念,后面详细了解中会进行解答
我们在详细分析源码先把它大致的流程用图画出来,并进行讲解
我们先看下图进行讲解
第一条线
1.首先MainActivity里有个NavHostFragment
NavHostFragment有个NavHost接口,而NavHostFragment就是为了临时存放一个Fragment
最终是把他黏贴在Activity之上
2.有个关键点它会调用NavController里的NavInflater
3.而NavInflater通过NavGraph找到它的Navigation.xml,进行解析Navigation.xml布局
4.最终解析出来之后变成NavGraph对象,此对象拥有所有的Graph导航信息集
以后操作此对象,就可以操作导航信息了
第二条线
1.它通过NavController控制器 拿到NavAction去调用navigate()方法
构造出NavDestInation,控制我们跳转到那个Fragment,
2.而NavDestInation只是导航信息指挥对象,它是领导者并不进行直接的操作,
它去指挥Navigator进行干活,为什么这么做呢
就是用来隔离,做了一个中转,为了更好扩展性,这是单一职责原则。
因为Navigator有很多子类的
你会发现jetpack很多系统源码搞这么多中间层
是因为任何高耦合低扩展的问题,都可以来一个中间层解决,方便以后的维护
MainActivity
现在我们来看MainActivity里的代码
//让Navigation工作绑定
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
val controller = navHostFragment!!.navController
//底部导航栏tab+Navigation
NavigationUI.setupWithNavController(bootomNavigationView!!,controller)
它是通过拿到NavHostFragment,再拿到控制器再将他们绑定,
那么按着我上面说的概念它是不是就是将NavHostFragmen帖到MainActivity上?
现在我们先跳进去看NavHostFragment的源码
可以看到它的构造函数是个空的,所以它最开始的create方法,注意这个create并不是生命周期的那个create
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId) {
return create(graphResId, null);
}
这个graphResId是什么,它其实就是我们那个导航的ID
我们先进去查看,它是调用了下面的create
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId,
@Nullable Bundle startDestinationArgs) {
Bundle b = null;
if (graphResId != 0) {
b = new Bundle();
b.putInt(KEY_GRAPH_ID, graphResId);
}
if (startDestinationArgs != null) {
if (b == null) {
b = new Bundle();
}
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
}
final NavHostFragment result = new NavHostFragment();
if (b != null) {
result.setArguments(b);
}
return result;
}
这里首先进行了Bundle的保存
然后再将这个 final NavHostFragment result = new NavHostFragment();
就是将自己实例化
onInflate
我们接下来要补充一个知识那就是fragment生命周期,我们都知道它是从onAttach()开始开始,但是有一种情况它在onAttach()前还有一个生命周期onInflate
1.onInflate(Context,AttributeSet,Bundle)只有硬编码在xml中的Fragment(即使用fragment标签)才会调用该方法,与自定义View十分类似,在实例化Xml布局时该方法会被调用
它的一些接收你可以查看下面文章
https://www.cnblogs.com/xiaowj/p/13913742.html
而我们其实也是在xml布局中NavHostFragment
@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);
final int graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0);
if (graphId != 0) {
mGraphId = graphId;
}
navHost.recycle();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
if (defaultHost) {
mDefaultNavHost = true;
}
a.recycle();
}
看到这个TypedArray有过自定义View的人就会很熟悉就是在解析xml布局
而我们的XML布局其中如下
android:id="@+id/my_nav_host_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="9" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph_main" /> 先解析XMl布局 就开始走第二步 final int graphId = navHost.getResourceId( androidx.navigation.R.styleable.NavHost_navGraph, 0); XMl布局拿到我们的navGraph,拿到了它的ID int graphId 在它的下面解析到了app:defaultNavHost="true" 这里就不在过多的介绍, onCreateNavController 那么现在我们发现其中有个onCreateNavController,我们在MainActivity中有用到navController 而且在上面讲的流程中不管是第一条线还是第二条线都离不开他 我们来看他的代码 @CallSuper protected void onCreateNavController(@NonNull NavController navController) { // 这里是通过导航暴露者,获取到 NavigationProvider 对象 navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); } 其实 这里是通过导航暴露者,获取到 NavigationProvider 对象 NavigationProvider 我们查看NavigationProvider @NonNull public NavigatorProvider getNavigatorProvider() { return mNavigatorProvider; } @SuppressLint("TypeParameterUnusedInFormals") public class NavigatorProvider { private static final HashMap @SuppressWarnings("BooleanMethodIsAlwaysInverted") private static boolean validateName(String name) { return name != null && !name.isEmpty(); } @NonNull static String getNameForNavigator(@NonNull Class extends Navigator> navigatorClass) { String name = sAnnotationNames.get(navigatorClass); if (name == null) { Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class); name = annotation != null ? annotation.value() : null; if (!validateName(name)) { throw new IllegalArgumentException("No @Navigator.Name annotation found for " + navigatorClass.getSimpleName()); } sAnnotationNames.put(navigatorClass, name); } return name; } 看到了吗它最顶部HashMap,我们知道在很多源码中map就是用来做缓存的作用, 所以看到这其实我们就可以知道这个HashMap就是为了把我们的三个fragment进行保存 那我们就明白了原来onCreateNavController就是为了初始化它的容器,再把它的暴露者初始化就行 但是其实会有疑惑为什么onCreateNavController写了两个getNavigatorProvider(), 因为我们出入在xml写入的方式,还可以通过代码写入,第二种createFragmentNavigator就是 如果你通过代码写入的方式,去存储fragment onCreate 接下来我们再查看它的生命周期函数onCreate public void onCreate(@Nullable Bundle savedInstanceState) { final Context context = requireContext(); mNavController = new NavHostController(context); mNavController.setLifecycleOwner(this); mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher()); // Set the default state - this will be updated whenever // onPrimaryNavigationFragmentChanged() is called mNavController.enableOnBackPressed( mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate); mIsPrimaryBeforeOnCreate = null; mNavController.setViewModelStore(getViewModelStore()); onCreateNavController(mNavController); Bundle navState = null; if (savedInstanceState != null) { navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE); if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) { mDefaultNavHost = true; getParentFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); } mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID); } if (navState != null) { // Navigation controller state overrides arguments mNavController.restoreState(navState); } if (mGraphId != 0) { // Set from onInflate() mNavController.setGraph(mGraphId); } else { // See if it was set by NavHostFragment.create() final Bundle args = getArguments(); final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0; final Bundle startDestinationArgs = args != null ? args.getBundle(KEY_START_DESTINATION_ARGS) : null; if (graphId != 0) { mNavController.setGraph(graphId, startDestinationArgs); } } // We purposefully run this last as this will trigger the onCreate() of // child fragments, which may be relying on having the NavController already // created and having its state restored by that point. super.onCreate(savedInstanceState); } 它第一步就是实例化控制器NavHostController,设置lifecycler监听生命周期, 设置ViewModel保证数据在横竖屏切换不丢失,以及拦截back返回键 再就是我们上面提到的 onCreateNavController(mNavController); 而if判断就是为了拿到Bundle里面的值以及进行FragmentManager的操作 现在我们看重点 if (mGraphId != 0) { // Set from onInflate() mNavController.setGraph(mGraphId); } 就是拿到我们解析出来的navigation.xml导航图 将我们的布局设置进去 我们再进去查看,一层层的往下调用 @CallSuper public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) { setGraph(getNavInflater().inflate(graphResId), startDestinationArgs); } 这个startDestinationArgs就是在navigation.xml设置的我们第一个启动的fragment 我们去查看它的inflate public NavGraph inflate(@NavigationRes int graphResId) { Resources res = mContext.getResources(); XmlResourceParser parser = res.getXml(graphResId); final AttributeSet attrs = Xml.asAttributeSet(parser); try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty loop } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } String rootElement = parser.getName(); NavDestination destination = inflate(res, parser, attrs, graphResId); if (!(destination instanceof NavGraph)) { throw new IllegalArgumentException("Root element <" + rootElement + ">" + " did not inflate into a NavGraph"); } return (NavGraph) destination; } catch (Exception e) { throw new RuntimeException("Exception inflating " + res.getResourceName(graphResId) + " line " + parser.getLineNumber(), e); } finally { parser.close(); } } XmlResourceParser就是安卓原生的解析方式,去解析布局 从开头解析到结尾最终返回我们的NavGraph,就是我们在上面提到的.最终解析出来之后变成NavGraph对象, 我们继续看下一层的 @CallSuper public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) { if (mGraph != null) { // Pop everything from the old graph off the back stack popBackStackInternal(mGraph.getId(), true); } mGraph = graph; onGraphCreated(startDestinationArgs); } 这个if判断,是进行弹栈,如果我们是首次打开首页不会进行这个操作, 当我们进行fragment的切换,按下返回键时候就会进行弹栈 而下面的nGraphCreated(startDestinationArgs);设置默认启动的frgment private void onGraphCreated(@Nullable Bundle startDestinationArgs) { if (mNavigatorStateToRestore != null) { ArrayList KEY_NAVIGATOR_STATE_NAMES); if (navigatorNames != null) { for (String name : navigatorNames) { Navigator> navigator = mNavigatorProvider.getNavigator(name); Bundle bundle = mNavigatorStateToRestore.getBundle(name); if (bundle != null) { navigator.onRestoreState(bundle); } } } } if (mBackStackToRestore != null) { for (Parcelable parcelable : mBackStackToRestore) { NavBackStackEntryState state = (NavBackStackEntryState) parcelable; NavDestination node = findDestination(state.getDestinationId()); if (node == null) { final String dest = NavDestination.getDisplayName(mContext, state.getDestinationId()); throw new IllegalStateException("Restoring the Navigation back stack failed:" + " destination " + dest + " cannot be found from the current destination " + getCurrentDestination()); } Bundle args = state.getArgs(); if (args != null) { args.setClassLoader(mContext.getClassLoader()); } NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args, mLifecycleOwner, mViewModel, state.getUUID(), state.getSavedState()); mBackStack.add(entry); } updateOnBackPressedCallbackEnabled(); mBackStackToRestore = null; } if (mGraph != null && mBackStack.isEmpty()) { boolean deepLinked = !mDeepLinkHandled && mActivity != null && handleDeepLink(mActivity.getIntent()); if (!deepLinked) { // Navigate to the first destination in the graph // if we haven't deep linked to a destination navigate(mGraph, startDestinationArgs, null, null); } } else { dispatchOnDestinationChanged(); } } 这里很复杂,我也没研究透,但是可以大概知道如果我们在xml布局里设置了startDestination设置初始化启动的fragment,以及对栈的管理 我们在看 if (mGraph != null && mBackStack.isEmpty()) { boolean deepLinked = !mDeepLinkHandled && mActivity != null && handleDeepLink(mActivity.getIntent()); if (!deepLinked) { // Navigate to the first destination in the graph // if we haven't deep linked to a destination navigate(mGraph, startDestinationArgs, null, null); } } 主要是执行navigate private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = false; boolean launchSingleTop = false; if (navOptions != null) { if (navOptions.getPopUpTo() != -1) { popped = popBackStackInternal(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); } } Navigator node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); if (newDest != null) { if (!(newDest instanceof FloatingWindow)) { // We've successfully navigating to the new destination, which means // we should pop any FloatingWindow destination off the back stack // before updating the back stack with our new destination //noinspection StatementWithEmptyBody while (!mBackStack.isEmpty() && mBackStack.peekLast().getDestination() instanceof FloatingWindow && popBackStackInternal( mBackStack.peekLast().getDestination().getId(), true)) { // Keep popping } } // When you navigate() to a NavGraph, we need to ensure that a new instance // is always created vs reusing an existing copy of that destination ArrayDeque NavDestination destination = newDest; if (node instanceof NavGraph) { do { NavGraph parent = destination.getParent(); if (parent != null) { NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs, mLifecycleOwner, mViewModel); hierarchy.addFirst(entry); // Pop any orphaned copy of that navigation graph off the back stack if (!mBackStack.isEmpty() && mBackStack.getLast().getDestination() == parent) { popBackStackInternal(parent.getId(), true); } } destination = parent; } while (destination != null && destination != node); } // Now collect the set of all intermediate NavGraphs that need to be put onto // the back stack destination = hierarchy.isEmpty() ? newDest : hierarchy.getFirst().getDestination(); while (destination != null && findDestination(destination.getId()) == null) { NavGraph parent = destination.getParent(); if (parent != null) { NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs, mLifecycleOwner, mViewModel); hierarchy.addFirst(entry); } destination = parent; } NavDestination overlappingDestination = hierarchy.isEmpty() ? newDest : hierarchy.getLast().getDestination(); // Pop any orphaned navigation graphs that don't connect to the new destinations //noinspection StatementWithEmptyBody while (!mBackStack.isEmpty() && mBackStack.getLast().getDestination() instanceof NavGraph && ((NavGraph) mBackStack.getLast().getDestination()).findNode( overlappingDestination.getId(), false) == null && popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) { // Keep popping } mBackStack.addAll(hierarchy); // The mGraph should always be on the back stack after you navigate() if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) { NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs, mLifecycleOwner, mViewModel); mBackStack.addFirst(entry); } // And finally, add the new destination with its default args NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest, newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel); mBackStack.add(newBackStackEntry); } else if (navOptions != null && navOptions.shouldLaunchSingleTop()) { launchSingleTop = true; NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast(); if (singleTopBackStackEntry != null) { singleTopBackStackEntry.replaceArguments(finalArgs); } } updateOnBackPressedCallbackEnabled(); if (popped || newDest != null || launchSingleTop) { dispatchOnDestinationChanged(); } } 我们重点看这段 Navigator node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); 就是为了拿到我们设置的startDestination,找到我们的第一个fragment,也是我们在fragment用到的 我们查看我们之前创建的Fragment class MainPage1Fragment :Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_main_page1,container,false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) //点击事件跳转到第二个fragment val btn = view.findViewById btn.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page2) } } } 我们可以看到要跳转显示的fragment也是调用了navigate 在进去查看navigate,就是我们用到的同一个方法 这就是我上面说的 NavDestInation只是导航信息指挥对象,它是领导者并不进行直接的操作, 它去指挥Navigator进行干活 Navigator 我们去查看Navigator‘ public abstract class Navigator /** * This annotation should be added to each Navigator subclass to denote the default name used * to register the Navigator with a {@link NavigatorProvider}. * * @see NavigatorProvider#addNavigator(Navigator) * @see NavigatorProvider#getNavigator(Class) */ @Retention(RUNTIME) @Target({TYPE}) @SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120 public @interface Name { String value(); } /** * Construct a new NavDestination associated with this Navigator. * * Any initialization of the destination should be done in the destination's constructor as * it is not guaranteed that every destination will be created through this method.
* @return a new NavDestination
*/
@NonNull
public abstract D createDestination();
/**
* Navigate to a destination.
*
*
Requests navigation to a given destination associated with this navigator in
* the navigation graph. This method generally should not be called directly;
* {@link NavController} will delegate to it when appropriate.
*
* @param destination destination node to navigate to
* @param args arguments to use for navigation
* @param navOptions additional options for navigation
* @param navigatorExtras extras unique to your Navigator.
* @return The NavDestination that should be added to the back stack or null if
* no change was made to the back stack (i.e., in cases of single top operations
* where the destination is already on top of the back stack).
*/
@Nullable
public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);
/**
* Attempt to pop this navigator's back stack, performing the appropriate navigation.
*
*
Implementations should return {@code true} if navigation
* was successful. Implementations should return {@code false} if navigation could not
* be performed, for example if the navigator's back stack was empty.
*
* @return {@code true} if pop was successful
*/
public abstract boolean popBackStack();
/**
* Called to ask for a {@link Bundle} representing the Navigator's state. This will be
* restored in {@link #onRestoreState(Bundle)}.
*/
@Nullable
public Bundle onSaveState() {
return null;
}
/**
* Restore any state previously saved in {@link #onSaveState()}. This will be called before
* any calls to {@link #navigate(NavDestination, Bundle, NavOptions, Navigator.Extras)} or
* {@link #popBackStack()}.
*
* Calls to {@link #createDestination()} should not be dependent on any state restored here as
* {@link #createDestination()} can be called before the state is restored.
*
* @param savedState The state previously saved
*/
public void onRestoreState(@NonNull Bundle savedState) {
}
/**
* Interface indicating that this class should be passed to its respective
* {@link Navigator} to enable Navigator specific behavior.
*/
public interface Extras {
}
}
它就是个抽象类,我们可以看它有6个子类继承了它,我们比较常用的Fragment
FragmentNavigator
所以我们去看FragmentNavigator
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
这里就是把我们千辛万苦保存的startDestination通过反射去找到这个fragment
再将他交给FragmentManager进行管理
再下面就是它fragment的进出动画
重点是它如何进行替换的,
ft.replace(mContainerId, frag);
这里的mContainerId就是我们xml布局里的NavHostFragment
就是将我们在mainactivity里的临时NavHostFragment与我们要显示的fragment进行替换
android:id="@+id/my_nav_host_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="9" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph_main" /> 为什么这么说呢,因为我们在NavHostFragment里面有个createFragmentNavigator()方法 @Deprecated @NonNull protected Navigator extends FragmentNavigator.Destination> createFragmentNavigator() { return new FragmentNavigator(requireContext(), getChildFragmentManager(), getContainerId()); } private int getContainerId() { int id = getId(); if (id != 0 && id != View.NO_ID) { return id; } // Fallback to using our own ID if this Fragment wasn't added via // add(containerViewId, Fragment) return R.id.nav_host_fragment_container; } getContainerId()就是把我们的NavHostFragment的id返回 10 小结 小结: 第一步:解析所有的目的地 第二步:放到一个HashMap里面全部保存好 第三步:获取第一个目的地Fragment 第四步:初始化目的地的所有信息 第五步:显示第一个目的地Fragment 画面 额外补充 额外补充 NavHostFragment.onViwCreated 这里有一个 Fragment的生命周期函数,忘记说了 @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (!(view instanceof ViewGroup)) { throw new IllegalStateException("created host view " + view + " is not a ViewGroup"); } Navigation.setViewNavController(view, mNavController); // 会把我们的 mNavController 加入到 Navigation里面去 // 说白了,如果是代码的方式去写的话,并非是xml的写法的话,就做下面的代码,但是 大部分都是 xml的形式 // 当以编程方式添加时,我们需要在父级上设置 NavController - 即具有与此 NavHostFragment 匹配的 ID 的视图 // When added programmatically, we need to set the NavController on the parent - i.e., // the View that has the ID matching this NavHostFragment. if (view.getParent() != null) { mViewParent = (View) view.getParent(); if (mViewParent.getId() == getId()) { Navigation.setViewNavController(mViewParent, mNavController); } } } >>> 下面是使用navigation角度上分析源码 1 MainPage1Fragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val btn = view.findViewById btn.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page2) // 在这里开始导航 } } 2 NavController.navigation public void navigate(@IdRes int resId) { navigate(resId, null); } public void navigate(@IdRes int resId, @Nullable Bundle args) { navigate(resId, args, null); } public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions) { navigate(resId, args, navOptions, null); } public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { // 目的 是在 Graph里面去寻找 当前节点的Fragment 为 目的地 NavDestination currentNode = mBackStack.isEmpty() // 先去看看栈有没有 ? mGraph : mBackStack.getLast().getDestination(); // 如果栈没有,就取最后一个 if (currentNode == null) { throw new IllegalStateException("no current navigation node"); } @IdRes int destId = resId; // 获取节点后,就要去获取 action 导航动作,只有action才能通过resId 获取下一个要导航的目标id final NavAction navAction = currentNode.getAction(resId); Bundle combinedArgs = null; if (navAction != null) { if (navOptions == null) { navOptions = navAction.getNavOptions(); } destId = navAction.getDestinationId(); // 获取下一个目的地的 id号信息等 Bundle navActionArgs = navAction.getDefaultArguments(); if (navActionArgs != null) { combinedArgs = new Bundle(); combinedArgs.putAll(navActionArgs); } } if (args != null) { if (combinedArgs == null) { combinedArgs = new Bundle(); } combinedArgs.putAll(args); } if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) { popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); return; } if (destId == 0) { throw new IllegalArgumentException("Destination id == 0 can only be used" + " in conjunction with a valid navOptions.popUpTo"); } // 下面代码只是一个检查健壮性而已,先不管 NavDestination node = findDestination(destId); if (node == null) { final String dest = NavDestination.getDisplayName(mContext, destId); if (navAction != null) { throw new IllegalArgumentException("Navigation destination " + dest + " referenced from action " + NavDestination.getDisplayName(mContext, resId) + " cannot be found from the current destination " + currentNode); } else { throw new IllegalArgumentException("Navigation action/destination " + dest + " cannot be found from the current destination " + currentNode); } } // 这行代码就是我们要关心的主线流程 navigate(node, combinedArgs, navOptions, navigatorExtras); } 3 NavController.navigation private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = false; boolean launchSingleTop = false; if (navOptions != null) { if (navOptions.getPopUpTo() != -1) { popped = popBackStackInternal(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); } } // 同学们,这里又回到了,最初开始的代码哦 Navigator node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); // 同学们 还记得 新的目的地么? OK 分析完毕了,无效重复分析了 NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); if (newDest != null) { if (!(newDest instanceof FloatingWindow)) { // We've successfully navigating to the new destination, which means // we should pop any FloatingWindow destination off the back stack // before updating the back stack with our new destination //noinspection StatementWithEmptyBody while (!mBackStack.isEmpty() && mBackStack.peekLast().getDestination() instanceof FloatingWindow && popBackStackInternal( mBackStack.peekLast().getDestination().getId(), true)) { // Keep popping } } // When you navigate() to a NavGraph, we need to ensure that a new instance // is always created vs reusing an existing copy of that destination ArrayDeque NavDestination destination = newDest; if (node instanceof NavGraph) { do { NavGraph parent = destination.getParent(); if (parent != null) { NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs, mLifecycleOwner, mViewModel); hierarchy.addFirst(entry); // Pop any orphaned copy of that navigation graph off the back stack if (!mBackStack.isEmpty() && mBackStack.getLast().getDestination() == parent) { popBackStackInternal(parent.getId(), true); } } destination = parent; } while (destination != null && destination != node); } // Now collect the set of all intermediate NavGraphs that need to be put onto // the back stack destination = hierarchy.isEmpty() ? newDest : hierarchy.getFirst().getDestination(); while (destination != null && findDestination(destination.getId()) == null) { NavGraph parent = destination.getParent(); if (parent != null) { NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs, mLifecycleOwner, mViewModel); hierarchy.addFirst(entry); } destination = parent; } NavDestination overlappingDestination = hierarchy.isEmpty() ? newDest : hierarchy.getLast().getDestination(); // Pop any orphaned navigation graphs that don't connect to the new destinations //noinspection StatementWithEmptyBody while (!mBackStack.isEmpty() && mBackStack.getLast().getDestination() instanceof NavGraph && ((NavGraph) mBackStack.getLast().getDestination()).findNode( overlappingDestination.getId(), false) == null && popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) { // Keep popping } mBackStack.addAll(hierarchy); // The mGraph should always be on the back stack after you navigate() if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) { NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs, mLifecycleOwner, mViewModel); mBackStack.addFirst(entry); // 留意一下,每次都会管理 进栈 } // And finally, add the new destination with its default args NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest, newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel); mBackStack.add(newBackStackEntry); } else if (navOptions != null && navOptions.shouldLaunchSingleTop()) { launchSingleTop = true; NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast(); if (singleTopBackStackEntry != null) { singleTopBackStackEntry.replaceArguments(finalArgs); } } updateOnBackPressedCallbackEnabled(); if (popped || newDest != null || launchSingleTop) { dispatchOnDestinationChanged(); } } 推荐阅读
发表评论