Navigation 之 Fragment 切換

封面圖

本篇是有關(guān) Navigation 的第二篇,如有對(duì) Navigation 不了解的朋友請(qǐng)先閱讀來學(xué)一波 Navigation婚夫。

多次執(zhí)行 onCreateView

在上一篇中营密,我們利用 Navigation 與 BottomNavigationView 做出了一個(gè)有三個(gè) Tab 的頁面划址,分別是 Feed劲妙、Timer呢岗、Mine冕香,這三個(gè) Fragment 都是只在當(dāng)前頁面顯示各自的名稱蛹尝。

現(xiàn)在我們來給 TimerFragment 加點(diǎn)內(nèi)容,我們?cè)?TimerFragment 的 onCreateView 方法中啟動(dòng)一個(gè)倒計(jì)時(shí)悉尾。

private void startTimer() {
    new CountDownTimer(10 * 1000, 1000) {

        @Override
        public void onTick(long millisUntilFinished) {
            tvLabel.setText(String.valueOf((millisUntilFinished / 1000) + 1));
        }

        @Override
        public void onFinish() {
            tvLabel.setText("Finished");
        }
    }.start();
}

<img src="https://monster-image-backup.oss-cn-shanghai.aliyuncs.com/picgo/blog/blog_nav_tab.gif" style="zoom:33%;" />

仔細(xì)看上面的效果可以看到突那,每次切換到 TimerFragment 時(shí),倒計(jì)時(shí)總會(huì)重新開始构眯,不是我們想要的僅開始一次愕难。這是什么問題導(dǎo)致的呢?答案是 TimerFragment 執(zhí)行了多次的 onCreateView惫霸,為什么是會(huì)執(zhí)行多次猫缭,F(xiàn)ragment 為什么會(huì)加載多次?我們沒有什么特殊的操作呀壹店。是不是因?yàn)?Navigation猜丹?

現(xiàn)在讓我們深入到 Navigation 的源碼看一看這到底是怎么一回事,以及我們?cè)撊绾谓鉀Q這一問題硅卢。

首先射窒,我們需要明確我們的方向,就是 Navigation 到底是怎么做 Fragment 切換的将塑,為什么會(huì)導(dǎo)致 Fragment 的 onCreateView 被多次執(zhí)行轮洋。

從哪里作為入口呢?了解過 Navigation 的朋友對(duì)下面這行代碼應(yīng)該不會(huì)陌生抬旺,就是通過一個(gè) View 獲取到 NavController弊予,然后通過執(zhí)行 NavController 的 navigate 這個(gè)方法,我們就從這個(gè)方法開始开财。

Navigation.findNavController(view)
        .navigate(id);

這個(gè) navigate 有多個(gè)重載方法汉柒,我們開始的 navigate 方法最終也是執(zhí)行到下面這個(gè)重載方法。

navigate(NavDestination node, Bundle args, NavOptions navOptions, Navigator.Extras navigatorExtras)

方法的具體內(nèi)容如下圖:

image

其中在第 9 行责鳍,我們可以看到通過 mNavigatorProvider 獲取到了一個(gè)泛型類型為 NavDestination 的 Navigator 對(duì)象碾褂,并且在第 12 行時(shí),通過調(diào)用剛獲取到的 navigator 的 navigate 方法历葛,得到了 NavDestination 這個(gè)對(duì)象正塌。

這兩行是關(guān)鍵代碼,一個(gè)是獲取到執(zhí)行 navigator 的對(duì)象恤溶,一個(gè)是實(shí)際執(zhí)行 navigate 的方法乓诽。看到這咒程,我們就只需要找到 Navigator 的 navigate 方法即可鸠天。不過,Navigator 這個(gè)只是一個(gè)抽象類帐姻,我們還需要繼續(xù)尋找它的實(shí)現(xiàn)類稠集。

快捷鍵:Implementation(s) Mac: option(?) + command(?)+B

image-20190724110340568

Navigator 抽象類的關(guān)鍵代碼:

