如何从 BackStack 中恢复现有的片段

我正在学习如何使用片段。我在类的顶部初始化了三个 Fragment实例。我把这个片段加入到一个类似这样的活动中:

声明和初始化:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

更换/添加:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

这些片段工作正常。每个片段都附加到活动上,并且毫无问题地保存到回栈中。

所以当我启动 AC,然后 B,堆栈看起来像这样:

| |
|B|
|C|
|A|
___

当我按下“返回”按钮时,B被破坏,C恢复。

但是,当我第二次启动片段 A时,它不是从回栈中恢复,而是被添加到回栈的顶部

| |
|A|
|C|
|A|
___

但是我想恢复 A并且销毁它上面的所有片段(如果有的话)。实际上,我只是喜欢默认的回栈行为。

我该怎么做?

预计: (A应该恢复,顶部碎片应该被销毁)

| |
| |
| |
|A|
___

编辑: (由 A-C 建议)

这是我的试用代码:

private void selectItem(int position) {
Fragment problemSearch = null, problemStatistics = null;
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
String backStateName = null;
Fragment fragmentName = null;
boolean fragmentPopped = false;
switch (position) {
case 0:
fragmentName = profile;
break;
case 1:
fragmentName = submissionStatistics;
break;
case 2:
fragmentName = solvedProblemLevel;
break;
case 3:
fragmentName = latestSubmissions;
break;
case 4:
fragmentName = CPExercise;
break;
case 5:
Bundle bundle = new Bundle();
bundle.putInt("problem_no", problemNo);
problemSearch = new ProblemWebView();
problemSearch.setArguments(bundle);
fragmentName = problemSearch;
break;
case 6:
fragmentName = rankList;
break;
case 7:
fragmentName = liveSubmissions;
break;
case 8:
Bundle bundles = new Bundle();
bundles.putInt("problem_no", problemNo);
problemStatistics = new ProblemStatistics();
problemStatistics.setArguments(bundles);
fragmentName = problemStatistics;
default:
break;
}
backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();


// I am using drawer layout
mDrawerList.setItemChecked(position, true);
setTitle(title[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}

问题是,当我启动 A,然后是 B,然后按“返回”,B被删除,A被恢复。第二次点击“返回”应该退出应用程序。但它显示一个空白窗口,我必须按回第三次关闭它。

而且,当我启动 A,然后 B,然后 C,然后 B再次..。

预期:

| |
| |
|B|
|A|
___

实际情况:

| |
|B|
|B|
|A|
___

我应该重写 onBackPressed()与任何定制或我错过了什么?

210645 次浏览

读取 文件时,有一种方法可以根据事务名称或提交提供的 id 弹出回栈。使用名称 更容易,因为它不需要跟踪可能发生变化的数字,并加强了“惟一的回栈条目”逻辑。

因为每个 Fragment只需要一个回退堆栈条目,所以将回退状态名称设置为片段的类名(通过 getClass().getName())。然后,在替换 Fragment时,使用 popBackStackImmediate()方法。如果返回 true,则意味着在回栈中有一个片段的实例。如果没有,实际执行片段替换逻辑。

private void replaceFragment (Fragment fragment){
String backStateName = fragment.getClass().getName();


FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);


if (!fragmentPopped){ //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.addToBackStack(backStateName);
ft.commit();
}
}

剪辑

问题是,当我先启动 A 然后启动 B 然后按后退键 B 是删除和 A 是恢复。并再次按下后退按钮应 退出应用程序。但它显示一个空白窗口,需要另一个按 关上它。

这是因为将 FragmentTransaction添加到后台堆栈中,以确保以后可以在顶部弹出这些片段。快速修复这个问题是覆盖 onBackPressed(),并完成活动,如果回栈只包含1个 Fragment

@Override
public void onBackPressed(){
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
finish();
}
else {
super.onBackPressed();
}
}

关于重复的回栈条目,如果片段没有被弹出,那么替换它的 If判断语句显然是 与众不同,而不是我原来的代码片段。您正在做的是添加到回堆栈,而不管是否弹出了回堆栈。

这样的东西应该更接近你想要的:

private void replaceFragment (Fragment fragment){
String backStateName =  fragment.getClass().getName();
String fragmentTag = backStateName;


FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);


