碎片从后退栈中消失

4
为什么在以下情况下FragmentTwo会从返回栈中消失:
  • 我的应用程序有一个名为FragmentOneFragment在一个Activity中。
  • FragmentOne包含一个Button。点击时,它会启动FragmentTwo,将其添加到Fragment返回栈中。
  • FragmentTwo有一个Button,点击时会向与两个Fragments相关联的ActionBar添加两个选项卡,FragmentThreeFragmentFourFragmentThree是可见的。
  • 如果我现在点击返回按钮,我希望看到FragmentTwo。 相反,我看到了FragmentOne。那么FragmentTwo去哪了?

在我覆盖onKeyDown()并开始为Fragments实现自己的返回栈之前,我想问一下是否有什么明显的遗漏?请注意,在测试时没有发生配置更改。

enter link description here

细节:

FragmentOne的按钮单击处理程序包含:

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    FragmentTwo fragment = new FragmentTwo();
    ft.addToBackStack(null);
    ft.replace(android.R.id.content, fragment).commit();

在 Activity 中处理 FragmentTwo 按钮的点击事件:

    getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    FragmentFour fragmentThree = new FragmentThree();
    FragmentFive fragmentFive = new FragmentFive();

    ActionBar.Tab tab = getActionBar().newTab().setText("Frag 3").setTabListener(new CustomTabListener<FragmentThree>(fragmentThree));
    getActionBar().addTab(tab);
    tab = getActionBar().newTab().setText("Frag 4").setTabListener(new CustomTabListener<FragmentFour>(fragmentFour));
    getActionBar().addTab(tab);

tab监听器的位置:

public static class CustomTabListener<T extends Fragment> implements TabListener {
    Fragment fragment;

    public CustomTabListener(Fragment fragment) {
        this.fragment = fragment;
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        ft.replace(android.R.id.content, fragment);
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        ft.remove(fragment);
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {

    }
}

如果在显示FragmentThree之前向后堆栈中添加更多Fragments,那么消失的总是并且只有FragmentThree之前的Fragment

当我通过按返回键离开选项卡用户视图并返回FragmentOne时,选项卡仍然显示。我明白我需要将ActionBar重置为NAVIGATION_MODE_STANDARD,但不清楚为什么会显示FragmentOne而不是FragmentTwo


@Pierre Rymiortz,我也遇到了和你一样的问题。请参考下面的答案。 - lakshman
5个回答

2

@Pierre Rymiortz,我刚刚为这个问题找到了解决方法。请参考下面的代码。

public class StartActivity extends SherlockFragmentActivity implements
    TabListener, OnBackStackChangedListener {

private Context mContext = null;

private ActionBar mActionBar;

private Tab mTab;

private Fragment mSelectFragment;

private final static int ONE = 0;
private final static int TWO = 1;
private final static int THREE = 2;
private final static int PLAYERS = 3;

private MenuItem mPlayerItem = null;

private FragmentTransaction mFragmentTransaction = null;

/**
 * by default is false - TABS were taken in the starting true - when we are
 * changing back to TABS from STANDARD
 */
private boolean mTabsChanges = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mContext = this;

    mActionBar = getSupportActionBar();

    // hiding the ActionBar icon
    mActionBar.setDisplayShowHomeEnabled(true);

    // for displaying the Arrow button
    mActionBar.setDisplayHomeAsUpEnabled(true);

    // hide Actionbar title
    mActionBar.setDisplayShowTitleEnabled(true);

    // Create Actionbar Tabs
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

    // Create first Tab
    mTab = mActionBar.newTab().setTabListener(this);
    mTab.setText("One");
    mActionBar.addTab(mTab);

    // Create Second Tab

    mTab = mActionBar.newTab().setTabListener(this);
    mTab.setText("Two");
    mActionBar.addTab(mTab);

    // Create Three Tab
    mTab = mActionBar.newTab().setTabListener(this);
    mTab.setText("Three");
    mActionBar.addTab(mTab);

    getSupportFragmentManager().addOnBackStackChangedListener(this);

}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    mFragmentTransaction = ft;
    if (mTabsChanges) {
        // nothing to do here
        mTabsChanges = false;
    } else {
        switch (mActionBar.getSelectedNavigationIndex()) {
        case ONE:
            clearBackStack(mTabsChanges);
            mSelectFragment = new Fragment1();
            ft.replace(R.id.fragment_container, mSelectFragment, ONE + "");
            break;
        case TWO:
            clearBackStack(mTabsChanges);
            mSelectFragment = new Fragment2();
            ft.replace(R.id.fragment_container, mSelectFragment, TWO + "");
            break;
        case THREE:
            clearBackStack(mTabsChanges);
            mSelectFragment = new Fragment3();
            ft.replace(R.id.fragment_container, mSelectFragment, THREE + "");
            break;
        }
    }

}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    Toast.makeText(
            mContext,
            "tab unselected." + mActionBar.getSelectedNavigationIndex()
                    + "", Toast.LENGTH_SHORT).show();
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
    Toast.makeText(
            mContext,
            "tab reselected." + mActionBar.getSelectedNavigationIndex()
                    + "", Toast.LENGTH_SHORT).show();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // TODO Auto-generated method stub
    MenuInflater mInflate = getSupportMenuInflater();
    mInflate.inflate(R.menu.header_menu, menu);

    mPlayerItem = menu.findItem(R.id.men_set_players);

    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == R.id.men_set_players) {
        addFragmentToStack();
        return true;
    }
    return false;
}