public abstract class Navigator<D extends NavDestination> {
    @Retention(RUNTIME)
    @Target({TYPE})
    @SuppressWarnings("UnknownNullness")
    public @interface Name {
        String value();
    }

    @NonNull
    public abstract D createDestination();

    @Nullable
    public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);

    public abstract boolean popBackStack();

    @Nullable
    public Bundle onSaveState() {
        return null;
    }

    public void onRestoreState(@NonNull Bundle savedState) {
    }

    public interface Extras {
    }
}

通過快捷鍵我們能找到多個(gè)實(shí)現(xiàn)類奶段,有 ActivityNavigator、DialogFragmentNavigator 還有 FragmentNavigator 等剥纷,這里我們只關(guān)注 FragmentNavigator 這個(gè)類中的 navigate 這個(gè)方法痹籍。

image

別看這么多代碼,別害怕晦鞋,其實(shí)關(guān)鍵部分的代碼就是第 32 行 ft.replace(mContainerId, frag) 這里使用的是 FragmentTransaction 的 replace 方法词裤,這個(gè)方法不用說了吧。 replace 是移除了相同 id 的 fragment 然后再進(jìn)行 add 的鳖宾。

所以吼砂,看到這,我們也就知道了鼎文,為什么 TimerFragment 的 onCreateView 方法會(huì)被執(zhí)行多次了渔肩,原因就是在這。

規(guī)避 replace

找到原因了拇惋,那我們有什么方法去規(guī)避周偎,或者說去繞過這個(gè) replace 嗎?答案是有的撑帖。

還記得剛才我們找的下面這行代碼吧(忘記的蓉坎,請(qǐng)看第一張代碼圖的第 9 行),剛才我說胡嘿,通過 mNavigatorProvider 找到一個(gè)泛型類型為 NavDestination 的 Navigator 對(duì)象蛉艾,那它實(shí)際上是怎么找到的呢?是通過 node.getNavigatorName() 然后找的衷敌,這個(gè) node 是什么東西勿侯?以及 mNavigatorProvider.getNavigator 內(nèi)部究竟發(fā)生了什么?

Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
        node.getNavigatorName());

實(shí)際上這里的 node 就是一個(gè) NavDestination 對(duì)象缴罗,而一個(gè) NavDestination 對(duì)象就是對(duì)應(yīng)著 navigation graph 中的節(jié)點(diǎn)信息助琐。我用來演示的 Demo 的 navigation graph 文件如下:

<?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/tab_navigation"
    app:startDestination="@id/feedFragment">

    <fragment
        android:id="@+id/feedFragment"
        android:name="me.monster.blogtest.tab.FeedFragment"
        android:label="fragment_feed"
        tools:layout="@layout/fragment_feed" />
    <fragment
        android:id="@+id/timerFragment"
        android:name="me.monster.blogtest.tab.TimerFragment"
        android:label="fragment_timer"
        tools:layout="@layout/fragment_timer" />
    <fragment
        android:id="@+id/mineFragment"
        android:name="me.monster.blogtest.tab.MineFragment"
        android:label="fragment_mine"
        tools:layout="@layout/fragment_mine" />
</navigation>

node.getNavigatorName 返回的就是 fragment 節(jié)點(diǎn)的節(jié)點(diǎn)名稱 fragment,而 getNavigator 其實(shí)內(nèi)部就是維護(hù)了一個(gè)類型為 HashMap 的 mNavigators面氓,這個(gè) HashMap 存的 key 就是節(jié)點(diǎn)名稱兵钮,value 就是抽象類 Navigator 的實(shí)現(xiàn)類。而與 fragment 對(duì)應(yīng)的 FragmentNavigator 也存儲(chǔ)在其中舌界。

既然是存在一個(gè) map掘譬,并從中取出相對(duì)于的 Navigator 實(shí)現(xiàn)類,那我們能不能創(chuàng)建一個(gè)類并實(shí)現(xiàn) Navigator禀横,然后將 key屁药、value 添加到那個(gè) HashMap 中粥血。答案是可行的柏锄。在NavigatorProvider 這個(gè)類中有兩個(gè)公共方法:

  • addNavigator(Navigator navigator)
  • addNavigator(String name, Navigator navigator)

