> 文章列表 > NavigationView 无法监听选择事件

NavigationView 无法监听选择事件

NavigationView 无法监听选择事件

背景

通过AS创建一个新的"Navigation Drawer Activity"项目,希望通过预构建的界面缩减代码开发时间,该功能希望能在选择不同的navigation后有不同的事件(比如密码验证)

问题

@Override
protected void onCreate(Bundle savedInstanceState) {...DrawerLayout drawer = binding.drawerLayout;NavigationView navigationView = binding.navView;navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(@NonNull MenuItem item) {Log.d(TAG, "onNavigationItemSelected: item = " + item.getTitle());return false;}});mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_recognize, R.id.nav_register, R.id.nav_admin).setOpenableLayout(drawer).build();navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);NavigationUI.setupWithNavController(navigationView, navController);....
}

通过上述方法无法给navigationview 设置选择监听,选择其他navigation后,没有log打印

分析

由于与构建的项目使用了如下代码:

NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
在代码NavigationUI.setupWithNavController方法中有默认设置navigationView 的 NavigationItemSelectedListener监听:
androidx.navigation.ui.NavigationUI.javapublic static void setupWithNavController(@NonNull final NavigationView navigationView,@NonNull final NavController navController) {// 给navigationView 设置监听方法navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(@NonNull MenuItem item) {// 选择了一个Item后,跳转到相对应的fragment中boolean handled = onNavDestinationSelected(item, navController);if (handled) {ViewParent parent = navigationView.getParent();if (parent instanceof Openable) {((Openable) parent).close(); // 关闭navigationView侧边栏} else {BottomSheetBehavior bottomSheetBehavior =findBottomSheetBehavior(navigationView);if (bottomSheetBehavior != null) {bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);}}}return handled;}});final WeakReference<NavigationView> weakReference = new WeakReference<>(navigationView);// 此方法监听切换fragment事件,保证切换后,对应的item可以被选中navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {@Overridepublic void onDestinationChanged(@NonNull NavController controller,@NonNull NavDestination destination, @Nullable Bundle arguments) {NavigationView view = weakReference.get();if (view == null) {navController.removeOnDestinationChangedListener(this);return;}Menu menu = view.getMenu();for (int h = 0, size = menu.size(); h < size; h++) {MenuItem item = menu.getItem(h);item.setChecked(matchDestination(destination, item.getItemId()));}}});
}

可以看出,我们设置NavigationItemSelectedListener后,setupWithNavController()方法又设置了一次,导致我们设置的监听被替换了。
所以可以有如下方法进行修改:

  1. 将我们的监听方法放在setupWithNavController()方法之后
  2. 在需要验证密码的fragment中跳转到密码验证界面
  3. 本地重新实现setupWithNavController()方法,将我们的功能做到自己实现的setupWithNavController()方法中

验证

首先验证方法1,修改如下:

@Override
protected void onCreate(Bundle savedInstanceState) {...DrawerLayout drawer = binding.drawerLayout;NavigationView navigationView = binding.navView;mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_recognize, R.id.nav_register, R.id.nav_admin).setOpenableLayout(drawer).build();navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);NavigationUI.setupWithNavController(navigationView, navController);// 设置选择监听navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(@NonNull MenuItem item) {Log.d(TAG, "onNavigationItemSelected: item = " + item.getTitle());return false;}});....
}

修改设置监听的顺序为setupWithNavController()方法之后,可以正常打印出我们的log,同时出现了一个新的问题:
setupWithNavController()方法中的listener不生效了,这个listener中当我们选中其他navigation后,会切换到相应的界面,没有了这个监听,导致点击navigation后没有任何效果。

验证方法2,修改如下:

AdminFragment.java@Override
public void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);DBOpenHelper helper = DBOpenHelper.getInstance(getActivity());boolean adminRegisted = helper.adminRegisted();if (adminRegisted) {startVerify();} else {startAdminRegiste(false);}
}private void startVerify() {Intent intent = new Intent();intent.setClassName("com.mobiletek.facerecognition","com.mobiletek.facerecognition.ui.admin.AdminVerifyActivity");startActivityForResult(intent,VERIFY_CODE);
}

当我们点击Admin navigation后,跳转到AdminFragment界面,在onCreate()方法中跳转到验证界面
当前方法会出现新问题,在我们点击navigation后,会先切换到adminfragment界面,然后在启动验证界面,导致屏幕会闪烁一下(在性能差的设备上尤其明显,可能会显示adminfragment界面一两秒后才会显示验证界面)

验证方法3,修改如下:

protected void onCreate(Bundle savedInstanceState) {...DrawerLayout drawer = binding.drawerLayout;NavigationView navigationView = binding.navView;mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow).setOpenableLayout(drawer).build();navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);setupWithNavController(navigationView, navController);}// 将NavigationUI.java中的方法cp到当前位置,修改为我们需要的功能public void setupWithNavController(@NonNull final NavigationView navigationView,@NonNull final NavController navController) {navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(@NonNull MenuItem item) {Log.d(TAG, "onNavigationItemSelected: " + navigationView.getCheckedItem().getTitle());MenuItem checkedItem = navigationView.getCheckedItem();boolean handled = false;if (checkedItem != null && checkedItem.getItemId() == item.getItemId()) {handled = true;} else {// 首先保存我们需要跳转的界面,待验证通过后再跳转needToItem = item;if (item.getItemId() == R.id.nav_gallery){Intent intent = new Intent();intent.setClassName("com.almalence.myapplication","com.almalence.myapplication.MainActivity2");mContext.startActivityForResult(intent, VERIFY_CODE);} else {// 切换到需要显示的界面handled = NavigationUI.onNavDestinationSelected(item, navController);}}ViewParent parent = navigationView.getParent();if (parent instanceof Openable) {((Openable) parent).close();}return handled;}});final WeakReference<NavigationView> weakReference = new WeakReference<>(navigationView);navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {@Overridepublic void onDestinationChanged(@NonNull NavController controller,@NonNull NavDestination destination, @Nullable Bundle arguments) {NavigationView view = weakReference.get();if (view == null) {navController.removeOnDestinationChangedListener(this);return;}Menu menu = view.getMenu();for (int h = 0, size = menu.size(); h < size; h++) {MenuItem item = menu.getItem(h);item.setChecked(matchDestination(destination, item.getItemId()));}}});}static boolean matchDestination(@NonNull NavDestination destination,@IdRes int destId) {NavDestination currentDestination = destination;while (currentDestination.getId() != destId && currentDestination.getParent() != null) {currentDestination = currentDestination.getParent();}return currentDestination.getId() == destId;}@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case VERIFY_CODE:if (resultCode == RESULT_OK)// 如果验证通过,跳转到之前选择的界面NavigationUI.onNavDestinationSelected(needToItem, navController);break;}}

此方法暂时没有其他问题,也没有闪屏的问题出现