private void addFragmentToStack() {

    // Add the fragment to the activity, pushing this transaction
    // on to the back stack.
    mActionBar.setTitle(getResources().getString(R.string.menu_players));
    mSelectFragment = new PlayersFragment();
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.replace(R.id.fragment_container, mSelectFragment, "Players");
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    ft.addToBackStack("players");
    ft.commit();
}

@Override
public void onBackStackChanged() {
    FragmentManager fm = getSupportFragmentManager();
    if (fm.getBackStackEntryCount() > 0) {
        int TOP = fm.getBackStackEntryCount() - 1;
        Log.i("BACKSTACK", TOP + ".."
                + fm.getBackStackEntryAt(TOP).getName());
        if (fm.getBackStackEntryAt(TOP).getName()
                .equalsIgnoreCase("players")) {
            if (mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS) {
                mTabsChanges = false;
                mActionBar
                        .setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
            }
        } else {
            if (mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_STANDARD) {
                mTabsChanges = true;
                mActionBar
                        .setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
            }
        }

    } else {
        if (mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_STANDARD) {
            mTabsChanges = true;
            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        } else if (mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS) {
            mTabsChanges = false;
        }
    }

    Toast.makeText(mContext,
            "FM Back count." + fm.getBackStackEntryCount(),
            Toast.LENGTH_SHORT).show();
}

public void clearBackStack(boolean callFromSelection) {
    if (!callFromSelection) {
        FragmentManager fm = getSupportFragmentManager();
        for (int i = 0; i < fm.getBackStackEntryCount(); i++) {
            fm.popBackStackImmediate();
        }
    } else {
        Toast.makeText(mContext, "callfromreapprearence",
                Toast.LENGTH_SHORT).show();
    }
}

}

2
我认为问题在于如果你构建了标签页。
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    ft.replace(android.R.id.content, fragment);
}

在未添加到返回栈之前,将调用该片段。

你尝试过调用吗?

ft.addToBackStack(null);

在Fragment的两个按钮处理代码中。

但是你仍然需要实现onBackPressed()来摆脱选项卡。我认为我会创建一个带有选项卡的新活动。


感谢回复。如果我在OnTabSelected()方法中添加了addToBackStack方法,就会抛出IllegalStateException异常。 - Pierre Rymiortz
嗯,那很糟糕。也许你可以在FragmentTwo按钮点击时将该片段添加到后退栈中。 - n3utrino
尝试了不同的解决方案后,我相信ActionBar Tabs只是作为顶级导航模式而设计的,这意味着Tabs必须是Fragment导航的起点(如果有人能证明这是错误的,请务必这样做),以使Fragment backstack按预期工作。因此,在我的示例中,FragmentTwo必须启动一个包含具有两个选项卡的ActionBar的Activity。这就是我最终实现的内容。由于您在答案中提到了这一点,gabe,我将其标记为正确。 - Pierre Rymiortz

