【问题标题】:Update selected state of navigation drawer after back press后按后更新导航抽屉的选定状态
【发布时间】:2025-12-12 00:30:01
【问题描述】:

按下后处理导航抽屉的选定状态的正确方法是什么?

我有一个导航抽屉,其中包含 n 个条目(在列表视图中),例如 Android Studio 中的 SDK 示例。 当我单击导航抽屉条目时,我希望将它们添加到后台堆栈中,以便我可以移回它们。

在 onNavigationDrawerItemSelected(int pos) 我有

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        if (position == 0) {
            transaction.replace(R.id.container, new FragmentA());
        } else if (position == 1) {
            transaction.replace(R.id.container, new FragmentB());
        } else {
            transaction.replace(R.id.container, new FragmentC());
        }
        transaction.addToBackStack(null);
        transaction.commit();

当我单击抽屉中的第二个条目时,B 被选中并替换 A。如果我随后单击后退按钮,片段 A 会再次按原样显示,但 B 在导航抽屉中仍处于选中状态。

如何在按下返回后更新抽屉的选择状态?

不知何故,我需要调用 mDrawerListView.setItemChecked(position, true);或 NavigationDrawerFragment.selectItem(int position)。但是到哪个位置呢?我怎么记住它?

使用 onBackPressed 拦截?

@Override
    public void onBackPressed() {}

但是我怎么知道哪个片段再次处于活动状态?以及对应的位置。

有一些我看不到的简单解决方案吗?似乎将返回与抽屉导航结合使用并更新选择状态是一种标准模式。

