zoukankan      html  css  js  c++  java
  • Jetpack学习-Navigation

    个人博客

    http://www.milovetingting.cn

    Jetpack学习-Navigation

    Navigation翻译过来就是导航。

    导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。

    导航组件由以下三个关键部分组成:

    • 导航图:在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。

    • NavHost:显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现 (NavHostFragment),可显示 Fragment 目标。

    • NavController:在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。

    在应用中导航时,您告诉 NavController,您想沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。NavController 便会在 NavHost 中显示相应目标。

    导航组件提供各种其他优势,包括以下内容:

    • 处理 Fragment 事务。

    • 默认情况下,正确处理往返操作。

    • 为动画和转换提供标准化资源。

    • 实现和处理深层链接。

    • 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。

    • Safe Args - 可在目标之间导航和传递数据时提供类型安全的 Gradle 插件。

    • ViewModel 支持 - 您可以将 ViewModel 的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。

    • 此外,您还可以使用 Android Studio 的 Navigation Editor 来查看和编辑导航图。

    以上内容来自官方文档(我只是一个搬运工(^o^)/)

    简单使用

    引入Navigation

    在需要使用Navigation的模块的build.gradle中引入

        def nav_version = "2.3.0-alpha01"
        implementation "androidx.navigation:navigation-fragment:$nav_version"
        implementation "androidx.navigation:navigation-ui:$nav_version"
    

    建立导航图

    在res目录右键-New-Android Resource File

    nav-graph.png

    在弹出的界面中,File name可随意输入,Resource type选择Navigation,点击确定

    nav-graph2.png

    点击确定后,会在res目录下创建navigation目录,以及刚才定义的导航文件

    nav-graph3.png

    双击打开刚才创建的导航文件,在Design界面可以看到目前还没有内容,可以点击上方的+号图标添加fragment,也可以自己手动在xml中添加

    nav-graph4.png

    我们需要为这个文件指定startDestination,即起始的界面

    nav-graph5.png

    startDestination指定为mainFragment,mainFragment对应的布局为fragment_main

    Navigation首先会加载一个默认的Fragment,这个需要在Activity中指定

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment
            android: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" />
    
    </LinearLayout>
    

    配置defaultNavHost为true,即指定这个fragment为默认的NavHost,每个导航图只能指定一个默认的NavHost。这里的name配置为androidx.navigation.fragment.NavHostFragment,navGraph配置为nav_graph,即指定nav_graph为导航图。这样当Activity启动时,会首先通过activity布局里的fragment去加载导航图中的startDestination配置的fragment。

    导航

    通过一个Fragment导航到另一个Fragment,可以通过

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_main, container, false);
            loginBtn = view.findViewById(R.id.fragment_main_login);
            loginBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Bundle bundle = new Bundle();
                    bundle.putString("name", "zs");
                    Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_loginFragment, bundle);
                }
            });
            return view;
        }
    

    这里通过点击一个按钮进行跳转,通过Navigation.findNavController(v).navigate()方法导航。这里还可以通过Bundle进行传值。

    在目的Fragment,可以通过getArguments()来获取到传递过来的数据

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            String name = getArguments().getString("name", "null");
            Toast.makeText(getContext(), name, Toast.LENGTH_SHORT).show();
            View view = inflater.inflate(R.layout.fragment_login, container, false);
            backBtn = view.findViewById(R.id.fragment_login_back);
            backBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Navigation.findNavController(v).popBackStack();
                }
            });
            return view;
        }
    

    在目的Fragment,还可以通过一个按钮返回上一个Fragment:Navigation.findNavController(v).popBackStack()

    原理

    Navigation的简单使用流程就介绍到这,可以在官方文档上看更多相关的使用方法。下面来分析下Navigation的流程

    显示起始Fragment

    在Activity启动时,会先实例化NavHostFragment,这个是我们前面在布局中指定的。

    首先会执行onInflate方法

    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();
        }
    

    这个方法,其实就是解析出来我们要使用哪个导航图,获取到了graphId。还获取了是否为默认的Host:defaultHost

    然后会执行onAttach方法

    public void onAttach(@NonNull Context context) {
            super.onAttach(context);
            // TODO This feature should probably be a first-class feature of the Fragment system,
            // but it can stay here until we can add the necessary attr resources to
            // the fragment lib.
            if (mDefaultNavHost) {
                getParentFragmentManager().beginTransaction()
                        .setPrimaryNavigationFragment(this)
                        .commit();
            }
        }
    

    由于在onInflate已经获取到mDefaultNavHost为true,因此这里会将当前Fragment通过commit加入到FragmentManager()中

    然后执行onCreate方法

    public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final Context context = requireContext();
    
            //设置NavHostController,NavHostController里初始化了NavigatorProvider
            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()
                //前面执行onInflate后,已经获取到mGraphId,因此会执行下面的setGraph代码
                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);
                }
            }
        }
    

    在这个方法里,设置了NavHostController及NavigatorProvider,然后执行NavController.setGraph方法

    public void setGraph(@NavigationRes int graphResId) {
            setGraph(graphResId, null);
        }
    

    继续调用setGraph

    public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
            setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
        }
    

    继续调用setGraph

    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);
        }
    

    调用onGraphCreated

    private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
            if (mNavigatorStateToRestore != null) {
                ArrayList<String> 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 (mBackStackToRestore != null) {
                for (Parcelable parcelable : mBackStackToRestore) {
                    NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
                    NavDestination node = findDestination(state.getDestinationId());
                    if (node == null) {
                        throw new IllegalStateException("unknown destination during restore: "
                                + mContext.getResources().getResourceName(state.getDestinationId()));
                    }
                    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);
                }
            }
        }
    

    首次进入Activity,会最终执行navigate(mGraph, startDestinationArgs, null, null)方法来导航到起始的目的Fragment

    private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
                @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
            boolean popped = false;
           //...
            Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                    node.getNavigatorName());
            Bundle finalArgs = node.addInDefaultArgs(args);
            NavDestination newDest = navigator.navigate(node, finalArgs,
                    navOptions, navigatorExtras);
            //...
        }
    

    这个方法,会调用navigator.navigate(node, finalArgs,navOptions, navigatorExtras)方法,这个方法的实现在NavGraphNavigator

    public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
                @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
            int startId = destination.getStartDestination();
            if (startId == 0) {
                throw new IllegalStateException("no start destination defined via"
                        + " app:startDestination for "
                        + destination.getDisplayName());
            }
            NavDestination startDestination = destination.findNode(startId, false);
            if (startDestination == null) {
                final String dest = destination.getStartDestDisplayName();
                throw new IllegalArgumentException("navigation destination " + dest
                        + " is not a direct child of this NavGraph");
            }
            Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                    startDestination.getNavigatorName());
            return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
                    navOptions, navigatorExtras);
        }
    

    在这个方法里调用navigator.navigate方法,这个方法实现在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;
            }
            //通过反射实例化Fragment
            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);
            }
    
            //替换fragment
            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<View, String> 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;
            }
        }
    

    在这个方法中,通过反射实例化目的Fragment,然后replace原来的Fragment,并commit,这样目的Fragment就显示出来了。

    导航到其它Fragment

    通过Navigation.findNavController(v).navigate(resId)可以导航到指定的Fragment

    public static NavController findNavController(@NonNull View view) {
            NavController navController = findViewNavController(view);
            if (navController == null) {
                throw new IllegalStateException("View " + view + " does not have a NavController set");
            }
            return navController;
        }
    

    然后调用findViewNavController

    private static NavController findViewNavController(@NonNull View view) {
            while (view != null) {
                NavController controller = getViewNavController(view);
                if (controller != null) {
                    return controller;
                }
                ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
            return null;
        }
    

    这里通过子View往父View不停查找NavController,这个NavController在前面onCreate的时候已经附加到了view上。

    找到NavController后,调用navigate。这个过程和前面第一次导航到起始Fragment是一样的流程,这里不再分析。

    其实这里只是比较粗的一个梳理,涉及很多细节并没有具体去看,暂且先有一个流程的印象吧。

    附上一张时序图

    Navigation时序图.png

  • 相关阅读:
    静态INCLUDE与动态INCLUDE的区别
    SQL注入
    SpringMVC
    设计模式—七大原则
    Demo小细节-2
    Java运算符
    Java内部类的基本解析
    接口类和抽象类
    Statement和PreparedStatement
    ArrayList,LinkedLIst,HashMap
  • 原文地址:https://www.cnblogs.com/milovetingting/p/12723281.html
Copyright © 2011-2022 走看看