1
根据您上面展示的代码,您从未将Fragment2添加到后退栈中。
@gabe,@Pierre,您在监听器中使用addToBackStack()导致IllegalStateException的原因在API http://developer.android.com/reference/android/app/ActionBar.TabListener.html中有记录。
public abstract void onTabSelected (ActionBar.Tab tab, FragmentTransaction ft)  

API level 11 中新增 当选中一个标签时调用。 参数 tab:被选中的标签。 ft:FragmentTransaction,用于在标签切换期间排队执行片段操作。上一个标签的取消选择和这个标签的选择将在单个事务中执行。 此 FragmentTransaction 不支持添加到后退栈。
解决方法是在 TabListener 中使用 FragmentManager 获取自己的 FragmentTransaction,并将其添加到后退栈中。类似以下代码:
    public static class CustomTabListener<T extends Fragment> implements TabListener {
    Fragment fragment;

    public CustomTabListener(Fragment fragment) {
        this.fragment = fragment;
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        FragmentManager fm = getFragmentManager();
        FragmentTransaction newFragTrans = fm.beginTransaction();
        newFragTrans.replace(android.R.id.content, fragment);
        newFragTrans.addToBackStack(null);
        newFragTrans.commit()
        
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        ft.remove(fragment);
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {

    }
}

我只修改了onTabSelected,但你明白我的意思。另外,不要忘记在此事务中调用commit(),因为监听器只会自动commit()提供的FragmentTransaction ft

最后一件事,确保在执行事务后调用addToBackStack(),因为这是你添加到堆栈中的内容,而不是实际的片段。在上面的代码中,对于Fragment1的按钮单击未执行此操作。

希望这可以帮助你。


感谢Matt关于如何构建TabListener的建议。不幸的是,即使使用了新的Tablistener代码,问题仍然存在。我确实将FragmentTwo添加到了backstack中,请参见我的Q中的第一个代码块。 - Pierre Rymiortz
实际上,根据我上面看到的内容,您的代码没有将FragmentTwo添加到返回堆栈中。请记住,您正在向返回堆栈中添加一个事务。因此,在这种情况下,您正在删除fragmentOne并添加fragmentTwo(替换),然后通过将其添加到返回堆栈中来记住此转换/事务,就像撤消操作一样。当然,假设ft.addToBackStack()调用在ft.replace()之后。如果侦听器中的代码无法正常工作,那么会出现什么错误/问题? - Matt

1

始终使用此代码块来替换片段:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(android.R.id.content, YOUR_FRAGMENT);
ft.addToBackStack(null);
ft.commit();

谢谢,我已经在用这个来处理消失的Fragment了。请看我问题中的第一个代码块。对于选项卡式的Fragments,addToBackStack对我不起作用,请看我对gabe回答的评论。 - Pierre Rymiortz
在提交之前,请调用addToBackStack。所有片段操作都应该先执行,然后再执行addToBackStack(如果需要),最后进行提交。所有片段操作都应该始于beginTransaction,然后添加/替换/移除片段,接着是addToBackStack(可选),最后才进行提交。 - Leonidos
我测试过了,在调用addToBackStack()之前调用replace()没有任何影响。我的理解是,FragmentTransaction遵循Builder模式,在commit()之前的顺序并不重要。 - Pierre Rymiortz

1
我认为你需要在onTabUnselected()函数中删除ft.remove,因为replace已经完成了这个操作。

谢谢。我尝试了您的建议并删除了“ft.remove(fragment)”这一行,但不幸的是它并没有改变行为,仍然会在按下返回键时从FragmentThree跳转到FragmentOne。 - Pierre Rymiortz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接