Navigation深入淺出,到出神入化,再到實(shí)戰(zhàn)改造(三)

改造Navigation

目標(biāo):

  1. 摒棄xml文件,用注解的方式管理路由節(jié)點(diǎn)。利用映射關(guān)系,動(dòng)態(tài)生成路由節(jié)點(diǎn)配置文件
  2. 改造FragmentNavigator,,替換replace(),使用show(),hint()方式,路由Fragement

自定義注解處理器

1. 配置

gradle配置

//生成Json文件工具類
api 'com.alibaba:fastjson:1.2.59'
//注解處理器配置工具 
api 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

如果想要注解處理器能夠在編譯器生成代碼,需要做一個(gè)配置說明,這里有兩種配置方法:
具體參考這篇文章:Java AbstractProcessor實(shí)現(xiàn)自定義ButterKnife

注解處理器基本用法

//auto.service:auto-service使用時(shí)要添加這個(gè)注解
@AutoService(Processor.class)
// 項(xiàng)目配置 當(dāng)前正在使用的Java版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//要處理的注解類型的名稱(這里必須是完整的包名+類名
@SupportedAnnotationTypes({"org.devio.hi.nav_annotation.Destination"})
public class NavProcessor extends AbstractProcessor {
   
   @Override
    void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
      //處理器被初始化的時(shí)候被調(diào)用
    }
     
    
    boolean process(Set annotations, RoundEnvironment roundEnv) 
      //處理器處理自定義注解的地方
      return false
}

注解處理器的引用

//Kotkin項(xiàng)目用 kapt Java項(xiàng)目用 annotationProcessor 
kapt project(path:'nav-compiler')
api project(path:'nav-annotations')

下面會將用的方法做介紹, ==關(guān)于更多注解處理器和相關(guān)知識,可參考這幾篇文章:==

Java進(jìn)階--編譯時(shí)注解處理器(APT)詳解

Java AbstractProcessor實(shí)現(xiàn)自定義ButterKnife

JavaPoet的使用指南

Android AutoService 組件化

2. 創(chuàng)建項(xiàng)目

創(chuàng)建項(xiàng)目

這個(gè)工程會默認(rèn)生成Navigation+BottomNavigationView項(xiàng)目結(jié)構(gòu)。項(xiàng)目內(nèi)容比較簡單。這里不過多介紹。我們就改造這個(gè)項(xiàng)目。

創(chuàng)建兩個(gè)Java lib :

在這里插入圖片描述

為什么需要?jiǎng)?chuàng)建Java庫? 創(chuàng)建Java庫是因?yàn)樵谑褂米远xAbstractProcessor需要使用到j(luò)avax包中的相關(guān)類和接口,這個(gè)在android庫中并不存在,所以需要使用到Java庫。

nav_compiler module下的build.gradle:

dependencies {
    implementation fileTree(dir: 'libs', includes: ['*,jar'])

    //自定義注解處理器相關(guān)依賴

    //Json工具類
    api 'com.alibaba:fastjson:1.2.59'
    //讓自定義處理器在編譯時(shí) 能夠被喚醒 能夠執(zhí)行
    api 'com.google.auto.service:auto-service:1.0-rc6'
    //添加我們定義的注解lib依賴
    implementation project(path: ':nav_annotation')
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

}

nav_annotation module下創(chuàng)建注解文件:

/**
 * 自定義注解,將這個(gè)注解
 * 注釋到我們的要路由的類上面
 * 這樣我們就可以獲取配置的節(jié)點(diǎn)(e.g Activity/Fragment/Dialog)
 * 然后利用代碼生成節(jié)點(diǎn)配置,替換掉nav_graph.xml;
 */
@Target(ElementType.TYPE)//類作用域
@Retention(RetentionPolicy.CLASS)//編譯期生效
public @interface Destination {

    /**
     * 頁面在路由中的名稱
     */
    String pareUrl();

