【问题标题】:Handling ActionBar title with the fragment back stack?使用片段返回堆栈处理 ActionBar 标题?
【发布时间】:2012-11-08 11:08:44
【问题描述】:

我有一个Activity,我在其中加载了一个ListFragment,点击后,它会向下钻取一个级别并显示一种新类型的ListFragment,替换原来的类型(使用下面的showFragment 方法)。这被放置在后堆栈上。

一开始,Activity 在操作栏中显示默认标题(即它是根据应用程序的android:label 自动设置的)。

在显示层次结构中下一级的列表时,单击的项目名称应成为操作栏的标题。

但是,当按下 Back 时,我希望恢复原来的默认标题。这不是FragmentTransaction 知道的事情,所以不会恢复标题。

我隐约读到过FragmentBreadCrumbs,但这似乎需要使用自定义视图。我正在使用 ActionBarSherlock,并且不希望有自己的自定义标题视图。

这样做的最佳方法是什么?是否有可能无需加载样板代码并且必须跟踪沿途显示的标题?


protected void showFragment(Fragment f) {
  FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
  ft.replace(R.id.fragment_container, f);
  ft.addToBackStack(null);
  ft.commit();
}

【问题讨论】:

    标签: android android-fragments android-actionbar actionbarsherlock


    【解决方案1】:

    在每个片段和每个活动中,我都会像这样更改标题。这样,活动标题将始终正确:

    @Override
    public void onResume() {
        super.onResume();
        // Set title
        getActivity().getActionBar()
            .setTitle(R.string.thetitle);
    }
    

    在某些情况下,onResume 不会在片段内部调用。在某些情况下,我们可以使用:

    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(isVisibleToUser) {
            // Set title
            getActivity().getActionBar()
                .setTitle(R.string.thetitle);
        }
    }
    

    【讨论】:

    • 我不需要投射或获取操作栏(即我只需调用getActivity().setTitle(...)),但这是一种合理的方法。谢谢。
    • -1。这种做法是错误的!根据 Android 设计指南,当使用 Navigation Drawer 切换 Fragment 时,Action Bar 标题应仅在 Navigation Drawer onClosed 回调中更改。在导航抽屉关闭之前,您的方法会错误地更改标题。
    • @zyamys 如果你有更好的建议,我很乐意支持它。这是我当时能想到的最佳答案。
    • 这对我不起作用,所以我将其更改为 getActivity().setTitle(R.string.thetitle);,然后它起作用了。
    • 这仅在您在 FragmentTransaction 期间替换片段时才有效。如果你添加一个片段而不是替换呢?
    【解决方案2】:

    由于原始答案已经很老了,这也可能会有所帮助。正如文档所述,可能需要注册一个listener 来监听托管Activity 中的后台堆栈更改:

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

    然后,在回调方法中识别情况并设置适当的标题,而不是从Fragment 访问ActionBar

    这是一个更优雅的解决方案,因为Fragment 不必知道ActionBar 的存在,而Activity 通常是管理后台堆栈的地方,因此在那里处理它似乎更合适. Fragment 应始终只考虑其自身的内容,而不是周围环境。

    documentation 中有关该主题的更多信息。

    【讨论】:

    • 另一个答案是旧的,但我仍然发现它是确保标题始终正确的最简单和最实用的方法。正如我的评论中提到的,您不需要直接访问 ActionBar 。此处的解决方案需要更多代码,但对于您的初始片段也无济于事,通常不会将其添加到后台堆栈中。
    • 从片段访问操作栏也没有错。 Google 还提供了用于更改片段内的菜单项的 api。
    • 一段时间后我得出结论,两种解决方案都很好,这取决于您的用例,有时使用 onResume 和有时使用片段管理器侦听器会更可行。所以总而言之,了解您的工具是件好事,这样您就可以将它们充分应用到您的需求中。
    • @Maciej Pigulski 然后呢?我们对回调中的上下文一无所知。
    • @caBBAlainB,上下文保存在托管活动的 FragmentManager 中,因此您必须确定标题,对管理器进行一些繁琐的检查 - 因此它不是很好。今天我不记得我在这里的理由是什么。我认为这个想法主要来自这个答案中链接的 Android 文档中所写的内容(侦听器 sn-p 上方的段落)。今天我宁愿去使用公认的答案。
    【解决方案3】:

    让控制活动按如下方式完成所有工作:

    监听 backstack 事件(在活动的 onCreate() 中):

    // Change the title back when the fragment is changed
        getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Fragment fragment = getFragment();
                setTitleFromFragment(fragment);
            }
        });
    

    从容器中获取当前片段:

    /**
     * Returns the currently displayed fragment.
     * @return
     *      Fragment or null.
     */
    private Fragment getFragment() {
        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
        return fragment;
    }
    

    在内容视图中设置片段:

    private void setFragment(Fragment fragment, boolean addToBackStack) {
        // Set the activity title
        setTitleFromFragment(fragment);
        .
        .
        .
    }
    

    【讨论】:

    • 如果您在每个Fragment 中实现了一些接口,那么这并不是“让活动完成所有工作”,以便setTitleFromFragment() 可以检索其标题。这些似乎都不适合我的问题的“没有大量样板代码”部分。
    • 我的解决方案中没有提到接口,您的活动可以检查片段的实例并根据该实例设置标题,从而让您的活动完成所有工作。所有这些方法都在 Activity 内部定义。
    • 是的,从设计/关注点分离/可重用性的角度来看,这听起来可能更糟。另外,这会为添加到后台堆栈的每个片段重复更改标题两次,因为 setTitleFromFragment 方法在 setFragment 中被无条件调用,我猜?无论如何,谢谢,但我会坚持务实而简单的接受答案:)
    • 这听起来“从设计/关注点分离/可重用性的角度来看可能更糟”?请用有效的论据支持它?您让活动成为“控制器”并根据其内容设置自己的标题。如果我想在多个地方使用一个片段,或者有多个片段的活动,他们都尝试设置标题。
    • 很有帮助的答案
    【解决方案4】:

    Warpzit 是对的。这也解决了更改设备方向时的标题问题。此外,如果您将 support v7 用于操作栏,您可以从片段中获取操作栏,如下所示:

    @Override
    public void onResume() {
        super.onResume();
        ((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
    }
    

    【讨论】:

    • +1 支持 v7。我认为,每当给出 android 答案时,回答的人都应该考虑支持。到目前为止,要达到 80% 的设备覆盖率,您必须回到 API 16。在大多数情况下,这需要使用兼容库。
    • 我把你的代码放在public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 方法中并且工作正常。
    【解决方案5】:

    最好让操作系统完成尽可能多的工作。 假设每个片段都使用 .addToBackStack("title") 正确命名,然后 你可以重写 onBackPressed 这样的东西来实现所需的行为:

    // this example uses the AppCompat support library
    // and works for dynamic fragment titles
    @Override
    public void onBackPressed() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        int count = fragmentManager.getBackStackEntryCount();
        if (count <= 1) {
            finish();
        }
        else {
            String title = fragmentManager.getBackStackEntryAt(count-2).getName();
            if (count == 2) {
                // here I am using a NavigationDrawer and open it when transitioning to the initial fragment
                // a second back-press will result in finish() being called above.
                mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
            }
            super.onBackPressed();
            Log.v(TAG, "onBackPressed - title="+title);
            getSupportActionBar().setTitle(title);
        }
    }
    

    【讨论】:

      【解决方案6】:

      我使用与Lee approach 类似的解决方案,但替换为onBackStackChanged() 方法。

      首先我在将事务添加到后台堆栈时设置片段名称。

      getSupportFragmentManager().beginTransaction()
                      .replace(R.id.frame_content, fragment)
                      .addToBackStack(fragmentTitle)
                      .commit();
      

      然后我重写 onBackStackChanged() 方法并使用最后一个 backstack 条目名称调用 setTitle()

      @Override
      public void onBackStackChanged() {
          int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
          FragmentManager.BackStackEntry lastBackStackEntry =
                  getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);
      
          setTitle(lastBackStackEntry.getName());
      }
      

      【讨论】:

      • 使用 backstack 条目名称的问题是它不处理配置更改。处理此问题的一个有趣且正确的方法是使用 setBreadCrumbTitle(int res) 保存标题资源 id,您可以在 onBackStackChanged 中使用它来设置标题。
      【解决方案7】:

      使用 Fragments 方法:

      @Override
      public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
      

      在每次出现 Fragment 时都会调用它,但 onResume 不会。

      【讨论】:

      • 是的..没有一个答案没有保存我的问题..这很好用。这可能不是专业的方式..但它有点hacky。
      • @Bevor 要使此功能正常工作,您需要一个选项菜单,您可以通过调用“setHasOptionsMenu(true);”为片段激活它在片段的 OnCreateView(...) 方法中。
      【解决方案8】:

      最好的办法是利用android提供的接口OnBackStackChangedListener方法onBackStackChanged()。

      假设我们有一个导航抽屉,其中包含用户可以导航到的 4 个选项。在这种情况下,我们将有 4 个片段。让我们先看代码,然后我会解释工作原理。

          private int mPreviousBackStackCount = 0;
          private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
          Stack<String> mFragPositionTitleDisplayed;
      
          public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
          @Override
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          ....
          ....
          ....
          getSupportFragmentManager().addOnBackStackChangedListener(this);
          mFragPositionTitleDisplayed = new Stack<>();
      }
      
      public void displayFragment() {
          Fragment fragment = null;
          String title = getResources().getString(R.string.app_name);
          switch (position) {
              case 0:
                  fragment = new Fragment1();
                  title = title_name[position];
                  break;
              case 1:
                  fragment = new Fragment2();
                  title = title_name[position];
                  break;
              case 2:
                  fragment = new Fragment3();
                  title = title_name[position];
                  break;
              case 3:
                  fragment = new Fragment4();
                  title = title_name[position];
                  break;
              default:
                  break;
          }
          if (fragment != null) {
              FragmentManager fragmentManager = getSupportFragmentManager();
              fragmentManager.beginTransaction()
                      .replace(R.id.container_body, fragment)
                      .addToBackStack(null)
                      .commit();
              getSupportActionBar().setTitle(title);
          }
      }
      
      @Override
      public void onBackStackChanged() {
          int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
          if(mPreviousBackStackCount >= backStackEntryCount) {
              mFragPositionTitleDisplayed.pop();
              if (backStackEntryCount == 0)
                  getSupportActionBar().setTitle(R.string.app_name);
              else if (backStackEntryCount > 0) {
                  getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
              }
              mPreviousBackStackCount--;
          }
          else{
              mFragPositionTitleDisplayed.push(title_name[position]);
              mPreviousBackStackCount++;
          }
      
      }   
      

      在显示的代码中,我们有 displayFragment() 方法。这里我根据从导航抽屉中选择的选项显示片段。变量位置对应于从导航抽屉中的ListView或RecyclerView单击的项目的位置。我使用 getSupportActionBar.setTitle(title) 相应地设置了操作栏标题,其中标题存储了相应的标题名称。

      每当我们从导航抽屉中单击项目时,都会显示一个片段,具体取决于用户单击的项目。但是在后端,这个片段被添加到 backstack 并且方法 onBackStachChanged() 被命中。我所做的是创建了一个变量 mPreviousBackStackCount 并将其初始化为 0。我还创建了一个额外的堆栈来存储操作栏标题名称。每当我向后台堆栈添加一个新片段时,我都会将相应的标题名称添加到我创建的堆栈中。另一方面,每当我按下后退按钮时,都会调用 onBackStackChanged(),并从堆栈中弹出最后一个标题名称,并将标题设置为堆栈的 peek() 方法派生的名称。

      例子:

      假设我们的 android backstack 是空的:

      从导航抽屉中按选择 1: 调用 onBackStachChanged() 并将 Fragment 1 添加到 android backstack,backStackEntryCount 设置为 1,并将 Frag1 推送到我的堆栈中,并且 mFragPositionTitleDisplayed 的大小变为 1。

      从导航抽屉中按选择 2: 调用 onBackStachChanged() 并将 Fragment 2 添加到 android backstack 中,backStackEntryCount 设置为 2 并将 Frag2 推送到我的堆栈中,并且 mFragPositionTitleDisplayed 的大小变为 2。

      现在我们在 android 堆栈和我的堆栈中都有 2 个元素。当您按下返回按钮时,会调用 onBackStackChanged() 并且 backStackEntryCount 的值为 1。代码进入 if 部分并从我的堆栈中弹出最后一个条目。因此,android backstack 只有 1 个片段 - “Fragment 1”,而我的堆栈只有 1 个标题 - “Frag1”。现在我只是从我的堆栈中 peek() 标题并将操作栏设置为该标题。

      记住:要设置动作蝙蝠标题使用 peek() 而不是 pop() 否则当您打开超过 2 个片段并尝试按返回按钮返回时,您的应用程序将崩溃。

      【讨论】:

      • 这是一个荒谬的代码量(特别是与接受的答案相比),并且忽略了我在问题中的要求,即在没有样板代码负载且无需跟踪显示的标题。
      • 是的,如果您不想要过多的代码,那就是您的问题。你必须在某个地方妥协。此外,代码中唯一重要的部分是 OnBackStackChanged()。
      【解决方案9】:

      您可以使用 onKeyDown 解决! 我有一个布尔 mainisopen=true

      这是我的代码:

      public boolean onKeyDown(int keyCode, KeyEvent event) {
          if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
              mainisopen = true;
              HomeFrag fragment = new HomeFrag();
              FragmentTransaction fragmentTransaction =
                      getSupportFragmentManager().beginTransaction();
              fragmentTransaction.replace(R.id.fragmet_cont, fragment);
              fragmentTransaction.commit();
              navigationView = (NavigationView) findViewById(R.id.nav_view);
              navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
              navigationView.setNavigationItemSelectedListener(this);
              this.setTitle("Digi - Home"); //Here set the Title back
              return true;
          } else {
              if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
                  AlertDialog.Builder builder = new AlertDialog.Builder(this);
                  builder.setMessage("Wollen sie die App schliessen!");
                  builder.setCancelable(true);
      
                  builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
                      public void onClick(DialogInterface dialog, int which) {
                          System.exit(1);
                      }
                  });
      
                  builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
                      public void onClick(DialogInterface dialog, int which) {
                          Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
                      }
                  });
      
                  AlertDialog dialog = builder.create();
                  dialog.show();
      
                  return true;
              }
              return super.onKeyDown(keyCode, event);
          }
      
      }
      

      【讨论】:

        【解决方案10】:

        正如here 所述,我的解决方案是将此代码添加到 MainActivity onCreate method(): 并更改操作栏标题

        FragmentManager fragmentManager=getSupportFragmentManager();
        fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
                currentFragment.onResume();
            }
        });
        

        并在片段的 onResume() 方法中更改操作栏标题

        @Override
        public void onResume() {
            super.onResume();
            AppCompatActivity activity = (AppCompatActivity) getActivity();
            ActionBar actionBar = activity.getSupportActionBar();
            if(actionBar!=null) {
                actionBar.setTitle("Fragment Title");
                actionBar.setSubtitle("Subtitle");
            }
        
        }
        

        【讨论】:

          【解决方案11】:

          在后按时更新操作栏标题。简单地说

          getActivity.setTitle("title")

          在 onCreateView 方法中。

          【讨论】:

          • onCreateView 仅在...创建视图时被调用。如果你按下 Back 并且之前的 activity/fragment 还在栈上,那么这个方法将不会被调用。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多