使用片段回栈处理ActionBar标题?


74

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

开始时,活动在操作栏中显示默认标题(即,它是根据应用程序的自动设置的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();
}

Answers:


123

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

@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);
    }
}

2
我不需要强制转换或获得操作栏(即,我只是打电话给getActivity().setTitle(...)),但这是一种合理的方法。谢谢。
Christopher Orr 2012年

9
-1。这种方法是不正确的!根据Android设计指南,使用导航抽屉切换片段时,操作栏标题应仅在导航抽屉的onClosed回调中更改。在关闭导航抽屉之前,您的方法将错误地更改标题。
zyamys 2014年

6
@zyamys如果您有更好的建议,我很乐意赞成。这是当时我能想到的最好的答案。
Warpzit 2014年

7
这对我不起作用,所以我将其更改为getActivity().setTitle(R.string.thetitle);,然后起作用。
simeg 2015年

4
仅当您在FragmentTransaction期间替换片段时,此方法才有效。如果添加片段而不是替换怎么办?
Kamlesh Karwande

28

由于原始答案很旧,因此可能也会有所帮助。如文档所述,可能需要注册一个listener主机以监听主机中的后向堆栈更改Activity

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

然后,在回调方法中确定情况并设置适当的标题,而无需ActionBar从中访问Fragment

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

有关该主题的更多信息,请参见文档


1
另一个答案是旧的,但我仍然发现它是确保标题始终正确的最简单,最实用的方法。如我的评论中所述,您不需要直接访问ActionBar。此处的解决方案需要更多代码,但对于您的初始片段也无济于事,该片段通常不会添加到后台堆栈中。
Christopher Orr 2014年

1
从片段访问操作栏也没有错。Google还提供了用于更改片段内菜单项的api。
Warpzit 2014年

2
经过一段时间的总结,我得出两个解决方案都不错的结论,这仅取决于您的用例,有时使用onResume和有时使用片段管理器侦听器会更可行。因此,总的来说,了解您的工具是一件好事,这样您就可以充分地将其应用于您的需求。
Maciej Pigulski 2014年

2
@Maciej Pigulski然后呢?在此回调中,我们对上下文一无所知。
fralbo 2015年

2
@caBBAlainB,上下文位于托管活动的FragmentManager中,因此您必须确定标题,然后对管理器进行一些繁琐的检查-因此,它不是很好。今天,我不记得我在这里的理由了。我认为这个想法主要来自此答案中链接的Android文档中的内容(侦听器片段上方的段落)。今天,我宁愿去使用公认的答案。
Maciej Pigulski 2015年

11

让控制活动完成所有工作,如下所示:

侦听堆栈事件(在活动的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()可以检索其标题,那么这并不是“让活动完成所有工作” 。似乎没有一个适合我的问题的“没有样板代码”。
Christopher Orr 2014年

在我的解决方案中没有提及接口,您的活动可以检查片段的实例并根据该实例设置标题,从而让您的活动完成所有工作。所有这些方法都在活动中定义。
Meanman

是的,从设计/关注点分离/可重用性的角度来看,这听起来可能更糟。加上这会为添加到后堆栈中的每个片段重复两次更改标题,因为setTitleFromFragment方法是在setFragment中无条件调用的,我猜呢?无论如何,谢谢,但是我会坚持实用而简单的答案:)
Christopher Orr 2014年

3
从“设计/关注点分离/可重用性的角度来看”,这听起来如何?请使用有效的参数备份它吗?您让活动成为“控制者”,并根据其内容设置自己的标题。如果我想在多个地方使用一个片段,或者要进行包含多个片段的活动,而它们都试图设置标题,该怎么办?
Meanman

很有帮助的答案
VVB

7

Warpzit是正确的。当设备方向改变时,这也解决了标题问题。另外,如果您将支持v7用于操作栏,则可以从片段中获取操作栏,如下所示:

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

+1支持v7。我认为,每当给出Android答案时,回答者都应考虑到支持。到目前为止,要达到80%的设备覆盖率,您必须返回API16。在大多数情况下,需要使用compat库。
akousmata 2015年

1
我把您的代码放在public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)方法中,并且工作正常。
GFPF 2015年

7

最好让操作系统完成尽可能多的工作。假设每个片段都使用.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);
    }
}

5

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

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

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

然后,我将覆盖该onBackStackChanged()方法,并setTitle()使用最后一个Backstack条目名称进行调用。

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

    setTitle(lastBackStackEntry.getName());
}

3
使用Backstack条目名称的问题是它不能处理配置更改。解决此问题的一种有趣而正确的方法是使用setBreadCrumbTitle(int res)保存标题资源ID,您可以在onBackStackChanged中使用它来设置标题。
David Liu

4

使用片段方法:

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

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


是的..没有一个答案没有解决我的问题..这很好。它可能不是专业的方法..但是有点古怪。
Ranjith Kumar

@要使其正常工作,您需要一个选项菜单,您可以通过调用“ setHasOptionsMenu(true);”将其激活为一个片段。在片段的OnCreateView(...)方法中。
New2HTML

1

最好的方法是利用android提供的Interface 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)相应地设置了操作栏标题,其中标题存储了适当的标题名称。

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

例:

可以说我们的android backstack是空的:

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

从导航抽屉中按Choice 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()而不是pop(),否则当您打开两个以上的片段并尝试按“后退”按钮返回时,应用程序将崩溃。


这是荒谬的代码量(尤其是与接受的答案相比),并且忽略了我在问题中的要求,即在不增加样板代码且无需跟踪所显示标题的情况下实现该要求。
Christopher Orr

是的,如果您不想要过多的代码,那么这就是您的问题。你必须在某个地方妥协。此外,代码中唯一重要的部分是OnBackStackChanged()。
Abhishek 2015年

1

您可以使用onKeyDown解决!我有一个布尔mainisopen = true <-MainFragment是可见的其他片段mainisopen = false

这是我的代码:

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);
    }

}

0

如此处所述我的解决方案是将此代码添加到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");
    }

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.