不播放弹出动画的情况下弹出片段回退栈

61

我使用以下代码将一个Fragment推到Fragment堆栈中:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right,
     R.anim.slide_in_left, R.anim.slide_out_left);
fragmentTransaction.replace(getId(), newFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
这种方法在片段堆栈弹出时会播放片段弹出动画,例如通过按下返回按钮。然而,有些情况下我想要在不显示此动画的情况下弹出片段回退堆栈,例如因为我刚从另一个活动中返回并且想立即显示以前的片段,而不需要动画效果。
示例导航可能如下所示:
- 用户位于具有根片段的开始屏幕上 - 他选择了根片段上的一个项目,该项目显示一个新片段来显示该项目的详细信息。它使用片段事务进行操作,为推送和弹出情况设置动画(因此当用户按返回按钮时,转换是动画的)。 - 从此片段开始一个活动,该活动(出于某种原因)删除了刚刚显示的项目。 - 当此活动完成时,我希望返回到根片段,而不显示“详细片段”的“弹出动画”。
有没有一种方法可以在不播放指定的弹出动画的情况下弹出片段回退堆栈?

你所说的“我刚刚从另一个活动返回”是什么意思?你能告诉我过渡步骤吗,也就是你尝试导航的方式。 - 500865
嗨500865,我在问题中添加了一个示例导航。 - ChristophK
在setCustomAnimations中将第三个和第四个参数设置为0不就可以实现这个效果吗? - Boy
这将在任何情况下禁用动画,但通常我想要动画,并且必须在片段推入堆栈时调用setCustomAnimations。 - ChristophK
你也可以尝试像这样 - MatrixDev
13个回答

92

所以Warpzit的思路是正确的,只是他没有很好地解决您具体的问题。我遇到了完全相同的问题,以下是我解决它的方法。

首先,我创建了一个静态布尔变量(为了简单起见,我们将其放在FragmentUtils类中)...

public class FragmentUtils {
    public static boolean sDisableFragmentAnimations = false;
}

然后,在你的每个片段中,你需要重写onCreateAnimation方法...

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if (FragmentUtils.sDisableFragmentAnimations) {
        Animation a = new Animation() {};
        a.setDuration(0);
        return a;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

然后,当你需要清除Activity的回退栈时,只需执行以下操作...

public void clearBackStack() {
    FragmentUtils.sDisableFragmentAnimations = true;
    getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
    FragmentUtils.sDisableFragmentAnimations = false;
}

然后,调用clearBackStack()函数将带您回到根片段,而不会有任何过渡动画。

希望未来谷歌能够添加一个更少愚蠢的方法来完成这个操作。


9
可以实现功能,但片段仍会在一个短暂的帧中显示。有没有办法让它根本不出现? - plackemacher
1
+1 看起来是我在 2.3 和 4.3 上唯一有效的东西 :) - Dori
8
+1 需要记住的是,这仅适用于 popBackStackImmediate(而不适用于 popBackStack)。 - Bitcoin Cash - ADA enthusiast
4
为了使这个工作使用普通版本的popBackStack(而不是popBackStackImmediate),你需要避免在片段实际被弹出之前“取消设置”FragmentUtils.sDisableFragmentAnimations标志(即你需要一些回调来取消设置它)。 - Geoff
1
@Geoff 重写 onCreateAnimation 抛出 .lang.IllegalStateException: super.onCreateAnimation(transit, enter, nextAnim) must not be null。为什么? - maxbeaudoin
显示剩余4条评论

7

对于支持库,以下工作:

在应该具有自定义弹出动画的片段中,您可以使用自己的自定义动画覆盖onCreateAnimation。您可以获取它并根据需要设置某种参数。可能需要进行一些额外的工作才能使其与常规片段配合使用。

下面是我覆盖并更改设置持续时间的示例:

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    Animation anim = (Animation) super.onCreateAnimation(transit, enter, nextAnim);
    if(!enter) {
        if(anim != null) {
            anim.setDuration(0); // This doesn't seem to be called.
            return anim;
        } else {
            Animation test = new TestAnimation();
            test.setDuration(0);
            return test;
        }
    }
    return anim;
}

private class TestAnimation extends Animation {

}

Warpzit的回答实际上是正确的,只是没有解释得非常清楚...由于我无法在评论中发布代码,我将提交另一个答案... - Geoff

6

用户在起始屏幕上,使用根片段。

假设根片段包含在Activity A中。

他选择了根片段上的一个项目,然后显示一个新的片段来显示该项目的详细信息。它使用片段事务进行操作,并为推送和弹出情况设置动画(因此当用户按下返回按钮时,转换会有动画效果)。

该事务被添加到后退堆栈中。这意味着当从详细片段按下返回按钮时,弹出过程是有动画的。

从这个片段开始一个活动,该活动(出于某种原因)删除了刚刚显示的项目。