【问题讨论】:

    标签: android android-fragments navigation navigation-drawer onbackpressed


    【解决方案1】:

    此模式在“正确的后退导航”文档的Implement Back Navigation for Fragments 部分中进行了描述。

    如果您的应用程序更新其他用户界面元素以反映 您的片段的当前状态,例如操作栏,请记住 提交事务时更新 UI。你应该更新 您的用户界面在返回堆栈更改后除了何时 你提交交易。 您可以在 FragmentTransaction 通过设置一个 FragmentManager.OnBackStackChangedListener

    getSupportFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });
    

    这将是刷新导航抽屉中当前选项的合适位置。

    【讨论】:

    • 这仅回答何时更新的问题。我也可以使用 onBackPressed()。主要问题是应该在抽屉中突出显示哪个位置?我可能需要在 onNavigationDrawerItemSelected 中获取旧的、现在“活动”的片段和映射片段-> 位置反转。也许使用 setTargetFragment 将位置存储为请求代码?
    • @spatialist 是的,这会起作用,然后在 Fragment 的onCreateView() 中通知抽屉。尽管如此,我仍然建议使用监听器而不是onBackPressed()。例如,按 Back 可能实际上不会更改当前片段(例如关闭搜索视图)。
    【解决方案2】:

    今天我也有同样的挣扎。我通过在每个片段和我唯一的 MainActivity 中实现一个接口 FragmentToActivity 来解决它。

    界面:

    public interface FragmentToActivity {
        public void SetNavState(int id);
    }
    

    片段:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ((FragmentToActivity) getActivity()).SetNavState(R.id.nav_myitemid); //just add this line
    }
    

    MainActivity:

    @Override
    public void SetNavState(int id) {
        NavigationView nav = (NavigationView) findViewById(R.id.nav_view);
        nav.setCheckedItem(id);
    }
    

    如果你不知道R.id.nav_myitemid 来自哪里,这里是我的activity_main_drawer.xml

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">    
        <group android:checkableBehavior="single">
            <item
                android:id="@+id/nav_myitemid"
                android:title="@string/home" />
        </group>
    </menu>
    

    【讨论】:

    • 太棒了!但是我有一个问题,为什么你使用接口函数而不是普通的公共函数?
    • @KhanStan99 他需要回调,setCheckedItem 只能从父活动而不是 Fragment 完成,
    • 又好又干净!
    【解决方案3】:

    我在试图找出解决这个问题的方法时遇到了很多麻烦,我最终使用的解决方案在我看来并不“好”,但它确实有效。

    在我的应用程序中,当我将片段添加到后台堆栈时,我最终在导航抽屉中添加了片段位置。这样我就可以用我的 onBackStackChanged() 中的位置调用 setItemChecked。所以基本上我所做的是:

    @Override
    public void onBackStackChanged() {
        FragmentManager fm = getFragmentManager();
        String name = fm.getBackStackEntryAt(fm.getBackStackEntryCount()-1).getName();
        mDrawerList.setItemChecked(Integer.parseInt(name),true);
    }
    
    public class DrawerItemClickListener implements android.widget.AdapterView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }
    }
    
    private void selectItem(int position) {
        Fragment fragment;
    
        switch (position) {
            case 0: // Home
                fragment = new ContentFragment();
                break;
            case 1: // Another
               fragment = new AnotherFragment();
                break;
            default: // Logout
            {
                finish();
    
                Intent intent = new Intent(this, StartupActivity.class);
                startActivity(intent);
    
                return;
            }
        }
    
        FragmentManager fragmentManager = getFragmentManager();
        fragmentManager.beginTransaction()
                .replace(R.id.content_frame, fragment)
                .addToBackStack(Integer.toString(position))
                .commit();
    
        mDrawerList.setItemChecked(position, true);
        setTitle(mNavigationItems[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }
    

    【讨论】:

    • 哇,这太棒了。谢谢!
    • 非常感谢 :) 不得不花足够的时间以丑陋的方式,直到找到你的答案。 :)
    【解决方案4】:

    以@matiash 的出色回答为基础(如果您 +1 我的回答,请务必 +1 他/她)

    首先,我们假设我们在Activity

    getFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });
    

    您可以通过以下方式找到当前片段:

    (来自https://*.com/a/24589081/878159

    Fragment f = getFragmentManager().findFragmentById(R.id.frag_container);
    if (f instanceof CustomFragmentClass) {
        // do something with f
        f.doSomething();
    }
    

    这应该让你更近一步。然后这样的事情会让你参考你的导航抽屉:

    nav = (NavigationDrawerFragment) getFragmentManager().findFragmentById(R.id.nav);
    

    然后取决于你如何设置你的抽屉片段代码:

    nav.updateSelected(position);
    

    updateSelected(int) 方法将在您的 NavigationDrawerFragment 类中,可能看起来像这样:

    public void updateSelected(int position) {
        mCurrentSelectedPosition = position;
        if (mDrawerListView != null) {
            mDrawerListView.setItemChecked(position, true);
        }
    }
    

    注意:有很多方法可以做到这一点,但这应该可以帮助您入门。

    【讨论】:

      【解决方案5】:

      其实很简单。您已经在 Drawer 布局中拥有导航视图。对于每个按下的项目,它都会打开相应的片段,对吗? 现在转到每个片段的onCreateView 并执行此操作...

      NavigationView navigationView = getActivity().findViewById(R.id.navigation_view);
              navigationView.setCheckedItem(R.id.this_fragment's_item);
      

      另外,在您处理项目点击的主要活动中,使用 if 语句,这样

      if (navigation_view.getMenu().findItem(R.id.account_nav).isChecked())   
        drawer_layout.closeDrawer(GravityCompat.START);
      

      以上避免了在其自身之上创建片段的多个实例。

      【讨论】:

      • 这是最好的解决方案
      【解决方案6】:

      这已经很晚了,但是对于那些使用导航菜单而不是列表的人来说,我是这样做的

      @SuppressWarnings("StatementWithEmptyBody")
          @Override
          public boolean onNavigationItemSelected(MenuItem item) {
              // Handle navigation view item clicks here.
             int position=0;
             Fragment fragment;
              int id = item.getItemId();
              if (id == R.id.nav_home) {
                  fragment=new HomeFragment();
                  position=0;
      
              } else if (id == R.id.nav_profile) {
      
                 fragment=new ProfileFragment();
                  position=1;
              }  else if (id == R.id.nav_settings) {
                   fragment=new SettingsFragment();
                  position=2;
              }
      
                getSupportFragmentManager()
                  .beginTransaction()
                  .add(R.id.relative_container, fragment)
                  .addToBackStack(""+position)
                  .commit();
      
              DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
              drawer.closeDrawer(GravityCompat.START);
              return true;
          }
      

      并为 backstack 添加监听器

       getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
              @Override
              public void onBackStackChanged() {
      
                   FragmentManager fm = getSupportFragmentManager();
                   String name = fm.getBackStackEntryAt(fm.getBackStackEntryCount()-1).getName();
                     if(!TextUtils.isEmpty(name))
                   selectNavigationDrawerItem(Integer.parseInt(name));
      
              }
          });
      

      selectNavigationDrawerItem 方法

       private void selectNavigationDrawerItem(int position){
      
                     if(position==0){
                         navigationView.setCheckedItem(R.id.nav_home);
                     }
                     else if(position==1){
                         navigationView.setCheckedItem(R.id.nav_profile);
                     }
                     else if(position==2){
                         navigationView.setCheckedItem(R.id.nav_settings);   
                    }
      
           }
      

      【讨论】:

        【解决方案7】:

        这是 kotlin 中的一个解决方案。

        在你想要被抽屉选中并突出显示的片段中,你只需这样做:

            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
        
            val navView: NavigationView = activity!!.findViewById(R.id.nav_view)
            navView.setCheckedItem(R.id.name_of_this_fragment)
        
            ...
        }
        

        【讨论】:

          最近更新 更多