其中酿箭,一個(gè)參數(shù)的 addNavigator 也是調(diào)用了 兩個(gè)參數(shù)的 addNavigator 方法,那個(gè) name 也就是 navigation graph 中 fragment 節(jié)點(diǎn)的節(jié)點(diǎn)名稱趾娃,同時(shí)也是 Navigator 這個(gè)抽象類中注解 Name 定義的值缭嫡。而且在 NavController 這個(gè)類(最初我們找到的 navigate 所在的類)中有一個(gè) getNavigatorProvider() 方法。

image

看到這抬闷,關(guān)系應(yīng)該就比較清楚了妇蛀。所以,我們需要自己創(chuàng)建一個(gè)類笤成,實(shí)現(xiàn) Navigator 并為 Name 注解添加一個(gè)值评架,然后在使用 Navigation 這個(gè)模塊的 Activity 獲取到 NavController 并調(diào)用其 getNavigatorProvider 方法后再調(diào)用 addNavigator 即可。

自定義 Navigator

Github 上已經(jīng)有一個(gè)演示自定義實(shí)現(xiàn) Navigator 的項(xiàng)目了炕泳。這個(gè)項(xiàng)目是以 Kotlin 語言編寫的纵诞。

項(xiàng)目地址: https://github.com/STAR-ZERO/navigation-keep-fragment-sample

說起來這個(gè)項(xiàng)目還是 Drakeet 在他的知識(shí)星球中分享的培遵。感謝 Drakeet 的分享浙芙。

我根據(jù)按照他的代碼寫了一份 Java 版本的,并且在其中改了兩行代碼(注釋部分)籽腕。注釋的內(nèi)容其實(shí)就是使用 FragmentTranslation 對(duì) Fragment 進(jìn)行控制嗡呼。原作者寫的是 detach 與 attach 方法,我改成了使用 hide 和 show 方法皇耗。

@Navigator.Name("keep_state_fragment")
public class KeepStateNavigator extends FragmentNavigator {
    private Context context;
    private FragmentManager manager;
    private int containerId;

    public KeepStateNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
        super(context, manager, containerId);
        this.context = context;
        this.manager = manager;
        this.containerId = containerId;
    }

    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        String tag = String.valueOf(destination.getId());
        FragmentTransaction transaction = manager.beginTransaction();
        boolean initialNavigate = false;
        Fragment currentFragment = manager.getPrimaryNavigationFragment();
        if (currentFragment != null) {
//            transaction.detach(currentFragment);
            transaction.hide(currentFragment);
        } else {
            initialNavigate = true;
        }
        Fragment fragment = manager.findFragmentByTag(tag);
        if (fragment == null) {
            String className = destination.getClassName();
            fragment = manager.getFragmentFactory().instantiate(context.getClassLoader(), className);
            transaction.add(containerId, fragment, tag);
        } else {
//            transaction.attach(fragment);
            transaction.show(fragment);
        }

        transaction.setPrimaryNavigationFragment(fragment);
        transaction.setReorderingAllowed(true);
        transaction.commitNow();
        return initialNavigate ? destination : null;
    }
}

這里的代碼沒有傳遞 Bundle 類型的 args 同時(shí)也破壞了在 navigation graph 中切換動(dòng)畫的設(shè)置南窗,如需要,自行加上即可郎楼》可參考 FragmentNavigator 類中實(shí)現(xiàn)。

感謝 Qwer 提出問題

注意箭启,使用自定義 Navigator 的時(shí)候 navigation graph 需要把 fragment 節(jié)點(diǎn)名稱改為 keep_state_fragment壕翩,并且在承載的 Activity 中進(jìn)行設(shè)置并且還需要把 Activity 布局文件中 fragment 的 navGraph 屬性移除。