假设它是Activity B。

当这个活动完成时,我想返回到根片段,而不显示“详细片段”的“弹出动画”。

实现这种行为的一种方法是在Activity B中执行以下操作:

Intent intent = new Intent(this, A.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();

这将启动Activity A并根据文档重置其为其根状态。(请检查该部分的最后一段,其中说“此启动模式还可以与FLAG_ACTIVITY_NEW_TASK一起使用:......”)

通过这种配置,动画将在默认情况下存在,而在特殊情况下,您可以使用以下方法控制动画:

intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);

这个功能可以启动一个新的活动而不带有任何动画效果。如果您需要任何动画效果,可以使用 overridePendingTransition 方法来实现。


如果您查看我在答案中发布的链接中的第一段,您将看到“其上的所有其他活动都将关闭”。因为C在堆栈中位于A下面(Tabhost Activity将位于堆栈底部),所以它不会重置C。 - 500865
很遗憾,这不起作用:当我在活动B中运行您的代码时,活动A不再显示在其选项卡中,而是以“全屏”方式显示。 - ChristophK
不幸的是,这意味着在销毁并重新创建 C 的任何其他选项卡时,需要重新创建状态和片段堆栈。但可能没有其他方法了,对吧? - ChristophK
据我所知,没有任何方法可以实现。但看起来似乎不可能。你可以尝试一下,看看是否有什么可行的办法。祝你好运 :) - 500865
无论如何,感谢您的努力和可能适用于其他类似问题的解决方案! - ChristophK
显示剩余2条评论

5

实际上,现在Android有一种方法可以不需要@Geoff提出的解决方法就能实现这个功能。

为了避免在popBackStack()时运行动画,在填充您的fragment时,向您的fragmentTransaction添加.setReorderingAllowed(true)

例如:

supportFragmentTransaction.beginTransaction()
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .setCustomAnimations(
        android.R.anim.fade_in,
        android.R.anim.fade_out,
        android.R.anim.fade_in,
        android.R.anim.fade_out
    )
    .replace(yourContainer.id, yourFragment)
    .commit()

您会注意到,如果设置了setReorderingAllowed(true),则弹出动画将不再播放。 实际结果与@Geoff的答案类似。


这正是我所寻找的。运行得非常好! - Mücahid Kambur
+1,谢谢!注意:使用setReorderingAllowed避免动画效果与popBackStackImmediate不兼容,只能使用popBackStack - remain4life

1

所以,我想提出对@Geoff答案的一个小改变建议。

与其拥有全局静态布尔值,我宁愿拥有一个本地非静态值。这是我想到的。

创建一个接口

public interface TransitionAnimator {
    void disableTransitionAnimation();
    void enableTransitionAnimation();
}

让该片段实现该接口。
public class MyFragment extends Fragment implements TransitionAnimator {

    private boolean mTransitionAnimation;

    @Override
    public void disableTransitionAnimation() {
        mTransitionAnimation = false;
    }

    @Override
    public void enableTransitionAnimation() {
        mTransitionAnimation = true;
    }

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        Animation result;
        if (!mTransitionAnimation) {
            Animation dummyAnimation = new Animation() {
            };
            dummyAnimation.setDuration(0);
            result = dummyAnimation;
        } else {
            result = super.onCreateAnimation(transit, enter, nextAnim);
        }
        return result;
    }

然后,当您想要禁用片段的转换动画时,只需执行以下操作:


if (fragment instanceof TransitionAnimator) {
    ((TransitionAnimator) fragment).disableTransitionAnimation();
}

启用它们,只需执行

if (fragment instanceof TransitionAnimator) {
    ((TransitionAnimator) fragment).enableTransitionAnimation();
}

如果你想对片段管理器中的所有片段执行相同操作,只需执行以下操作:
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for (Fragment fragment : fragments) {
    if (fragment instanceof TransitionAnimator) {
        // disable animations
        ((TransitionAnimator) fragment).disableTransitionAnimation();
    }
}

非常相似,但没有静态字段。

0

这是对@Geoff出色回答的跟进,但适用于更动态和实时的场景。

我原本想这篇文章只是一个小小的帖子,但现在我意识到它有点失控了。然而,代码都在那里,我觉得它非常有用,尽管它涵盖了比仅禁用转换动画更多的内容。

通常,当我使用Fragments时,我喜欢有一个BaseFragment连接到一个BaseActivityCallback。这个BaseActivityCallback可以被我的Fragments用来在自己的顶部添加一个新的Fragment,甚至可以弹出它下面的Fragments,因此希望禁用弹出动画--或者静默地弹出:

interface BaseActivityCallback
{
    void addFragment ( BaseFragment f, int containerResId );
    void popFragment ( boolean silently );
}

