android的Jetpack的navigation原理详解
还没有用过navigation的小伙伴赶紧来用它,实在是太方便了,·再也不用写supportFragmentManager.beginTransaction().add()和supportFragmentManager.beginTransaction().replace()来切换布局了,只要将所有的需要跳转的Frament全部放到布局里面就好了,navigation和flutter的路由差不多,它只不过是将frament装到了作为一个个的跳转点而已。我觉着它最大的优势是可以通过android studio清晰的看到每个界面的跳转点。如图所示:
不用运行就可以看见每个模块的界面跳转,是不是很炫。大家可以参考一下官方文档android导航。
用起来也比较简单首先在build.gradle中加入
implementation "androidx.navigation:navigation-fragment-ktx:2.1.0" implementation "androidx.navigation:navigation-ui-ktx:2.1.0"
引入支持库之后,第二步在你的模块主布局中加入需要动态变化的Frament
对,就是上面这个东西,注意一点最终实例化的Fragment是androidx.navigation.fragment.NavHostFragment类,实例化的Frament是扩展库中的类,注意 app:navGraph="@navigation/mobile_navigation" 这个属性是真正的填充Fragment的类。第三步,在res下新建navigation文件夹,xml内容如下:
其中app:startDestination="@+id/home_dest"属性用来声明第一个显示的Fragment,argument标签用来声明传递的属性,action标签是用来跳转的,这和activity的action有异曲同工之妙,跳转到新的Fragment的时候调用Navigation.findNavController(view).navigate(),回到上一个Fragment的时候调Navigation.findNavController(view).navigateUp()
方法。好了,这就是Navigation的基本使用了,大家可以在官网下载demo看一下,接下来来探究一下navigation的原理。
前面介绍了navigation使用的入口就是
androidx.navigation.fragment.NavHostFragment这个类
public class NavHostFragment extends Fragment implements NavHost
NavHostFragment 就是一个Fragment并实现了NavHost接口,这个接口只有一个方法
public interface NavHost { /** * Returns the {@link NavController navigation controller} for this navigation host. * * @return this host's navigation controller */ @NonNull NavController getNavController();}
在它的onCreateView中,它只是填充了简单的帧布局FrameLayout,它的用处就是最终用来替换布局的
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { FrameLayout frameLayout = new FrameLayout(inflater.getContext()); // When added via XML, this has no effect (since this FrameLayout is given the ID // automatically), but this ensures that the View exists as part of this Fragment's View // hierarchy in cases where the NavHostFragment is added programmatically as is required // for child fragment transactions frameLayout.setId(getId()); return frameLayout; }
onCreateView执行完以后会执行onViewCreated,看一下
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); // 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) { View rootView = (View) view.getParent(); if (rootView.getId() == getId()) { Navigation.setViewNavController(rootView, mNavController); } } }
很简单就是为当前Fragment设置当前的根view设置一个tag,这个tag的值是NavHostController,这个类实现了对Fragment的控制的封装。而onInflate是在fragment标签被解析的时候调用的,它早于fragment的任何生命周期方法
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs, @Nullable Bundle savedInstanceState) { super.onInflate(context, attrs, savedInstanceState); final TypedArray navHost = context.obtainStyledAttributes(attrs, R.styleable.NavHost); final int graphId = navHost.getResourceId(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(); }
这里根据属性解析出app:navGraph引用的xml的id
然后在onCreate方法通过 mNavController.setGraph(mGraphId)方法解析出来第一个需要显示的Fragment
public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(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; requireFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); } } 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); } } }
继续看
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); }
如果mGraph 不为null就证明栈中已经存放了当前的Fragment,直接让它出栈就好了,接着看onGraphCreated方法
private void onGraphCreated(@Nullable Bundle startDestinationArgs) { if (mNavigatorStateToRestore != null) { ArrayList navigatorNames = mNavigatorStateToRestore.getStringArrayList( 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 (mBackStackUUIDsToRestore != null) { for (int index = 0; index < mBackStackUUIDsToRestore.length; index++) { UUID uuid = UUID.fromString(mBackStackUUIDsToRestore[index]); int destinationId = mBackStackIdsToRestore[index]; Bundle args = (Bundle) mBackStackArgsToRestore[index]; NavDestination node = findDestination(destinationId); if (node == null) { throw new IllegalStateException("unknown destination during restore: " + mContext.getResources().getResourceName(destinationId)); } if (args != null) { args.setClassLoader(mContext.getClassLoader()); } mBackStack.add(new NavBackStackEntry(uuid, node, args, mViewModel)); } updateOnBackPressedCallbackEnabled(); mBackStackUUIDsToRestore = null; mBackStackIdsToRestore = null; mBackStackArgsToRestore = 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); } } }
和流程有关的方法是navigate,继续进入
private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = false; if (navOptions != null) { if (navOptions.getPopUpTo() != -1) { popped = popBackStackInternal(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); } } Navigator navigator = mNavigatorProvider.getNavigator( 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 } } // The mGraph should always be on the back stack after you navigate() if (mBackStack.isEmpty()) { mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel)); } // Now ensure all intermediate NavGraphs are put on the back stack // to ensure that global actions work. ArrayDeque hierarchy = new ArrayDeque<>(); NavDestination destination = newDest; while (destination != null && findDestination(destination.getId()) == null) { NavGraph parent = destination.getParent(); if (parent != null) { hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel)); } destination = parent; } mBackStack.addAll(hierarchy); // And finally, add the new destination with its default args NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, newDest.addInDefaultArgs(finalArgs), mViewModel); mBackStack.add(newBackStackEntry); } updateOnBackPressedCallbackEnabled(); if (popped || newDest != null) { dispatchOnDestinationChanged(); } }
这个方法有点长,大部分代码都是保存当前需要显示的Fragment类的封装类NavDestination ,保存到栈中。
navigator.navigate(node, finalArgs, navOptions, navigatorExtras);
而navigator.navigate是真正实现Fragment切换的,真正实现类是FragmentNavigator
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 sharedElement : extras.getSharedElements().entrySet()) { 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; } }
这个方法最重要的就是这个代码 ft.replace(mContainerId, frag),ft是FragmentTransaction 类,而mContainerId就是NavHostFragment的布局View的id如下:
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { FrameLayout frameLayout = new FrameLayout(inflater.getContext()); // When added via XML, this has no effect (since this FrameLayout is given the ID // automatically), but this ensures that the View exists as part of this Fragment's View // hierarchy in cases where the NavHostFragment is added programmatically as is required // for child fragment transactions frameLayout.setId(getId()); return frameLayout; }
所以Frament的切换都是通过NavHostFragment的FrameLayout 来切换的
更多相关文章
- 安卓 屏蔽子控件点击事件
- Android(安卓)数据存取到文件当中
- android 实现自定义卫星菜单
- Activity的创建
- android的莫名其妙的事
- Android图形动画概述
- 自定义android Rating bar
- android 巧用finish方法
- 自定义Dialog之信息提示