NavController navController = Navigation.findNavController(this, R.id.fragment3);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment3);
KeepStateNavigator navigator = new KeepStateNavigator(this, navHostFragment.getChildFragmentManager(), R.id.fragment3);
navController.getNavigatorProvider().addNavigator(navigator);
navController.setGraph(R.navigation.tab_navigation);

最后來看一下使用自定義 Navigator 時(shí)的 TabActivity傅寡。

<img src="https://monster-image-backup.oss-cn-shanghai.aliyuncs.com/picgo/blog/blog_nav_tab_state.gif" style="zoom: 33%;" />

這樣好像看起來結(jié)束了放妈?其實(shí)并沒有,我們只是剛剛開始荐操。

首先芜抒,我先更正一下,在第一篇關(guān)于 Navigation 的博客中從 SettingFragment 返回到 RootFragment 那一段代碼有些問題托启。

沒看過那篇文章的不要著急宅倒,其實(shí)就是 A 調(diào)到 B,然后在 B 中觸發(fā)一個(gè)點(diǎn)擊事件屯耸,再從 B 返回到 A拐迁。返回的代碼如下蹭劈。

原代碼為:

btnToRoot.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Navigation.findNavController(btnToRoot)
                .popBackStack();
    }
});

這里在點(diǎn)擊事件中,最后執(zhí)行的是 popBackStack线召,其實(shí)不應(yīng)該調(diào)用這個(gè)方法應(yīng)該用 navigateUp 這個(gè)方法铺韧。

在第一篇博客中,Navigation Graph 中所有的節(jié)點(diǎn)名稱都是 Fragment缓淹,如果我用上面這種 keep_state_fragment 的方式哈打,會(huì)發(fā)生什么呢?

<img src="https://i.loli.net/2019/10/21/BAC6leVN1WhdaKJ.gif" style="zoom: 50%;" />

可以看到讯壶,在把 Navigation Graph 節(jié)點(diǎn)名替換為 keep_state_fragment 后料仗,在 SettingFragment 點(diǎn)擊返回并沒有進(jìn)行返回。這是為什么呢伏蚊?我沒干啥呀罢维,怎么不好使了。

不行丙挽,我要看看 Navigation 源碼里面到底怎么做的肺孵。于是我開始了 debug 之旅。后來颜阐,我發(fā)現(xiàn)在 Navigation.findNavController(btnToRoot).navigateUp(); 內(nèi)部判斷了當(dāng)前的返回棧個(gè)數(shù)是否為 1平窘,結(jié)果讓我很震驚,返回的竟然真的是 1凳怨。所以瑰艘,navigateUp 就理所當(dāng)然的返回 false,也就沒能從 SettingFragment 回到 RootFragment 了肤舞。

下面的兩段代碼分別是:NavController#navigateUp 和 NavController#getDestinationCountOnBackStack

image
private int getDestinationCountOnBackStack() {
    int count = 0;
    for (NavBackStackEntry entry : mBackStack) {
        if (!(entry.getDestination() instanceof NavGraph)) {
            count++;
        }
    }
    return count;
}

我查了一下 mBackStack 這個(gè)數(shù)據(jù)類型紫新,發(fā)現(xiàn)它是一個(gè)棧,緊接著找到 mBackStack 入棧的方法李剖。

image

mBackStack#add 相關(guān)的方法一共有 4 個(gè)芒率,第一個(gè)方法是在 NavController#NavController 方法中進(jìn)行調(diào)用的,其余 3 個(gè) add 相關(guān)方法均是在 NavController#navigate 內(nèi)調(diào)用篙顺,而調(diào)用 add 方法之外有一個(gè)判空偶芍。判空的對(duì)象就是來自 Navigator#navigate 這個(gè)方法。

NavController 的 navigate 方法德玫,有刪減匪蟀。

