jetpack的Navigation注意点

1,导航文件的注意点

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/fmt_a">
    <!-- app:startDestination="@id/fmt_a":开始时加载的Fragment,必须设置-->
    <!--一个fragment代表一个目标,也就是一个具体的Fragment-->
    <!--id:资源编号,name:资源类型,全类名(包名+类名),layout:fragment对应需要加载的布局资源,label:标签文本-->
    <fragment
        android:id="@+id/fmt_a"
        android:name="com.example.testnavigation.BlankFragment1"
        android:label="FragmentA"
        android:layout="@layout/fragment_blank1"
        >

    </fragment>
    <fragment
        android:id="@+id/fmt_b"
        android:name="com.example.testnavigation.BlankFragment2"
        android:label="FragmentA"
        tools:layout="@layout/fragment_blank2"></fragment>
    <fragment
        android:id="@+id/fmt_c"
        android:name="com.example.testnavigation.BlankFragment3"
        android:label="FragmentA"
        tools:layout="@layout/fragment_blank3"></fragment>
    <fragment
        android:id="@+id/fmt_d"
        android:name="com.example.testnavigation.BlankFragment4"
        android:label="FragmentA"
        tools:layout="@layout/fragment_blank4"></fragment>
    <!--一个activity代表一个目标,也就是一个具体的Activity-->
        <activity
            android:id="@+id/activity"
            android:name="com.example.testnavigation.MainActivity2"
            ></activity>

    <!--action表示路径,destination表示跳转的目的地,可以写在fragment/activity里面,也可以写在外面 -->
    <!--如果是用在底部导航栏的情况下,多写在外面。如果要使用BottomNavigationView,Menu中的ID,必须与nav_graph中的资源ID保持一致-->
    <action
        android:id="@+id/to2"
        app:destination="@+id/fmt_b"
        app:enterAnim="@anim/nav_default_enter_anim"
        app:exitAnim="@anim/nav_default_exit_anim" />
    <action
        android:id="@+id/toActivity"
        app:destination="@+id/activity"
        app:enterAnim="@anim/nav_default_enter_anim"
        app:exitAnim="@anim/nav_default_exit_anim" />
</navigation>

navigation携带参数跳转

  @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button btClicked = view.findViewById(R.id.btClicked);
        btClicked.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                bundle.putString("fromFragment1", "fromFragment1");
                //getView()的注释
//                Get the root view for the fragment's layout (the one returned by onCreateView), if provided.
//                Returns:The fragment's root view, or null if it has no layout.
                //具体跳转的代码,所有的跳转统一都在nav_graph里的<action/>
                Navigation.findNavController(getView()).navigate(R.id.to2,bundle);
            }
        });
    }

参数的接收

  //跳转的目的地如果是Fragment,那就从gerArguments里面找,
  getArguments().getString("fromFragment1")
 //如果跳转的目的地是activity,我们要这样写:getInten().getStringExtra("fromFragment1")
 getIntent().getStringExtra("fromFragment2")

深度链接

在这里插入图片描述

官方导航器缺点

因为官方考虑到实时性,每次切换都会用replace,,而不是show()和hide()每次都会新建Fragment。需要我们自己处理,扩展官方架构
在这里插入图片描述

package com.maniu.mn_vip_navigation.navigator;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.fragment.FragmentNavigator;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Map;

/**
 * 定制的Fragment导航器,替换ft.replace(mContainerId, frag);为 hide()/show()
 */
//框架内部导航跳转使用导航器。框架提供了四个导航器。框架是根据注解选择导航器,这里也需要增加注解
//虽然增加了注解, 但我们无法在nav_graph里写<fixfragment></fixfragment>标签
//解决方案:第一步:我们自己定义一个JSON文件
@Navigator.Name("fixfragment")
public class FixFragmentNavigator extends FragmentNavigator {
    private static final String TAG = "FixFragmentNavigator";
    private Context mContext;
    private FragmentManager mManager;
    private int mContainerId;

