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() {
        @Override
        public 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.java

public static void setupWithNavController(@NonNull final NavigationView navigationView,
        @NonNull final NavController navController) {
    // 给navigationView 设置监听方法
    navigationView.setNavigationItemSelectedListener(
            new NavigationView.OnNavigationItemSelectedListener() {
                @Override
                public 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 weakReference = new WeakReference<>(navigationView);
    // 此方法监听切换fragment事件,保证切换后,对应的item可以被选中
    navController.addOnDestinationChangedListener(
            new NavController.OnDestinationChangedListener() {
                @Override
                public 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() {
        @Override
        public 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() {
                    @Override
                    public 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 weakReference = new WeakReference<>(navigationView);
        navController.addOnDestinationChangedListener(
                new NavController.OnDestinationChangedListener() {
                    @Override
                    public 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;
    }
    
    @Override
    protected 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;
        }
    }

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

你可能感兴趣的:(android开发,android,java,navigation)