class BaseActivity extends android.support.v4.app.FragmentActivity implements BaseActivityCallback
{
    public void addFragment ( BaseFragment f, int containerResId )
    {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.enter, R.anim.pop_exit); // https://dev59.com/im445IYBdhLWcg3wg6tm#17488542
        ft.addToBackStack(DEFAULT_FRAGMENT_STACK_NAME);
        ft.replace(containerResId, fragment);
        ft.commitAllowingStateLoss();
    }        

    public void popFragment ( boolean silently )
    {
        FragmentManager fm = getSupportFragmentManager();                
        if ( silently ) {
            int count = fm.getFragments().size();
            BaseFragment f = (BaseFragment)fm.getFragments().get(count-1);
            f.setDisableTransitionAnimations(true);
        }
        fm.popBackStackImmediate();
    }
}

public abstract class BaseFragment extends android.support.v4.app.Fragment
{
    private static final String TAG = "BaseFragment";
    private final String STATE_DISABLE_TRANSITION_ANIMATIONS = TAG+".stateDisableTransitionAnimations";

    protected BaseActivityCallback baseActivityCallback;
    private boolean disableTransitionAnimations;    

    @Override
    public void onCreate ( @Nullable Bundle savedInstanceState )
    {
        super.onCreate(savedInstanceState);
        disableTransitionAnimations = (savedInstanceState==null ? false : savedInstanceState.getBoolean(STATE_DISABLE_TRANSITION_ANIMATIONS, false));
    }

    @Override
    public void onAttach ( Context context )
    {
        super.onAttach(context);
        baseActivityCallback = (BaseActivityCallback)context;
    }

    @Override
    public void onSaveInstanceState ( Bundle outState )
    {
        super.onSaveInstanceState(outState);
        outState.putBoolean(STATE_DISABLE_TRANSITION_ANIMATIONS, disableTransitionAnimations);
    }

    @Override
    public Animation onCreateAnimation ( int transit, boolean enter, int nextAnim )
    {
        if ( disableTransitionAnimations ) {
            Animation nop = new Animation(){};
            nop.setDuration(0);
            return nop;
        }
        return super.onCreateAnimation(transit, enter, nextAnim);
    }

    public void setDisableTransitionAnimations ( boolean disableTransitionAnimations )
    {
        this.disableTransitionAnimations = disableTransitionAnimations; // https://dev59.com/bWox5IYBdhLWcg3weUCe#11253987
    }
}

现在,您可以创建您的MainActivity并显示一个Fragment1,该片段可以添加另一个Fragment2,该片段可能会静默地弹出Fragment1
public class MainActivity extends BaseActivity
{
    protected void onCreate ( Bundle savedInstanceState )
    {
        setContentView(R.layout.main_activity);
        ...
        if ( getSupportFragmentManager().getFragments() != null && !getSupportFragmentManager().getFragments().isEmpty() ) {

            addFragment( FragmentA.newInstance(), R.id.main_activity_fragment_container );
        }
    }
    ...
}

public class FragmentA extends BaseFragment
{
    public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
    {
        ViewGroup root = (ViewGroup)inflater.inflate(R.layout.fragment_a, container, false);
        ...
        root.findViewById(R.id.fragment_a_next_button)
            .setOnClickListener( new View.OnClickListener() {
                 public void onClick ( View v ) {
                      baseActivityCallback.addFragment( FragmentB.newInstance(), R.id.main_activity_fragment_container );
                 }
             });
    }
}

public class FragmentB extends BaseFragment
{
    public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
    {
        ViewGroup root = (ViewGroup)inflater.inflate(R.layout.fragment_b, container, false);
        ...
        root.findViewById(R.id.fragment_b_pop_silently_button)
            .setOnClickListener( new View.OnClickListener() {
                 public void onClick ( View v ) {
                      baseActivityCallback.popFragment( true );
                 }
             });
    }
}

0

通过使用overridePendingTransition(int enterAnim, int exitAnim),可以很容易地实现这一点,其中两个参数都设置为0表示没有动画。

FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
    fm.popBackStack();
    overridePendingTransition(0, 0);
}

3
只有在处理活动时才能实现此功能。这个问题是指片段。 - LightMan

0
更简单的解决方案:
for (fragment in supportFragmentManager.fragments) {
    removeFragment(fragment)
}
if (supportFragmentManager.backStackEntryCount > 0) {
    supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}

0

在您希望无动画弹出片段并在进入时仍保留动画的片段中覆盖此功能

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if(!enter){
        Animation a = new Animation() {};
        a.setDuration(0);
        return a;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

0
在回答你的问题之前,我需要问一个问题。
在第二个活动的onBackPressed()方法中,你能够访问第一个活动的后退栈吗?
如果可以,那么你可以调用popBackStackImmediate(String trnaisiotnName, int inclusive)方法,它将从后退栈中移除片段转换,并且你不需要担心动画。
我假设你可以访问上一个活动的后退栈,否则这个方法将无法工作。

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