    public FixFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
        super(context, manager, containerId);
        mContext = context;
        mManager = manager;
        mContainerId = containerId;
    }

    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        //直接把源码复制过来加以修改
        if (mManager.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;
        }
        //注释掉这句话
        // 不需要每次去navigate的时候都去实例化fragment
        //final Fragment frag = instantiateFragment(mContext, mManager,
        //       className, args);
        //frag.setArguments(args);
        final FragmentTransaction ft = mManager.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
        Fragment fragment = mManager.getPrimaryNavigationFragment();
        //如果不为空  就隐藏
        if (fragment != null) {
            ft.hide(fragment);
        }
        //去获取目的地的Fragment 即将要显示的Fragment
        Fragment frag = null;
        String tag = String.valueOf(destination.getId());
        //去通过tag从manager中获取fragment
        frag = mManager.findFragmentByTag(tag);
        //如果不为空就显示
        if (frag != null) {
            ft.show(frag);
        } else {
            //如果为空就创建一个fragment的对象,而不是每次都创建一个Fragment
            frag = instantiateFragment(mContext, mManager, className, args);
            frag.setArguments(args);
            ft.add(mContainerId, frag, tag);
        }
        //不再需要replace
        //ft.replace(mContainerId, frag);
        //帮要显示的fragment设置成当前的fragment
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        //通过反射获取mBackStack 然后重新设置参数。因为在源代码中,mBackStack是私有的,只能通过反射获取
        ArrayDeque<Integer> mBackStack = null;
        try {
            Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
            field.setAccessible(true);
            //因为是反射父类的私有成员变量,这里可以直接传参数this
            mBackStack = (ArrayDeque<Integer>) field.get(this);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        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
                mManager.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;
        }
    }

    private String generateBackStackName(int backStackindex, int destid) {
        return backStackindex + "-" + destid;
    }
}


{
  "afragment": {
    "isFragment": true,
    "asStarter": true,
    "needLogin": false,
    "pageUrl": "afragment",
    "className": "com.maniu.mn_vip_navigation.Fragment.AFragment",
    "id": 40134018,
    "label": "首页"
  },
  "bfragment": {
    "isFragment": true,
    "asStarter": false,
    "needLogin": false,
    "pageUrl": "bfragment",
    "className": "com.maniu.mn_vip_navigation.Fragment.BFragment",
    "id": 1767320445,
    "label": "列表"
  },
  "efragment": {
    "isFragment": true,
    "asStarter": false,
    "needLogin": false,
    "pageUrl": "efragment",
    "className": "com.maniu.mn_vip_navigation.Fragment.EFragment",
    "id": 1400250758,
    "label": ""
  },
  "cfragment": {
    "isFragment": true,
    "asStarter": false,
    "needLogin": false,
    "pageUrl": "cfragment",
    "className": "com.maniu.mn_vip_navigation.Fragment.CFragment",
    "id": 720192388,
    "label": "购物车"
  },
  "dfragment": {
    "isFragment": true,
    "asStarter": false,
    "needLogin": false,
    "pageUrl": "dfragment",
    "className": "com.maniu.mn_vip_navigation.Fragment.DFragment",
    "id": 1087262075,
    "label": "我的"
  }
}

>
pageUrl:对应nav_graph中的的deepLink
id:对应nav_graph中的的id
label:对应nav_graph中的的label
className:对应nav_graph中的的name
是不是太麻烦了这样,还要写json文件,
没关系,我们可以用注解

package com.maniu.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
public @interface ActivityDestination {
    String pageUrl();

    boolean needLogin() default false;

    boolean asStarter() default false;
}

package com.maniu.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
public @interface FragmentDestination {
    String pageUrl();

    boolean needLogin() default false;

    boolean asStarter() default false;

    String label() default "";

}

写个注解处理器,自动生成json(类似于框架的nav_graph.xml)文件

package com.maniu.annotation_compiler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.auto.service.AutoService;
import com.maniu.annotation.ActivityDestination;
import com.maniu.annotation.FragmentDestination;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

/**
 * APP页面导航信息收集注解处理器
 * <p>
 * AutoService注解:就这么一标记,annotationProcessor  project()应用一下,编译时就能自动执行该类了。
 * <p>
 * SupportedSourceVersion注解:声明我们所支持的jdk版本
 * <p>
 * SupportedAnnotationTypes:声明该注解处理器想要处理那些注解
 */
