Pop the fragment backstack without playing the Pop-Animation
Asked Answered
B

13

61

I push a fragment on the fragment stack using the following code:

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

This way, when the fragment stack is popped, e.g. by pressing the back button, a fragment pop animation is played. However, there are situations in which i would like to pop the fragment backstack without showing this animation, e.g. because I just returned from another activity and want to display the previous fragment at once, without animation.

An example navigation could look like this:

  • The user is on the start screen with the root fragment
  • He selects an item on the root fragment which then displays a new fragment to show details of that item. It does so using a fragment transaction that sets animations both for the push and the pop case (so when the user presses the back button, the transition is animated)
  • From this fragment he starts an activity which (for whatever reason) deletes the item that was just shown
  • When this activity finishes, I would like to return to the root fragment without showing the "pop animation" of the "detail fragment"

Is there a way to pop the fragment backstack without playing the specified pop animation?

Breslau answered 8/2, 2012 at 13:32 Comment(5)
What do you mean by I just returned from another activity? Can you tell the transition steps i.e., how you are trying to navigate.Aggravation
Hi 500865, I added an example navigation to the question.Breslau
doesn't setting 0 as 3rd and 4th argument in setCustomAnimations do this?Limestone
this would disable animations in every case, however I usually want the animation, and setCustomAnimations has to be called when the fragment is pushed on the stack.Breslau
You can also try do it like [this][1] [1]: https://mcmap.net/q/324219/-clear-android-fragment-back-stack-without-poppingLatonya
P
91

So Warpzit was on the right track, he just didn't address your specific issue too well. I came across the exact same issue and here is how I solved it.

First I created a static boolean variable (for simplicity's sake, lets put it in the FragmentUtils class)...

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

Then, in EVERY fragment you have, you need to override the onCreateAnimation method...

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

Then, when you need to clear the backstack from your activity simply do the following...

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

And voila, a call to clearBackStack() will drop you back into the root fragment without any transition animations.

Hopefully the big G will add a less stupid way of doing this in the future.

Plenum answered 28/6, 2012 at 22:48 Comment(9)
This works but the fragment will still show up for a brief frame. Is there a way to have it not show up at all?Crusade
+1 seems to be the only thing that works for me on 2.3 and 4.3 :)Echevarria
+1 One must keep in mind this only works with popBackStackImmediate (it does not work with popBackStack).Viscacha
For this to work using the regular version of popBackStack (instead of popBackStackImmediate), you'd have to avoid "unsetting" the FragmentUtils.sDisableFragmentAnimations flag until the fragments had actually been popped (i.e. you'd need some kind of callback to unset it).Plenum
Crikey, it works! I did implement it slightly different by letting each individual Fragment have a boolean disabledTransitionAnimation and accompanying public void setDisabledTransitionAnimation(boolean disabled) . Remember to preserve this state during Fragment.onSaveInstanceState(Bundle outState) and recreate it Fragment.onCreate(Bundle savedInstanceState).Lais
@Plenum overriding onCreateAnimation throws .lang.IllegalStateException: super.onCreateAnimation(transit, enter, nextAnim) must not be null. Why?Container
Because you're not allowed to return a null animation from it. Instead, return an empty animation with a 0ms duration like the sample above.Plenum
This feels like a work around which :( why is android like this :'(Min
Use setReorderingAllowed(true) then you don't have to do this. Look at my answer below.Min
E
7

So for the support library following works:

In the fragment which should have a custom pop animation you override the onCreateAnimation with your own custom one. You could get it and set some kind of parameter depending on what you want. There might need to be done some extra work to make it work with regular fragments.

Here is the example where I'm overriding it and changing the set duration:

@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 {

}
Evolutionary answered 21/5, 2012 at 9:37 Comment(1)
Warpzit's answer is actually correct, just not explained super well... Since I can't post code in a comment, I'm going to submit another answer...Plenum
A
6

The user is on the start screen with the root fragment

Lets say the root fragment is contained in Activity A.

He selects an item on the root fragment which then displays a new fragment to show details of that item. It does so using a fragment transaction that sets animations both for the push and the pop case (so when the user presses the back button, the transition is animated)

The transaction is added to the back stack. Which means that when the back button is pressed from detail fragment, the popping process is animated.

From this fragment he starts an activity which (for whatever reason) deletes the item that was just shown.

Lets say it is Activity B

When this activity finishes, I would like to return to the root fragment without showing the "pop animation" of the "detail fragment"

One way of getting this behavior is by doing this in 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();

This will start the Activity A resetting it to its root state according to the documentation.(check the last paragraph in the section which says "This launch mode can also be used to good effect in conjunction with FLAG_ACTIVITY_NEW_TASK:......")

With this configuration, the animation will be present in the default case while in the special case you can control the animation using :

intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);

Which starts new activity without any animations. If you do want any animation, you can do it using the overridePendingTransition method.

Aggravation answered 17/2, 2012 at 13:29 Comment(7)
Thanks for your answer! There is just one problem for my specific app: The Activity A is contained inside a TabHost which is created by my "main activity" (let's call it C). Is there a way to reset A without resetting C?Breslau
If you check the first paragraph in the link I posted in the answer, you will see "all of the other activities on top of it will be closed". Because C is below A in the stack(Tabhost Activity will be at the bottom of the stack) it will not reset C.Aggravation
Unfortunately, this does not work: When I run your code in the activity B, the activity A is not displayed in its tab anymore, instead it is shown "full-screen"Breslau
Then you will have to start the Activity C with Tab holding Activity A as the selected tab :)Aggravation
Unfortunately, that would mean recreating the state and fragment stack on any other tab that is destroyed and recreated along with C. But probably there is no other way, is there?Breslau
Nothing that I'm aware of. But it doesn't look like it is possible. You can play around and see if something works. Good luck :)Aggravation
Well, thank you anyway for your effort and a solution that will probably work for others with a similar problem!Breslau
A
5