if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content_frame, fragment, fragmentTag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();
}
}

由于选择了相同的片段,条件被稍微改变了一点,当它可见的时候也会导致重复的条目。

实施方法:

我强烈建议不要像在代码中那样拆分更新后的 replaceFragment()方法。所有的逻辑都包含在这个方法中,移动部件可能会导致问题。

这意味着您应该将更新后的 replaceFragment()方法复制到类中,然后进行更改

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

所以这很简单

replaceFragment (fragmentName);

编辑 # 2

若要在回退堆栈更改时更新抽屉,请创建一个在片段中接受并比较类名的方法。如果有匹配的内容,请更改标题和所选内容。还要添加一个 OnBackStackChangedListener,如果有有效的片段,让它调用更新方法。

例如,在活动的 onCreate()中,添加

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {


@Override
public void onBackStackChanged() {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
if (f != null){
updateTitleAndDrawer (f);
}


}
});

另一种方法是:

private void updateTitleAndDrawer (Fragment fragment){
String fragClassName = fragment.getClass().getName();


if (fragClassName.equals(A.class.getName())){
setTitle ("A");
//set selected item position, etc
}
else if (fragClassName.equals(B.class.getName())){
setTitle ("B");
//set selected item position, etc
}
else if (fragClassName.equals(C.class.getName())){
setTitle ("C");
//set selected item position, etc
}
}

现在,只要回退堆栈发生变化,标题和选中的位置就会反映出可见的 Fragment

步骤1: 用您的活动类实现一个接口

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{


@Override
protected void onCreate(Bundle savedInstanceState) {
.............
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
}


private void switchFragment(Fragment fragment){
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
}


@Override
public void onBackStackChanged() {
FragmentManager fragmentManager = getFragmentManager();


System.out.println("@Class: SummaryUser : onBackStackChanged "
+ fragmentManager.getBackStackEntryCount());


int count = fragmentManager.getBackStackEntryCount();


// when a fragment come from another the status will be zero
if(count == 0){


System.out.println("again loading user data");


// reload the page if user saved the profile data


if(!objPublicDelegate.checkNetworkStatus()){


objPublicDelegate.showAlertDialog("Warning"
, "Please check your internet connection");


}else {


objLoadingDialog.show("Refreshing data...");


mNetworkMaster.runUserSummaryAsync();
}


// IMPORTANT: remove the current fragment from stack to avoid new instance
fragmentManager.removeOnBackStackChangedListener(this);


}// end if
}
}

步骤2: 当你调用另一个片段时,添加这个方法:

String backStateName = this.getClass().getName();


FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this);


Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);


fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {


@Override
public void onBackStackChanged() {


if(getFragmentManager().getBackStackEntryCount()==0) {
onResume();
}
}
});

我认为这个方法可以解决你的问题:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {




FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
FragmentTransaction ft = manager.beginTransaction ();


if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
ft.add ( fragmentHolderLayoutId, fragment, tag );
ft.addToBackStack ( tag );
ft.commit ();
}
else {
ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
}
}

最初是在 这个问题

更简单的解决方案是更改这一行

ft.replace(R.id.content_frame, A);ft.add(R.id.content_frame, A);

在您的 XML 布局中,请使用

  android:background="@color/white"
android:clickable="true"
android:focusable="true"

Clickable意味着它可以被指针设备点击或被触摸设备点击。

Focusable意味着它可以通过键盘等输入设备获得焦点。键盘等输入设备无法根据输入本身决定将其输入事件发送到哪个视图,因此它们将输入事件发送到具有焦点的视图。

我知道现在回答这个问题已经很晚了,但我自己解决了这个问题,并认为值得与大家分享。`

public void replaceFragment(BaseFragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
final FragmentManager fManager = getSupportFragmentManager();
BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);


if (fragm == null) {  //here fragment is not available in the stack
transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
transaction.addToBackStack(fragment.getFragmentTag());
} else {
//fragment was found in the stack , now we can reuse the fragment
// please do not add in back stack else it will add transaction in back stack
transaction.replace(R.id.container, fragm, fragm.getFragmentTag());
}
transaction.commit();
}

在 onBackPress ()中

 @Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>1){
super.onBackPressed();
}else{
finish();
}
}