    /**
     * 節(jié)點(diǎn)是不是默認(rèn)首次啟動(dòng)頁
     */
    boolean asStarter() default false;
}

在這里我們有必要認(rèn)識一下什么是Element。 在Java語言中,Element是一個(gè)接口,表示一個(gè)程序元素,它可以指代包、類、方法或者一個(gè)變量。Element已知的子接口有如下幾種:

  • PackageElement 表示一個(gè)包程序元素。提供對有關(guān)包及其成員的信息的訪問。
  • ExecutableElement 表示某個(gè)類或接口的方法、構(gòu)造方法或初始化程序(靜態(tài)或?qū)嵗?,包括注釋類型元素?/li>
  • TypeElement 表示一個(gè)類或接口程序元素。提供對有關(guān)類型及其成員的信息的訪問。注意,枚舉類型是一種類,而注解類型是一種接口。
  • VariableElement 表示一個(gè)字段、enum 常量、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù)。

注解解釋器具體代碼如下:

/**
 * @Author :ggxz
 * @Date: 2022/3/5
 * @Desc:
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"org.devio.hi.nav_annotation.Destination"})
public class NavProcessor extends AbstractProcessor {
    private static final String PAGE_TYPE_ACTIVITY = "Activity";
    private static final String PAGE_TYPE_FRAGMENT = "Fragment";
    private static final String PAGE_TYPE_DIALOG = "Dialog";
    private static final String OUTPUT_FILE_NAME = "destination.json";

    private Messager messager;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        //日志打印工具類
        messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, "enter init...");

        //創(chuàng)建打印文件
        filer = processingEnv.getFiler();


    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //獲取代碼中所有使用@Destination 注解的類或字段
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Destination.class);
        if (!elementsAnnotatedWith.isEmpty()) {
            Map<String, JSONObject> destMap = new HashMap<>();
            handleDestination(elementsAnnotatedWith, destMap, Destination.class);

            try {
                //創(chuàng)建資源文件
                FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME);
                // 獲取創(chuàng)建資源文件默認(rèn)路徑: .../app/build/intermediates/javac/debug/classes/目錄下
                // 希望存放的目錄為: /app/main/assets/
                String resourcePath = resource.toUri().getPath();
                //  獲取 .../app 之前的路徑
                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();
                }

                String content = JSON.toJSONString(destMap);
                File outputFile = new File(assetsPath, OUTPUT_FILE_NAME);

                if (outputFile.exists()) {
                    outputFile.delete();
                }

                outputFile.createNewFile();

                FileOutputStream outputStream = new FileOutputStream(outputFile);
                OutputStreamWriter writer = new OutputStreamWriter(outputStream);
                writer.write(content);
                writer.flush();
                outputStream.close();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    private void handleDestination(Set<? extends Element> elements, Map<String, JSONObject> destMap, Class<Destination> aClass) {
        for (Element element : elements) {
            TypeElement typeElement = (TypeElement) element;

            //全類名
            String clzName = typeElement.getQualifiedName().toString();

            Destination annotation = typeElement.getAnnotation(aClass);
            String pageUrl = annotation.pageUrl();
            boolean asStart = annotation.asStart();
            //獲取目標(biāo)頁的id 用全類名的hasCode
            int id = Math.abs(clzName.hashCode());


            //獲取 注解標(biāo)記的類型(Fragment Activity Dialog)
            String destType = getDestinationType(typeElement);

            if (destMap.containsKey(pageUrl)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "不同頁面不允許使用相同的pageUrl:" + pageUrl);
            } else {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("pageUrl", pageUrl);
                jsonObject.put("asStarter", asStart);
                jsonObject.put("id", id);
                jsonObject.put("destType", destType);
                jsonObject.put("clzName", clzName);

                destMap.put(pageUrl, jsonObject);

            }
        }
    }

    private String getDestinationType(TypeElement typeElement) {
        //父類型
        TypeMirror typeMirror = typeElement.getSuperclass();
        //androidx.fragment.app.Fragment
        String superClzName = typeMirror.toString();

        if (superClzName.contains(PAGE_TYPE_ACTIVITY.toLowerCase())) {
            return PAGE_TYPE_ACTIVITY.toLowerCase();
        } else if (superClzName.contains(PAGE_TYPE_FRAGMENT.toLowerCase())) {
            return PAGE_TYPE_FRAGMENT.toLowerCase();
        } else if (superClzName.contains(PAGE_TYPE_DIALOG.toLowerCase())) {
            return PAGE_TYPE_DIALOG.toLowerCase();
        }
        //1. 這個(gè)父類型是類的類型,或是接口的類型
        if (typeMirror instanceof DeclaredType) {
            Element element = ((DeclaredType) typeMirror).asElement();
            //如果這個(gè)父類的類型 是類的類型
            if (element instanceof TypeElement) {
                //遞歸調(diào)用自己
                return getDestinationType((TypeElement) element);
            }

        }

        return null;
    }


}

主項(xiàng)目引用:

    api project(':nav_annotation')
    kapt project(':nav_compiler')
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    //添加這句
    id 'kotlin-kapt'
}

在路由節(jié)點(diǎn)頁面添加:

@Destination(pageUrl = "main/tabs/home", asStarter = true)
class HomeFragment : Fragment() {}

@Destination(pageUrl = "main/tabs/notifications", asStarter = false)
class NotificationsFragment : Fragment() {}

@Destination(pageUrl = "main/tabs/dashboard", asStarter = false)
class DashboardFragment : Fragment() {

點(diǎn)擊build->Rebuild Projiect,就可可以看到assets目錄下生成的destination.json文件:

{
  "main/tabs/dashboard": {
    "asStarter": false,
    "pageUrl": "main/tabs/dashboard",
    "id": 1537160370,
    "clzName": "org.devio.proj.navigatorrouter.ui.dashboard.DashboardFragment",
    "destType": "fragment"
  },
  "main/tabs/home": {
    "asStarter": true,
    "pageUrl": "main/tabs/home",
    "id": 524823610,
    "clzName": "org.devio.proj.navigatorrouter.ui.home.HomeFragment",
    "destType": "fragment"
  },
  "main/tabs/notifications": {
    "asStarter": false,
    "pageUrl": "main/tabs/notifications",
    "id": 1214358362,
    "clzName": "org.devio.proj.navigatorrouter.ui.notifications.NotificationsFragment",
    "destType": "fragment"
  }
}

接下來就開始加載這個(gè)文件,把他替換成mobile_navigation.xml。在解析加載之前,再次強(qiáng)調(diào)下,為什么要這么做。最終我們的目的是,通過此Json來配置我們的路由。進(jìn)行統(tǒng)一管理,解耦。解決不夠靈活,擺脫繁瑣的xml文件編寫。使得開發(fā)階段可以使用注解。編譯時(shí)自動(dòng)掃描配置,運(yùn)行時(shí)自行管理頁面映射。

接下來我們開始解析這個(gè)destination.json文件

1. 重寫FragmentNavigator replace()替換成show()/hide()

創(chuàng)建HiFragmentNavigator 類,并將FragmentNavigator 全部粘貼過去,同時(shí)修改public NavDestination navigate()方法的邏輯如下:


@Navigator.Name("hifragment")//1
public class HiFragmentNavigator extends Navigator<HiFragmentNavigator.Destination> {
    @Nullable
    @Override
    public NavDestination navigate(@NonNull HiFragmentNavigator.Destination destination, @Nullable Bundle args,
                                   @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
           ... 省略
           //2
//        Fragment frag = instantiateFragment(mContext, mFragmentManager,
//                className, args);

        //這里每次都會利用反射去實(shí)例化對象 這里我改成用Tag標(biāo)記
        //className=android.fragment.app.homeFragment  tag=HomeFragment
        String tag = className.substring(className.lastIndexOf(".") + 1);
        //不要每次都實(shí)例化對象
        Fragment frag = mFragmentManager.findFragmentByTag(tag);
        if (frag == null) {
            frag = instantiateFragment(mContext, mFragmentManager,
                    className, args);
        }


        //替換成 show() hide()
//        ft.replace(mContainerId, frag);
          //3
        if (!frag.isAdded()) {
            ft.add(mContainerId, frag, tag);
        }

        List<Fragment> fragments = mFragmentManager.getFragments();
        for (Fragment fragment : fragments) {
            //把其他的全部隱藏
            ft.hide(fragment);
        }
        //展示的頁面
        ft.show(frag);

   ... 省略
   //4
        ft.setReorderingAllowed(true);
        ft.commit();
    }
}
  1. Navigator要求子類,類頭必須添加@Navigator.Name注解標(biāo)識,參考其他子類可知
  2. 每次都會利用反射去實(shí)例化對象 這里改成用Tag標(biāo)記,隨后恢復(fù)
  3. 避免反復(fù)創(chuàng)建 添加。使用hide()/show()方式 不需要commit()
  4. 方法的最后會 ft.commit();

2. 創(chuàng)建Destination實(shí)體類

與NavProcessor中創(chuàng)建的Json文件中的實(shí)體,字段一一對應(yīng)

public class Destination {
    public String pageUrl;  //頁面url
    public int id;          //路由節(jié)點(diǎn)(頁面)的id
    public boolean asStarter;//是否作為路由的第一個(gè)啟動(dòng)頁
    public String destType;//路由節(jié)點(diǎn)(頁面)的類型,activity,dialog,fragment
    public String clzName;//全類名
}

3. 創(chuàng)建NavUtil解析類

/**
     * key:pageUrl value:Destination
     */
    private static HashMap<String, Destination> destinationHashMap;

    /**
     * 由于我們刪除掉mobile_navigation.xml文件,那我們就需要自己處理解析流程,然后把節(jié)點(diǎn)和各個(gè)類進(jìn)行關(guān)聯(lián)
     * 賦值給NavGraph
     *
     * @param activity             上下文
     * @param controller           控制器
     * @param childFragmentManager 必須是childFragmentManager 源碼中創(chuàng)建FragmentNavigator和DialogNavigator都是用的它
     * @param containerId          activity.xml中裝載NavHostFragment的id
     */
    public static void buildNavGraph(FragmentActivity activity,
                                     @NonNull NavController controller,
                                     FragmentManager childFragmentManager,
                                     int containerId) {


        //獲取json文件內(nèi)容
        String content = parseFile(activity, "destination.json");

        //json文件映射成實(shí)體HashMap
        destinationHashMap = JSON.parseObject(content, new TypeReference<HashMap<String, Destination>>() {
        }.getType());


        /**
         * 創(chuàng)建NavGraph  它是解析mobile_navigation.xml文件后,存儲所有節(jié)點(diǎn)的Destination
         *  我們解析的Destination節(jié)點(diǎn),最終都要存入NavGraph中
         */
        // 獲取Navigator管理器中的Map 添加Destination
        NavigatorProvider navigatorProvider = controller.getNavigatorProvider();
        //創(chuàng)建NavGraphNavigator 跳轉(zhuǎn)類
        NavGraphNavigator navigator = new NavGraphNavigator(navigatorProvider);
        // 最終目的是創(chuàng)建navGraph
        NavGraph navGraph = new NavGraph(navigator);


        //創(chuàng)建我們自定義的FragmentNavigator
        HiFragmentNavigator hiFragmentNavigator = new HiFragmentNavigator(activity, childFragmentManager, containerId);
        //添加到Navigator管理器中
        navigatorProvider.addNavigator(hiFragmentNavigator);

        //獲取所有value數(shù)據(jù)
        Iterator<Destination> iterator = destinationHashMap.values().iterator();

        while (iterator.hasNext()) {
            Destination destination = iterator.next();
            if (destination.destType.equals("activity")) {
                //如果是activity類型,上節(jié)源碼中分析,它的必要參數(shù)是ComponentName

                ActivityNavigator activityNavigator = navigatorProvider.getNavigator(ActivityNavigator.class);
                //通過activityNavigator得到ActivityNavigator.Destination
                ActivityNavigator.Destination node = activityNavigator.createDestination();
                node.setId(destination.id);
                node.setComponentName(new ComponentName(activity.getPackageName(), destination.clzName));

                //添加到我們的navGraph對象中 它存儲了所有的節(jié)點(diǎn)
                navGraph.addDestination(node);
            } else if ((destination.destType.equals("fragment"))) {
                HiFragmentNavigator.Destination node = hiFragmentNavigator.createDestination();
                node.setId(destination.id);
                node.setClassName(destination.clzName);

                navGraph.addDestination(node);
            } else if (destination.destType.equals("dialog")) {
                DialogFragmentNavigator dialogFragmentNavigator = navigatorProvider.getNavigator(DialogFragmentNavigator.class);
                DialogFragmentNavigator.Destination node = dialogFragmentNavigator.createDestination();
                node.setId(destination.id);
                node.setClassName(destination.clzName);

                navGraph.addDestination(node);
            }

            //如果當(dāng)前節(jié)點(diǎn)
            if (destination.asStarter) {
                navGraph.setStartDestination(destination.id);
            }
        }
        // 視圖navGraph和controller 相關(guān)聯(lián)
        controller.setGraph(navGraph);

    }

    private static String parseFile(Context context, String fileName) {

        AssetManager assetManager = context.getAssets();
        StringBuilder builder = null;
        try {
            InputStream inputStream = assetManager.open(fileName);
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

            builder = new StringBuilder();

            String line;
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }

            inputStream.close();
            reader.close();
            return builder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * main_tabs_config.json 通常由服務(wù)器下發(fā),告知我們那些menu需要展示
     * 自定義BottomBar的目的是 讓Tab和Destination建立映射關(guān)系
     * 根據(jù)pageUrl斷定那個(gè)menu對應(yīng)那個(gè)Destination
     *
     * 也就是bottom_nav_menu.xml文件 中的配置 按照對應(yīng)要求 改成json文件后端下發(fā)
     */
    public static void builderBottomBar(BottomNavigationView navView) {
        String content = parseFile(navView.getContext(), "main_tabs_config.json");
        BottomBar bottomBar = JSON.parseObject(content, BottomBar.class);
        List<BottomBar.Tab> tabs = null;
        tabs = Objects.requireNonNull(bottomBar).tabs;

        Menu menu = navView.getMenu();
        for (BottomBar.Tab tab : tabs) {
            if (!tab.enable)
                continue;
            Destination destination = destinationHashMap.get(tab.pageUrl);
            if (destinationHashMap.containsKey(tab.pageUrl)) {//pageUrl對應(yīng)不上 則表示無此頁面
                //對應(yīng)頁面節(jié)點(diǎn)的destination.id要和menuItem  id對應(yīng)
                if (destination!=null){
                    MenuItem menuItem = menu.add(0, destination.id, tab.index, tab.title);
                    menuItem.setIcon(R.drawable.ic_home_black_24dp);
                }
            }
        }
    }
}

