安卓:保持Fragment运行

9

在我使用FragmentManager打开新的Fragment时,是否可以让旧的Fragment继续运行?

基本上,我希望在切换到其他Fragment时(通过replace方法),不要暂停旧的Fragment。

这种情况是否可行? 或者说,正确的做法是每次需要打开Fragment时都实例化一个新的Fragment,并恢复其之前的状态吗?

谢谢!


你是用新的Fragment替换旧的Fragment吗? - Rod_Algonquin
尝试隐藏片段而不是替换或移除。 - Ercan
@Rod_Algonquin 是的,我正在替换。 - igorjrr
@Ercan 会尝试,谢谢,但如果我隐藏它,我应该在另一个布局中打开另一个片段,否则它会覆盖当前的吧? - igorjrr
1
如果您不使用replace或remove方法,它将保留在那里。您甚至可以在同一布局中加载其他片段而不删除先前的片段。最后一个加载的片段将位于顶部并可见(如果顶部的片段是透明的,则当然会可见)。 - Ercan
1个回答

20

FragmentManger替换方法会完全销毁先前的片段,因此在每次事务中,onDestroyView()、onDestroy()和onDetach()都将调用先前的片段。如果您想保持片段运行,则可以使用FragmentManger的隐藏和显示方法而不是销毁它们!它会隐藏和显示片段而不会销毁它们。

所以首先将两个片段添加到fragment manager中,并且还要隐藏第二个片段。

        fragmentManager.beginTransaction()
            .add(R.id.new_card_container, FragmentA)
            .add(R.id.new_card_container,FragmentB)
            .hide(FragmentB)
            .commit();

请注意,您只能在隐藏的片段上调用show()。因此,在FragmentA上您不能调用show(),但这不是问题,因为通过隐藏和显示FragmentB,您可以获得所需的替换效果。

这里有一种在您的片段之间来回导航的方法。

public void showOtherFragment() {

    if(FragmentB.isHidden()){
        fragmentManager.beginTransaction()
                .show(FragmentB).commit();

    } else {
        fragmentManager.beginTransaction()
                .hide(FragmentB).commit();
    }
}

如果您将日志消息放在片段回调方法中,您会发现除了屏幕方向更改之外,没有销毁(即使视图也不会被销毁,因为onDistroyView不会被调用)。

唯一的问题是,在应用程序启动时,每个片段的onCreateView()方法都会被调用一次(这是必须的!),但是当方向更改时,每个片段的onCreateView()都会被调用两次,这是因为片段一次以通常的方式创建,另一次是由于它们附加到FragmentManger(保存在bundle对象上)。要避免这种情况,您有两个选择:1)在onSaveInstaneState()回调中分离片段。

@Override
protected void onSaveInstanceState(Bundle outState) {
    fragmentManager.beginTransaction()
            .detach(FragmentA)
            .detach(FragmentB)
            .commit();

    super.onSaveInstanceState(outState);
}

它能工作,但视图状态不会自动更新,例如如果您有一个EditText,每次发生方向更改时,文本都会被清除。当然,您可以通过在片段中保存状态来简单地解决这个问题,但如果使用第二个选项,则无需执行此操作!

首先,在onSaveInstanceState()方法中保存一个Boolean值以记住显示的片段。

@Override
protected void onSaveInstanceState(Bundle outState) {
    boolean isFragAVisible = true;
    if(!FragmentB.isHidden())
        isFragAVisible = false;

    outState.putBoolean("isFragAVisible",isFragAVisible);

    super.onSaveInstanceState(outState);
}

现在在onCreate方法中,我检查savedInstanceState是否为null。如果是,则像往常一样执行,如果不是,则该活动已经创建了第二次。因此,片段管理器已经包含了片段。因此,我从片段管理器中获取对我的片段的引用。同时,我确保显示正确的片段,因为它不会自动恢复。

    fragmentManager = getFragmentManager();

    if(savedInstanceState == null){

        FragmentA = new FragmentA();
        FragmentB = new FragmentB();
        fragmentManager.beginTransaction()
                .add(R.id.new_card_container, FragmentA, "fragA")
                .add(R.id.new_card_container, FragmentB, "fragB")
                .hide(FragmentB)
                .commit();

    } else {
        FragmentA = (FragmentA) fragmentManager.findFragmentByTag("fragA");
        FragmentB = (FragmentB) fragmentManager.findFragmentByTag("fragB");

        boolean isFragAVisible = savedInstanceState.getBoolean("isFragAVisible");
        if(isFragAVisible)
            fragmentManager.beginTransaction()
                    .hide(FragmentB)
                    .commit();
        else
            fragmentManager.beginTransaction()
                    .hide(FragmetA) //only if using transaction animation
                    .commit();
    }

目前,如果您不使用事务动画,则片段将完美运行。如果您使用了事务动画,则还需要显示和隐藏FragmentA。因此,当您想要先显示FragmentB时,请先隐藏FragmentA,然后在同一事务中显示FragmentB,并且当您想要隐藏FragmentB时,请先隐藏它,然后再显示FragmentA(同样在相同的事务中)。这是我从developer.goodle.com下载的卡片翻转动画代码。

public void flipCard(String direction) {
    int animationEnter, animationLeave;
    if(direction == "left"){
        animationEnter = R.animator.card_flip_right_in;
        animationLeave = R.animator.card_flip_right_out;
    } else {
        animationEnter = R.animator.card_flip_left_in;
        animationLeave = R.animator.card_flip_left_out;
    }

    if(cardBack.isHidden()){
        fragmentManager.beginTransaction()
                .setCustomAnimations(animationEnter, animationLeave)
                .hide(cardFront)
                .show(cardBack)
                .commit();

    } else {
        fragmentManager.beginTransaction()
                .setCustomAnimations(animationEnter,animationLeave)
                .hide(cardBack)
                .show(cardFront)
                .commit();
    }
}

谢谢,当我用现有的Fragment实例替换时,它不会调用onCreateView(根据文档似乎是正确的)。然后...我的控件不可见,因为它没有通过布局膨胀。我应该在onResume中膨胀吗?每次恢复时膨胀和填充控件听起来有点奇怪。此外,onResume没有保存信息的Bundle。需要帮助吗? - igorjrr
我刚刚按照你说的在fragment的onCreateView()方法中添加了一个Toast进行测试,结果发现它会在每次事务中都被调用,而不仅仅是第一次。这应该是因为当fragment从屏幕上消失时,onDistroyView()方法会被调用,所以当它重新出现时,没有视图需要重新创建。无论如何,我认为你的问题并不是来自于fragment事务。请在此处发布你的fragment代码,这可能有助于我们找到问题所在。 - Alireza Ahmadi
1
今天我发现了关于FragmentManager的惊人事实,它真的帮助了我正在开发的应用程序。我想你也会很高兴!请查看编辑后的答案。 - Alireza Ahmadi

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