为什么导航在 2.4.1 版的导航抽屉 Activity 模板中不起作用?

Why does navigation not work in the Navigation Drawer Activity template with version 2.4.1?

(使用 Android Studio 2021.1.1)

正在使用导航抽屉创建新项目Activity:

  1. 使用导航抽屉 Activity 模板创建了默认 android 应用程序。
  2. 向项目添加了设置片段以测试 action_settings 菜单和配置菜单项。
  3. 覆盖 MainActivity.java 中的 onOptionsItemSelected() 以处理设置菜单,如下所示:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    Bundle bundle = new Bundle();
        switch (item.getItemId()) {
            case R.id.action_settings:
                Navigation
                    .findNavController(this, R.id.nav_host_fragment_content_main)
                    .navigate(R.id.nav_settings);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

测试:

运行 项目,抽屉菜单工作正常并按预期打开片段。问题是,当您单击溢出菜单打开设置片段时它可以工作,但打开主片段时抽屉菜单不再工作。


观察:

经过测试,发现是依赖版本的问题,从2.4.1降级到2.3.5解决了问题。

我的代码有问题还是因为 API 更改?我如何在不降级的情况下处理这个问题?

额外信息:

MainActivityonCreate() 方法中,我添加了以下内容:

     mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow, R.id.nav_settings)
                .setOpenableLayout(drawer)
                .build();

app 模块的 build.gradle:

plugins {
    id 'com.android.application'
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 23
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 
            'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    buildFeatures {
        viewBinding true
    }

    buildToolsVersion '32.0.0'
        ndkVersion '23.1.7779620'
    }

    dependencies {
        implementation 'androidx.appcompat:appcompat:1.4.1'
        implementation 'com.google.android.material:material:1.5.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
        implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
        implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
        implementation 'androidx.navigation:navigation-fragment:2.4.1'
        implementation 'androidx.navigation:navigation-ui:2.4.1'
        implementation 'androidx.legacy:legacy-support-v4:1.0.0'
        
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.3'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    }
}

tl/dr:您应该遵循 Tie destinations to menu items documentation 并使用 NavigationUI.onNavDestinationSelected() 以获得正确的行为:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    NavController navController = Navigation.findNavController(this,
        R.id.nav_host_fragment_content_main);

    // By calling onNavDestinationSelected(), you always get the right behavior
    return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
}

为什么

导航 2.4 使用 multiple back stacks associated with each element in your NavigationView as per the Add a navigation drawer guide:

Starting in Navigation 2.4.0-alpha01, the state of each menu item is saved and restored when you use setupWithNavController.

这意味着 'Home' 屏幕有一个与之关联的返回堆栈,当您点击该图标时会恢复,画廊、幻灯片和设置也是如此。这是该项目的状态保存方式。

这意味着每次点击抽屉中的项目不仅导航到该屏幕,而且交换与该项目关联的整个返回堆栈 - 您从第一个屏幕导航到的所有内容。

因此,当您调用 Navigation.findNavController(this, R.id.nav_host_fragment_content_main).navigate(R.id.nav_settings); 时,您所做的事情与选择抽屉中的“设置”项目不同——您只是将“设置”屏幕添加到 'Home' 屏幕的返回堆栈.这就是为什么点击主页图标没有任何作用的原因 - 您已经在 'Home' 屏幕的后退堆栈中。

您真正想要做的是切换到与 nav_settings 项关联的完全独立的返回堆栈。这会将 nav_settings 返回堆栈与主页返回堆栈分开,从而确保点击主页图标会将您带回到主屏幕的返回堆栈。

这正是 NavigationUI.onNavDestinationSelected() API 所做的(因为这正是 setupWithNavController API 使用的),因此您可以直接在 [= =17=].

但是,如果您想手动调用 navigate()(顺便说一下,这意味着您不会获得使用 onNavDestinationSelected 时默认获得的淡入淡出动画),您可以添加 saving state flags to your navigate call by applying NavOptions programmatically:

@Override
 public boolean onOptionsItemSelected(MenuItem item) {
    Bundle bundle =new Bundle();
    switch (item.getItemId()) {
        case R.id.action_settings:
        {
            // Manually build the NavOptions that manually do
            // what NavigationUI.onNavDestinationSelected does for you
            NavOptions navOptions = new NavOptions.Builder()
                .setPopUpTo(R.id.nav_home, false, true)
                .setRestoreState(true)
                .build();

            NavController navController = Navigation.findNavController(this, 
                R.id.nav_host_fragment_content_main);

            navController.navigate(R.id.nav_settings, navOptions);
            return true;
        }

        default:
            return super.onOptionsItemSelected(item);
    }
}

请注意,setupWithNavController API 依赖于当前目的地的 nested graph 来确定选择了哪个项目 - 预期是 'Home' 中的所有目的地选项卡是 'Home' 导航图的一部分。因此,因为您已经交换到 nav_settings,setupWithNavController 假设您已经交换到那个返回堆栈。由于您实际上没有这样做,这就是为什么您选择的项目与您所在的返回堆栈不同步的原因。