BottomNavigationView 與 Navigation使用遇見的一個小坑

BottomNavigationView 與 Navigation使用遇見的一個小坑

先說結(jié)論吧抗楔,只是一個小問題,自己記錄一下。如果大家是BottomNavigationView與NavController結(jié)合使用實現(xiàn)fragment的切換,發(fā)現(xiàn)無法切換,記得檢查一下你創(chuàng)建的menu文件與navigation文件中的id是否是一一對應(yīng)苹熏。

最近在開發(fā)一個項目,因為項目的UI比較少相對也比較簡單币喧,考慮用一單Activity的實現(xiàn)方式轨域,使用JetpackNavigation進行頁面導(dǎo)航,主界面的tab實現(xiàn)使用谷歌的BottomNavigationView控件杀餐。因為之前寫過Demo干发,所以以為可以信手拈來。

創(chuàng)建主界面的布局史翘,Menu文件枉长,以及navigation文件

  • 主界面布局
<?xml version="1.0" encoding="utf-8"?>
<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">

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

  <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:layout_constraintBottom_toTopOf="@id/nav_view"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:navGraph="@navigation/main_tab_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • Menu
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

  <item
    android:id="@+id/navigation_cruise"
    android:icon="@mipmap/main_tab_icon_cruise"
    android:title="@string/main_tab_title_cruise" />

  <item
    android:id="@+id/navigation_video"
    android:icon="@mipmap/main_tab_icon_video"
    android:title="@string/main_tab_title_video" />

  <item
    android:id="@+id/navigation_warning"
    android:icon="@mipmap/main_tab_icon_warning"
    android:title="@string/main_tab_title_warning" />
  <item
    android:id="@+id/navigation_profile"
    android:icon="@mipmap/main_tab_icon_profile"
    android:title="@string/main_tab_title_profile" />

</menu>
  • navigation
<?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/main_tab_navigation"
  app:startDestination="@id/navigation_cruise">

  <fragment
    android:id="@+id/cruiseFragment"
    android:name="com.gzwx.securityrobot.ui.main.cruise.CruiseFragment"
    android:label="CruiseFragment"
    tools:layout="@layout/fragment_cruise" />
  <fragment
    android:id="@+id/videoFragment"
    android:name="com.gzwx.securityrobot.ui.main.video.VideoFragment"
    android:label="VideoFragment"
    tools:layout="@layout/fragment_video" />
  <fragment
    android:id="@+id/warnigFragment"
    android:name="com.gzwx.securityrobot.ui.main.warning.WarningFragment"
    android:label="WarningFragment"
    tools:layout="@layout/fragment_warning" />
  <fragment
    android:id="@+id/profileFragment"
    android:name="com.gzwx.securityrobot.ui.main.profile.ProfileFragment"
    android:label="ProfileFragment"
    tools:layout="@layout/fragment_profile" />
</navigation>
  • MainActivity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initNavigation()
    }

    private fun initNavigation() {
        val navController = findNavController(R.id.nav_host_fragment)
        nav_view.setupWithNavController(navController)
        adjustNavigationIconSize(nav_view)
    }
}

大致的代碼結(jié)構(gòu)如上冀续,我以為這樣就可以實現(xiàn)切換了,之前的demo差不多也是這么個創(chuàng)建流程和使用方式必峰,可是洪唐,tab終未能如約的切換。最開始我以為是我自己哪里寫錯了吼蚁,上網(wǎng)搜了一圈各種教程桐罕,發(fā)現(xiàn)寫的都大差不差,也沒發(fā)現(xiàn)什么明顯的錯誤桂敛,后來debug了一下,發(fā)現(xiàn)在切換的時候拋出了異常:

java.lang.IllegalArgumentException: Navigation action/destination [package]:id/navigation_video cannot be found from the current destination Destination( [package]:id/cruiseFragment) label=CruiseFragment class= [package].ui.main.cruise.CruiseFragment

然后發(fā)現(xiàn)拋出異常的地方在NavigationUI的onNavDestinationSelected方法中

public static boolean onNavDestinationSelected(@NonNull MenuItem item,
            @NonNull NavController navController) {
        NavOptions.Builder builder = new NavOptions.Builder()
                .setLaunchSingleTop(true)
                .setEnterAnim(R.animator.nav_default_enter_anim)
                .setExitAnim(R.animator.nav_default_exit_anim)
                .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
                .setPopExitAnim(R.animator.nav_default_pop_exit_anim);
        if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
            builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
        }
        NavOptions options = builder.build();
        try {
            //TODO provide proper API instead of using Exceptions as Control-Flow.
            //具體報錯在這一行 
            navController.navigate(item.getItemId(), null, options);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

繼續(xù)跟蹤在NavController里面

public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras) {
        NavDestination currentNode = mBackStack.isEmpty()
                ? mGraph
                : mBackStack.getLast().getDestination();
        if (currentNode == null) {
            throw new IllegalStateException("no current navigation node");
        }
        @IdRes int destId = resId;
        final NavAction navAction = currentNode.getAction(resId);
        Bundle combinedArgs = null;
        if (navAction != null) {
            if (navOptions == null) {
                navOptions = navAction.getNavOptions();
            }
            destId = navAction.getDestinationId();
            Bundle navActionArgs = navAction.getDefaultArguments();
            if (navActionArgs != null) {
                combinedArgs = new Bundle();
                combinedArgs.putAll(navActionArgs);
            }
        }

        if (args != null) {
            if (combinedArgs == null) {
                combinedArgs = new Bundle();
            }
            combinedArgs.putAll(args);
        }

        if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) {
            popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
            return;
        }

        if (destId == 0) {
            throw new IllegalArgumentException("Destination id == 0 can only be used"
                    + " in conjunction with a valid navOptions.popUpTo");
        }

        NavDestination node = findDestination(destId);
        if (node == null) {
            final String dest = NavDestination.getDisplayName(mContext, destId);
            if (navAction != null) {
                throw new IllegalArgumentException("Navigation destination " + dest
                        + " referenced from action "
                        + NavDestination.getDisplayName(mContext, resId)
                        + " cannot be found from the current destination " + currentNode);
            } else {
                throw new IllegalArgumentException("Navigation action/destination " + dest
                        + " cannot be found from the current destination " + currentNode);
            }
        }
        navigate(node, combinedArgs, navOptions, navigatorExtras);
    }

