使用片段返回堆栈处理 ActionBar 标题?
Posted
技术标签:
【中文标题】使用片段返回堆栈处理 ActionBar 标题?【英文标题】:Handling ActionBar title with the fragment back stack? 【发布时间】: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();
【问题讨论】:
【参考方案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 还在栈上,那么这个方法将不会被调用。以上是关于使用片段返回堆栈处理 ActionBar 标题?的主要内容,如果未能解决你的问题,请参考以下文章