@AutoService(Processor.class)
public class NavProcessor extends AbstractProcessor {
    private Messager messager;
    private Filer filer;
    private static final String OUTPUT_FILE_NAME = "destination.json";

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //日志打印,在java环境下不能使用android.util.log.e()
        messager = processingEnv.getMessager();
        //文件处理工具
        filer = processingEnv.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(FragmentDestination.class.getCanonicalName());
        types.add(ActivityDestination.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //通过处理器环境上下文roundEnv分别获取 项目中标记的FragmentDestination.class 和ActivityDestination.class注解。
        //此目的就是为了收集项目中哪些类 被注解标记了
        Set<? extends Element> fragmentElements = roundEnv.getElementsAnnotatedWith(FragmentDestination.class);
        Set<? extends Element> activityElements = roundEnv.getElementsAnnotatedWith(ActivityDestination.class);

        if (!fragmentElements.isEmpty() || !activityElements.isEmpty()) {
            HashMap<String, JSONObject> destMap = new HashMap<>();
            //分别 处理FragmentDestination  和 ActivityDestination 注解类型
            //并收集到destMap 这个map中。以此就能记录下所有的页面信息了
            handleDestination(fragmentElements, FragmentDestination.class, destMap);
            handleDestination(activityElements, ActivityDestination.class, destMap);

            //app/src/main/assets
            FileOutputStream fos = null;
            OutputStreamWriter writer = null;
            try {
                //filer.createResource()意思是创建源文件
                //我们可以指定为class文件输出的地方,
                //StandardLocation.CLASS_OUTPUT:java文件生成class文件的位置,/app/build/intermediates/javac/debug/classes/目录下
                //StandardLocation.SOURCE_OUTPUT:java文件的位置,一般在/ppjoke/app/build/generated/source/apt/目录下
                //StandardLocation.CLASS_PATH 和 StandardLocation.SOURCE_PATH用的不多,指的了这个参数,就要指定生成文件的pkg包名了
                FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME);
                String resourcePath = resource.toUri().getPath();
                messager.printMessage(Diagnostic.Kind.NOTE, "resourcePath:" + resourcePath);

                //由于我们想要把json文件生成在app/src/main/assets/目录下,所以这里可以对字符串做一个截取,
                //以此便能准确获取项目在每个电脑上的 /app/src/main/assets/的路径
                String appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4);
                String assetsPath = appPath + "src/main/assets/";

                File file = new File(assetsPath);
                if (!file.exists()) {
                    file.mkdirs();
                }

                //此处就是稳健的写入了
                File outPutFile = new File(file, OUTPUT_FILE_NAME);
                if (outPutFile.exists()) {
                    outPutFile.delete();
                }
                outPutFile.createNewFile();

                //利用fastjson把收集到的所有的页面信息 转换成JSON格式的。并输出到文件中
                String content = JSON.toJSONString(destMap);
                fos = new FileOutputStream(outPutFile);
                writer = new OutputStreamWriter(fos, "UTF-8");
                writer.write(content);
                writer.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }


        return true;
    }

    private void handleDestination(Set<? extends Element> elements, Class<? extends Annotation> annotationClaz,
                                   HashMap<String, JSONObject> destMap) {
        for (Element element : elements) {
            //TypeElement是Element的一种。
            //如果我们的注解标记在了类名上。所以可以直接强转一下。使用它得到全类名
            TypeElement typeElement = (TypeElement) element;
            //全类名com.mooc.ppjoke.home
            String clazName = typeElement.getQualifiedName().toString();
            //页面的id.此处不能重复,使用页面的类名做hascode即可,navigation框架内部走的也是Integer
            int id = Math.abs(clazName.hashCode());
            //页面的pageUrl相当于隐士跳转意图中的host://schem/path格式
            String pageUrl = null;
            //是否需要登录
            boolean needLogin = false;
            //是否作为首页的第一个展示的页面
            boolean asStarter = false;
            //标记该页面是fragment 还是activity类型的
            boolean isFragment = false;
            String label = null;
            //提取类的注解,生成json文件
            Annotation annotation = element.getAnnotation(annotationClaz);
            if (annotation instanceof FragmentDestination) {
                FragmentDestination dest = (FragmentDestination) annotation;
                pageUrl = dest.pageUrl();
                asStarter = dest.asStarter();
                needLogin = dest.needLogin();
                isFragment = true;
                label = dest.label();
            } else if (annotation instanceof ActivityDestination) {
                ActivityDestination dest = (ActivityDestination) annotation;
                pageUrl = dest.pageUrl();
                asStarter = dest.asStarter();
                needLogin = dest.needLogin();
                isFragment = false;
            }

            if (destMap.containsKey(pageUrl)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "不同的页面不允许使用相同的pageUrl:" + clazName);
            } else {
                JSONObject object = new JSONObject();
                object.put("id", id);
                object.put("needLogin", needLogin);
                object.put("asStarter", asStarter);
                object.put("pageUrl", pageUrl);
                object.put("className", clazName);
                object.put("isFragment", isFragment);
                object.put("label",label);
                destMap.put(pageUrl, object);
            }
        }
    }
}

在这里插入图片描述
创建三个解释器
算了不写了,直接上传源码吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值