此方法提供兩種能力buildNavGraph()

  1. 將Json文件看成原來的mobile_navigation.xml文件,由于是我們自定義的Json,Navigation無法解析,所以我們要解析成節(jié)點(diǎn),封裝成NavGraph(存儲導(dǎo)航文件所有節(jié)點(diǎn)信息),然后按照解析流程,封裝成不同的Destination。然后與controller形成聯(lián)系。==注意== 值得注意的是,生成FragmentNavigator.Destination時(shí),要用我們自定義的HiFragmentNavigator
  2. 提供頁面MenuItem動(dòng)態(tài)設(shè)置能力。文件服務(wù)端下發(fā),這樣。我們在顯示時(shí),就可以指定有個(gè)頁面,顯示與否。比如某個(gè)頁面未實(shí)名不顯示。后臺直接下發(fā)的文件,不包含這個(gè)節(jié)點(diǎn),或是我們可以用代碼進(jìn)行攔截。 數(shù)據(jù)與路由配置Json文件內(nèi)容映射對應(yīng),如下:
{
  "selectTab": 0,
  "tabs": [
    {
      "size": 24,
      "enable": true,
      "index": 0,
      "pageUrl": "main/tabs/home",
      "title": "Home"
    },
    {
      "size": 24,
      "enable": true,
      "index": 1,
      "pageUrl": "main/tabs/dashboard",
      "title": "Dashboard"
    },
    {
      "size": 40,
      "enable": false,
      "index": 2,
      "pageUrl": "main/tabs/notification",
      "title": "Notification"
    }
  ]
}