Android actually now has a way to do this without the work around @Geoff answered.

To avoid the animation to run on popBackStack(), when inflating your fragments add .setReorderingAllowed(true) to your fragmentTransaction.

So for example:

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()

You'll notice that if you set setReorderingAllowed(true), the pop animation would no longer play. The results are actually similar to the result of @Geoff's answer.

Adieu answered 3/5, 2019 at 1:22 Comment(2)
This was what i looked for. Works like a charm!Cowgirl
+1, thank you! Notice: avoiding animation with setReorderingAllowed does not work with popBackStackImmediate, use only popBackStackPerjure
V
1

So, I'd like to suggest a small change to @Geoff's answer.

Instead of having a global static boolean, I'd rather have a local non-static one. This is what I came up with.

Create an interface

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

Make the fragment implement that interface.

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

And then, when you want to disable the transition animations for a fragment, just do

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

to enable them, just do

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

If you want to do the same for all the fragments in the fragment manager, just do

List<Fragment> fragments = getSupportFragmentManager().getFragments();
for (Fragment fragment : fragments) {
    if (fragment instanceof TransitionAnimator) {
        // disable animations
        ((TransitionAnimator) fragment).disableTransitionAnimation();
    }
}

Very similar, but without static fields.

Victory answered 25/6, 2016 at 21:6 Comment(0)
L
0

Just use another overloaded method of setCustomAnimation() and in which do not set the R.anim.slide_out and that will solve your problem

Cheers :)

Lianneliao answered 14/2, 2012 at 8:45 Comment(1)
This would however prevent any animation from playing when the user presses the back button, wouldn't it? I want the animation in the default case (user presses back button), however there is a case where the fragment stack is popped in response to a button push, and in this case I do not want an animation.Breslau
P
0

Before answering your question, I need to ask a question myself.

In the onBackPressed() method of the second activity, can you access the backstack of the first activity?

If yes, then you can call popBackStackImmediate(String trnaisiotnName, int inclusive) and it will remove the fragment transition from the backstack, and you dont need to worry about animations.

I am assuming you can access backstack of the previous activity, otherwise this wont work

Prepositive answered 19/4, 2013 at 16:10 Comment(0)
S
0

This is fairly easy to achieve through overridePendingTransition(int enterAnim, int exitAnim) with both 0 for no animation.

FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
    fm.popBackStack();
    overridePendingTransition(0, 0);
}
Salford answered 20/3, 2016 at 10:43 Comment(1)
This only works if you are working with activities.The question is referring to fragments.Sauternes
L
0

This is a follow-up to @Geoff's excellent answer, but fitted for a more dynamic and real-live scenario.

I imagined this being a nice little post, but I realize now that it got a little out of hand. However, the code is all there and I find it really useful, though it covers a lot more than just how to disable transition animations.

Usually, when I work with Fragments I like to have a BaseFragment that attaches to a BaseActivityCallback. This BaseActivityCallback can be used by the my Fragments to add a new Fragment on top of itself, or even to pop Fragments beneath it, hence the desire to disable pop animations -- or pop silently:

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://mcmap.net/q/101659/-android-fragments-and-animation
        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://mcmap.net/q/322567/-pop-the-fragment-backstack-without-playing-the-pop-animation
    }
}

Now you can create your MainActivity and have that show a Fragment1 which can add another Fragment2 which may in turn pop Fragment1 silently:

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 );
                 }
             });
    }
}
Lais answered 5/9, 2016 at 19:41 Comment(0)
B
0

Override this in the fragment that you want to pop without animation and still keep the animation when you enter

@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);
}
Besides answered 2/10, 2017 at 2:42 Comment(0)
R
0

Easier solution:

for (fragment in supportFragmentManager.fragments) {
    removeFragment(fragment)
}
if (supportFragmentManager.backStackEntryCount > 0) {
    supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
Regen answered 21/2, 2019 at 11:17 Comment(0)
G
0

@Geoff solutoin in Kotlin:

override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation {
    val a: Animation = object : Animation() {}
    a.duration = 0
    return a
}

If you use Navigation library, use empty animation instead:

val animation = NavOptions.Builder().build()
Gleason answered 13/7, 2023 at 13:4 Comment(0)
A
-1

Reply to Geoff and plackemacher comment.

You can try to remove all views from this Fragment. Then fragment will show but it should be transparent.

Remove all-1 (I use navigate drawer so drawer fragment should stay) fragment:

    int size = fragmentsList.size ()-1;

    FragmentTransaction transaction = fragmentManager.beginTransaction ();
    transaction.setTransition (FragmentTransaction.TRANSIT_NONE);

    Fragment fragment;

    for (int i = size ; i > 0 ; i--)
    {
        fragment = fragmentsList.get (i);
        if(fragment != null)
        {
            View viewContainer = fragment.getView ();
            if (viewContainer != null)
            {
                ((ViewGroup) viewContainer).removeAllViews ();
            }
            transaction.remove (fragment);
        }
    }

    size = fragmentManager.getBackStackEntryCount ();

    for (int i = 0; i < size  ; i++)
    {
        fragmentManager.popBackStack (null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }

Sorry for my English

Acquittance answered 24/11, 2014 at 19:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.