private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
        @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
    //......
    Navigator<NavDestination> navigator = mNavigatorProvider
           .getNavigator(node.getNavigatorName());
    Bundle finalArgs = node.addInDefaultArgs(args);
    NavDestination newDest = navigator.navigate(node, finalArgs,
            navOptions, navigatorExtras);
    if (newDest != null) {
        // The mGraph should always be on the back stack after you navigate()
        if (mBackStack.isEmpty()) {
            mBackStack.add(new NavBackStackEntry(mGraph, finalArgs));
        }
        // Now ensure all intermediate NavGraphs are put on the back stack
        // to ensure that global actions work.
        ArrayDeque<NavBackStackEntry> 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));
            }
            destination = parent;
        }
        mBackStack.addAll(hierarchy);
        // And finally, add the new destination with its default args
        NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest,
                newDest.addInDefaultArgs(finalArgs));
        mBackStack.add(newBackStackEntry);
    }
  //......
}

根據(jù)我們之前的經(jīng)驗(yàn),可以得出這里的 Navigator 就是我們自定義的 KeepStateNavigator 這個(gè)對(duì)象宰僧,那 navgate 這個(gè)方法的返回值也就是我們自己控制的材彪,也就是我們自己給自己挖了個(gè)坑。2333~

來吧,來看一下剛才寫的代碼段化。

public NavDestination navigate(Destination destination, Bundle args, NavOptions navOptions, Navigator.Extras navigatorExtras) {
    String tag = String.valueOf(destination.getId());
    FragmentTransaction transaction = manager.beginTransaction();
    boolean initialNavigate = false;
    Fragment currentFragment = manager.getPrimaryNavigationFragment();
    if (currentFragment != null) {
      transaction.hide(currentFragment);
    } else {
      initialNavigate = true;
    }
    Fragment fragment = manager.findFragmentByTag(tag);
    if (fragment == null) {
      String className = destination.getClassName();
      fragment = manager.getFragmentFactory().instantiate(context.getClassLoader(), className);
      transaction.add(containerId, fragment, tag);
    } else {
      transaction.show(fragment);
    }

    transaction.setPrimaryNavigationFragment(fragment);
    transaction.setReorderingAllowed(true);
    transaction.commitNow();
    return initialNavigate ? destination : null;
  }

在最后一行中嘁捷,我們通過對(duì) initialNavigate 進(jìn)行判斷然后返回 null 或是 destination 對(duì)象。而把 initialNavigate 賦值為 true 則是只有在 currentFragment 為空時(shí)才會(huì)進(jìn)行穗泵,什么時(shí)候 currentFragment 才會(huì)為空普气?只有當(dāng)打開一個(gè) Activity 并為其填充第一個(gè) Fragment 時(shí)才會(huì)為 true谜疤,在我們當(dāng)前這個(gè)場景里佃延,就是當(dāng)應(yīng)用啟動(dòng),打開 RootFragment 時(shí) initialNavigate 為 true夷磕,從 RootFragment 跳轉(zhuǎn)到 SettingFragment 時(shí) initialNavigate 為 false履肃。

這顯然是有問題的,那么我們需要改為坐桩,當(dāng)這個(gè) fragmen 為空時(shí)尺棋,在 transaction.add(containerId, fragment, tag); 之后把 initialNavigate 賦值為 true。這樣一來绵跷,NavController#getDestinationCountOnBackStack 就能獲取到實(shí)際的 fragment 大小了膘螟,也就不會(huì)直接 return fase 了。

運(yùn)行一下看看結(jié)果碾局?別著急啊荆残,再檢查檢查。剛才我說在 Navigation.findNavController(btnToRoot).navigateUp(); 內(nèi)部判斷了當(dāng)前的返回棧個(gè)數(shù)是否為 1净当,現(xiàn)在我們把為 1 的情況解決了内斯,那么當(dāng)返回棧的個(gè)數(shù)不為 1 時(shí)它怎么做的?在判斷返回棧個(gè)數(shù)不是 1 的后經(jīng)過內(nèi)部調(diào)用像啼,最終來到了 NavController#popBackStackInternal 這個(gè)方法內(nèi)俘闯。

NavController 的 popBackStackInternal 方法,有刪減

boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
    if (mBackStack.isEmpty()) {
        // Nothing to pop if the back stack is empty
        return false;
    }
    ArrayList<Navigator> popOperations = new ArrayList<>();
    Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
    boolean foundDestination = false;
    while (iterator.hasNext()) {
        NavDestination destination = iterator.next().getDestination();
        Navigator navigator = mNavigatorProvider.getNavigator(
                destination.getNavigatorName());
        if (inclusive || destination.getId() != destinationId) {
            popOperations.add(navigator);
        }
    //......
    boolean popped = false;
    for (Navigator navigator : popOperations) {
        if (navigator.popBackStack()) {
            NavBackStackEntry entry = mBackStack.removeLast();
            popped = true;
     // ......
    return popped;
}

在這個(gè)方法內(nèi)忽冻,我又看到了那個(gè)熟悉的面孔 navigator真朗,在這里 navigator 執(zhí)行了一個(gè)叫 popBackStack 的方法,這個(gè)方法看起來好像就是做返回事件的僧诚∶刍可是,我們的 KeepStateNavigator 并沒有這個(gè)方法啊振诬,那是因?yàn)槲覀冞x擇了繼承自 FragmentNavigator蹭睡,在 FragmentNavigator 有一套 popBackStack 邏輯,不過我們用不了赶么。所以我們需要在 FragmentNavigator 進(jìn)行重寫這個(gè)方法肩豁。

由于我們需要返回到上一個(gè)頁面,所以我們也得有個(gè)管理?xiàng)#缓笤?KeepStateNavigator#navigate 方法中的 transaction.add(containerId, fragment, tag); 之后把當(dāng)前 Fragment 添加到返回棧中清钥,在 popBackStack 中根據(jù)一些條件再進(jìn)行 remove 即可琼锋。

image

這樣一來就可以了。

<img src="https://i.loli.net/2019/10/17/qn84IfTF7ybrViS.gif" style="zoom:50%;" />

不知道為什么祟昭,錄制的 gif 畫面一直在閃……

隨意切換

好了缕坎,這樣就可以了,終于可以愉快的使用 Navigation 了篡悟。直到有一天谜叹,老大找到我,跟我說了一個(gè)需求搬葬。

從 A 頁面進(jìn)入 B 頁面荷腊,再從 B 頁面進(jìn)入 C 頁面,在 C 頁面產(chǎn)生一個(gè)事件急凰,然后用戶返回時(shí)女仰,需要跳過 B,也就是從 C 直接回到 A抡锈。

問我這個(gè)能不能在 Navigation 上實(shí)現(xiàn)疾忍,我想了一下,說可以床三。下面就分享一下實(shí)現(xiàn)這種效果的思路一罩,我個(gè)人覺得可以有兩個(gè)解決方法,下面我依次來說一下勿璃。

假設(shè)頁面打開順序?yàn)椋篈擒抛、B、C补疑。

  • 第一種:優(yōu)先關(guān)閉

    當(dāng)從 C 返回到 A 時(shí)歧沪,其實(shí)并不一定是返回的時(shí)候進(jìn)行操作,可能是在某個(gè)事件產(chǎn)生之后莲组,這時(shí)就把 B 給關(guān)閉诊胞,此時(shí)回退棧里面也就只剩下 A 和 B。這時(shí)候只需要正常走頁面返回邏輯即可锹杈。

  • 第二種:優(yōu)先返回

    當(dāng)從 C 返回到 A 時(shí)撵孤,也可以直接跳過 B,具體方法為:當(dāng)從 C 點(diǎn)擊返回時(shí)竭望,觸發(fā)返回棧的操作邪码,當(dāng)完成返回操作后發(fā)現(xiàn)當(dāng)前頁面需要跳過時(shí),則繼續(xù)返回咬清,此時(shí)也就回到了 A闭专。

落實(shí)到 Navigation 中奴潘,就是在自定義 Navigator 中添加一些方法,然后在需要執(zhí)行此類操作的地方獲取到 Navigator 對(duì)象影钉,并進(jìn)行相關(guān)操作画髓。