對應(yīng)實(shí)體:  
public class BottomBar {
    

    public int selectTab;//默認(rèn)選中下標(biāo)
    public List<Tab> tabs;

    public static class Tab {
        /**
         * size : 24  按鈕的大小
         * enable : true 是否可點(diǎn)擊 不可點(diǎn)擊則隱藏
         * index : 0 在第幾個(gè)Item上
         * pageUrl : main/tabs/home   和路由節(jié)點(diǎn)配置相同,不存在則表示無此頁面
         * title : Home  按鈕文本
         */

        public int size;
        public boolean enable;
        public int index;
        public String pageUrl;
        public String title;
    }
}

activity.xml刪除一下兩項(xiàng):

app:menu="@menu/bottom_nav_menu""

app:navGraph="@navigation/mobile_navigation"

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="0dp"
        android:layout_marginStart="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
         />

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

解綁mobile_navigation.xml文件 解綁app:menu="@menu/bottom_nav_menu文件 MainAcivity.class代碼:

val navController = findNavController(R.id.nav_host_fragment_activity_main)

        //NavHostFragment 容器
        val fragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main)
        NavUtil.buildNavGraph(
            this,
            navController,
            fragment!!.childFragmentManager,
            //容器 id
            R.id.nav_host_fragment_activity_main
        )

        //創(chuàng)建底部按鈕 刪除app:menu="@menu/bottom_nav_menu" 配置
        NavUtil.builderBottomBar(navView)

        //跳轉(zhuǎn)itemId就是我們在builderBottomBar中 MenuItem的 destination.id --> menuItem = menu.add(0, destination.id, tab.index, tab.title);的
        navView.setOnItemSelectedListener { item ->
            navController.navigate(item.itemId)
            true
        }

現(xiàn)在Navigation無須xml配置,路由注解即可實(shí)現(xiàn),切換不會重建Fragment和重建View,支持tab高定制聯(lián)動(dòng)功能。

主要說明都是方法中。實(shí)現(xiàn)此功能要求對Navgiation源碼有足夠的了解,和自定義注解器相關(guān)知識。看代碼如果難懂,下面對面幾篇文章并附送源碼:

Navigation深入淺出,到出神入化,再到實(shí)戰(zhàn)改造(一)

Navigation深入淺出,到出神入化,再到實(shí)戰(zhàn)改造(二)

Java AbstractProcessor實(shí)現(xiàn)自定義ButterKnife

Java進(jìn)階--編譯時(shí)注解處理器(APT)詳解

Java AbstractProcessor實(shí)現(xiàn)自定義ButterKnife

JavaPoet的使用指南

Android AutoService 組件化

Github地址 AS4.1以上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容