为什么导航在 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:
- 使用导航抽屉 Activity 模板创建了默认 android 应用程序。
- 向项目添加了设置片段以测试
action_settings
菜单和配置菜单项。
- 覆盖
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 更改?我如何在不降级的情况下处理这个问题?
额外信息:
在 MainActivity
的 onCreate()
方法中,我添加了以下内容:
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
假设您已经交换到那个返回堆栈。由于您实际上没有这样做,这就是为什么您选择的项目与您所在的返回堆栈不同步的原因。
(使用 Android Studio 2021.1.1)
正在使用导航抽屉创建新项目Activity:
- 使用导航抽屉 Activity 模板创建了默认 android 应用程序。
- 向项目添加了设置片段以测试
action_settings
菜单和配置菜单项。 - 覆盖
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 更改?我如何在不降级的情况下处理这个问题?
额外信息:
在 MainActivity
的 onCreate()
方法中,我添加了以下内容:
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
假设您已经交换到那个返回堆栈。由于您实际上没有这样做,这就是为什么您选择的项目与您所在的返回堆栈不同步的原因。