獲取 Navigator 的相關(guān)代碼如下:

NavController navController = Navigation.findNavController(btnToRoot);
NavigatorProvider navigatorProvider = navController.getNavigatorProvider();
Navigator<?> navigator = navigatorProvider.getNavigator("keep_state_fragment");
if (navigator instanceof KeepStateNavigator) {
    ((KeepStateNavigator) navigator).closeMiddle(R.id.settingsFragment);
}

思考

我第一次學(xué)習(xí) Navigation 的時(shí)候就瞄了一眼,覺得這不就是個(gè) Fragment 的管理框架嗎平委?有什么的呀奈虾,比其他Fragment 的管理框架好嗎?看起來一般般啊廉赔。哇哦肉微,好復(fù)雜啊,算了昂勉,不看了浪册,知道大致怎么用就行扫腺「谡眨看著大家討論的越來越多,沒忍住笆环,又去仔細(xì)看了一下 Navigation 的使用攒至,以及稍微閱讀了源碼。不就是個(gè) Fragment 管理框架嗎躁劣?怎么搞這么復(fù)雜迫吐,什么 NavigatorProvider、Navigator账忘、Destination 這些都是啥啊志膀。

隨著我看的內(nèi)容越來越多,實(shí)踐的也變多了鳖擒,原生的 Navigation 越來不不能滿足需求了溉浙,才發(fā)現(xiàn),原來 Google 早就想到了蒋荚,只是沒有給我們提供具體的解決方法戳稽,只是把一些東西開放出來供開發(fā)者在不同場景下進(jìn)行自定義使用。

想想自己項(xiàng)目里的代碼期升,好像如果要擴(kuò)展的話惊奇,就得改動(dòng)較多原來的代碼,不能像 Navigation 這樣播赁,在需要改動(dòng)的時(shí)候颂郎,盡量不觸動(dòng)原有代碼,而通過接口容为、Provider乓序、泛型等更多是編碼技巧或是設(shè)計(jì)模式上的技巧來完成業(yè)務(wù)需求诞吱。

也不應(yīng)該把過多的心思花在各式各樣的第三方庫上,而是把更多的精力花在基礎(chǔ)技能上竭缝,雖然可能一時(shí)半會(huì)兒看不出什么結(jié)果房维,但這可能是笑到最后的方法。


本文首發(fā)于個(gè)人博客抬纸,文中全部源代碼已上傳至 GitHub咙俩,代碼分支為 closeBefore。喜歡本文的麻煩點(diǎn)個(gè)??湿故。

本文封面圖:Photo by Jo?o Silas on Unsplash

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阿趁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坛猪,更是在濱河造成了極大的恐慌脖阵,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墅茉,死亡現(xiàn)場離奇詭異命黔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)就斤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門悍募,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洋机,你說我怎么就攤上這事坠宴。” “怎么了绷旗?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵喜鼓,是天一觀的道長。 經(jīng)常有香客問我衔肢,道長庄岖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任膀懈,我火速辦了婚禮顿锰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘启搂。我一直安慰自己硼控,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布胳赌。 她就那樣靜靜地躺著牢撼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疑苫。 梳的紋絲不亂的頭發(fā)上熏版,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天纷责,我揣著相機(jī)與錄音,去河邊找鬼撼短。 笑死再膳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的曲横。 我是一名探鬼主播喂柒,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禾嫉!你這毒婦竟也來了灾杰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤熙参,失蹤者是張志新(化名)和其女友劉穎艳吠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孽椰,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昭娩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弄屡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片题禀。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鞋诗,死狀恐怖膀捷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情削彬,我是刑警寧澤全庸,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站融痛,受9級(jí)特大地震影響壶笼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雁刷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一覆劈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沛励,春花似錦责语、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至企蹭,卻和暖如春白筹,著一層夾襖步出監(jiān)牢的瞬間智末,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工徒河, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留系馆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓顽照,卻偏偏與公主長得像它呀,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棒厘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353