看到這里溅潜,大家應(yīng)該明白术唬,在NavController里面,NavDestination會根據(jù)destId查找下一個跳轉(zhuǎn)的dest滚澜,而這個destId就是傳進來的menuItem的Id粗仓。我們在主界面的布局文件中用來展示Fragment的宿主控件是"androidx.navigation.fragment.NavHostFragment"

<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:layout_constraintBottom_toTopOf="@id/nav_view"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:navGraph="@navigation/main_tab_navigation" />

這里有個屬性 app:navGraph,具體查看這個NavHostFragment的源碼设捐,我們會發(fā)現(xiàn)它會基于這個navGraph指向的xml文件進行一個導(dǎo)航圖表的創(chuàng)建借浊,因為在navigation的每一個fragment中我們都設(shè)置了一個id,可以想象的是創(chuàng)建的這個導(dǎo)航圖萝招,肯定與這個id相關(guān)蚂斤,那么navController調(diào)用navigate的時候如果傳入一個導(dǎo)航圖中不存在的id,肯定是無法找到合適的路由進行跳轉(zhuǎn)的槐沼,所以這里曙蒸,我們要保證Menu中的每一個Item的Idnavigation.xml中的每一個fragment的Id保持一致。最終修改的menu與navigation的文件如下

  • Menu
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

  <item
    android:id="@+id/navigation_cruise"
    android:icon="@mipmap/main_tab_icon_cruise"
    android:title="@string/main_tab_title_cruise" />

  <item
    android:id="@+id/navigation_video"
    android:icon="@mipmap/main_tab_icon_video"
    android:title="@string/main_tab_title_video" />

  <item
    android:id="@+id/navigation_warning"
    android:icon="@mipmap/main_tab_icon_warning"
    android:title="@string/main_tab_title_warning" />
  <item
    android:id="@+id/navigation_profile"
    android:icon="@mipmap/main_tab_icon_profile"
    android:title="@string/main_tab_title_profile" />

</menu>
  • naviagtion
<?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/main_tab_navigation"
  app:startDestination="@id/navigation_cruise">

  <fragment
    android:id="@+id/navigation_cruise"
    android:name="com.gzwx.securityrobot.ui.main.cruise.CruiseFragment"
    android:label="CruiseFragment"
    tools:layout="@layout/fragment_cruise" />
  <fragment
    android:id="@+id/navigation_video"
    android:name="com.gzwx.securityrobot.ui.main.video.VideoFragment"
    android:label="VideoFragment"
    tools:layout="@layout/fragment_video" />
  <fragment
    android:id="@+id/navigation_warning"
    android:name="com.gzwx.securityrobot.ui.main.warning.WarningFragment"
    android:label="WarningFragment"
    tools:layout="@layout/fragment_warning" />
  <fragment
    android:id="@+id/navigation_profile"
    android:name="com.gzwx.securityrobot.ui.main.profile.ProfileFragment"
    android:label="ProfileFragment"
    tools:layout="@layout/fragment_profile" />
</navigation>

當然岗钩,這里的id我個人覺得其實可以在values中創(chuàng)建一個ids的文件纽窟,創(chuàng)建對應(yīng)的Id,然后在這兩個布局文件中直接@id指向即可兼吓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臂港,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子视搏,更是在濱河造成了極大的恐慌审孽,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑娜,死亡現(xiàn)場離奇詭異瓷胧,居然都是意外死亡,警方通過查閱死者的電腦和手機棚愤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門搓萧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杂数,“玉大人,你說我怎么就攤上這事瘸洛∽嵋疲” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵反肋,是天一觀的道長那伐。 經(jīng)常有香客問我,道長石蔗,這世上最難降的妖魔是什么罕邀? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮养距,結(jié)果婚禮上诉探,老公的妹妹穿的比我還像新娘。我一直安慰自己棍厌,他們只是感情好肾胯,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耘纱,像睡著了一般敬肚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上束析,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天艳馒,我揣著相機與錄音,去河邊找鬼员寇。 笑死鹰溜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的丁恭。 我是一名探鬼主播曹动,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牲览!你這毒婦竟也來了墓陈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤第献,失蹤者是張志新(化名)和其女友劉穎贡必,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庸毫,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡仔拟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了飒赃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片利花。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡科侈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炒事,到底是詐尸還是另有隱情臀栈,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布挠乳,位于F島的核電站权薯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏睡扬。R本人自食惡果不足惜盟蚣,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卖怜。 院中可真熱鬧屎开,春花似錦、人聲如沸韧涨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虑粥。三九已至,卻和暖如春宪哩,著一層夾襖步出監(jiān)牢的瞬間娩贷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工锁孟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彬祖,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓品抽,卻偏偏與公主長得像储笑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子圆恤,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容