78

I am trying to achieve the following effect using FragmentTransaction.setCustomAnimations.

  1. Fragment A is showing
  2. Replace Fragment A with Fragment B. Fragment A should remain visible during the replacement. Fragment B should slide in from the right. Fragment B should slide in OVER THE TOP of Fragment A.

I have no problem getting the slide in animation setup. My problem is that I cannot figure out how to make Fragment A stay where it is and be UNDER Fragment B while the slide in animation is running. No matter what I do it seems that Fragment A is on top.

How can I achieve this?

Here is the FragmentTransaction code:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing,
    R.anim.slide_out_right);
ft.replace(R.id.fragment_content, fragment, name);
ft.addToBackStack(name);
ft.commit();

As you can see I have defined an animation R.anim.nothing for the "out" animation because I actually don't want Fragment A to do anything other than just stay where it is during the transaction.

Here are the animation resources:

slide_in_right.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="100%p"
    android:toXDelta="0"
    android:zAdjustment="top" />

nothing.xml

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromAlpha="1.0"
    android:toAlpha="1.0"
    android:zAdjustment="bottom" />
Matt Accola
  • 4,090
  • 4
  • 28
  • 37
  • 1
    I have filed a bug report because the current Z ordering is just plain wrong. https://code.google.com/p/android/issues/detail?id=163384&thanks=163384&ts=1428443402 – sbaar Apr 07 '15 at 21:51

12 Answers12

28

Update (June 16, 2020)

Starting from fragment library 1.2.0 the recommanded way to fix this issue is to use FragmentContainerView with FragmentTransaction.setCustomAnimations().

According to the documentation:

Fragments using exit animations are drawn before all others for FragmentContainerView. This ensures that exiting Fragments do not appear on top of the view.

Steps to fix this issue are:

  1. Update fragment library to 1.2.0 or more androidx.fragment:fragment:1.2.0;
  2. Replace your xml fragment container (<fragment>, <FrameLayout>, or else) by <androidx.fragment.app.FragmentContainerView>;
  3. Use FragmentTransaction.setCustomAnimations() to animate your fragments transitions.

Previous answer (Nov 19, 2015)

Starting from Lollipop, you can increase de translationZ of your entering fragment. It will appear above the exiting one.

For example:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ViewCompat.setTranslationZ(getView(), 100.f);
}

If you want to modify the translationZ value only for the duration of the animation, you should do something like this:

@Override
public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
    Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim);
    nextAnimation.setAnimationListener(new Animation.AnimationListener() {

        private float mOldTranslationZ;

        @Override
        public void onAnimationStart(Animation animation) {
            if (getView() != null && enter) {
                mOldTranslationZ = ViewCompat.getTranslationZ(getView());
                ViewCompat.setTranslationZ(getView(), 100.f);
            }
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            if (getView() != null && enter) {
                ViewCompat.setTranslationZ(getView(), mOldTranslationZ);
            }
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });
    return nextAnimation;
}
JFrite
  • 444
  • 5
  • 6
  • Good job on finding this, was looking for this solution long ago and now it is solved. – Rod_Algonquin Dec 18 '15 at 04:32
  • Thanks. Seems to work. May I know why for overriding `onCreateAnimation`, we need to manually construct Animation via `AnimationUtils.loadAnimation`? I do realize if I try to obtain `Animation` via super.onCreateAnimation, it is null. – Cheok Yan Cheng Jul 04 '17 at 02:18
  • @CheokYanCheng Because `Fragment.onCreateAnimation()` method does nothing by default and just returns null. This method is just there to leave you the possibility to create yourself the animation. If you take a look at the method `FragmentManager.loadAnimation()` you will see it first tries to get an animation from `fragment.onCreateAnimation()` and if it returns null the FragmentManager creates the animation itself. – JFrite Jul 12 '17 at 13:34
  • 2
    Worth noting that decreasing the elevation at the ned can cause some flickering because of timing issues, meaning that the replaced fragment did not complete yet the animation. I solved it by returning the elevation with a small delay. – Efi G Jan 16 '19 at 13:08
  • 2
    Doesnot work for me. It gives android.content.res.Resources$NotFoundException: Resource ID #0x0 error – Rohit Singh Jun 12 '20 at 20:47
  • @RohitSingh you can try the updated solution and it should work – JFrite Jun 16 '20 at 09:15
  • This seems to choke during a switch from day mode to night mode. – Brill Pappin Oct 16 '20 at 18:38
  • Even with the new fragment version doesn't slide over – Tiago Almeida Feb 04 '21 at 13:18
20

I don't know if you still need an answer but I recently needed to do the same and I found a way to do what you want.

I made something like this :

FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();

MyFragment next = getMyFragment();

ft.add(R.id.MyLayout,next);
ft.setCustomAnimations(R.anim.slide_in_right,0);
ft.show(next);
ft.commit();

I display my Fragment in a FrameLayout.

It work fines but the older Fragment is still in my View, I let android manage it like he wants because if I put:

ft.remove(myolderFrag);

it is not displayed during the animation.

slide_in_right.xml

    <?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="150" android:fromXDelta="100%p" 
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="0" />
 </set>
MaTriXy
  • 1,659
  • 1
  • 20
  • 27
Meph
  • 280
  • 3
  • 9
  • Thank you and I am still looking for a solution. Your answer is accurate but unfortunately it doesn't completely address the problem. I can't leave the old fragment in memory or it will eat up too much memory. I took the approach of creating a bitmap from the outgoing view and placing that under the view that the new fragment is being added to. That achieves the effect but again I am having memory problems because the bitmap is too big. I am looking into a couple other approaches including ViewPager w/ FragmentStatePagerAdapter and one FrameLayout per Fragment. Will post back later. – Matt Accola Dec 28 '12 at 23:27
  • 1
    @domji84 ` ` – Meph Jul 29 '13 at 08:46
  • Thanks. `overridePendingTransition(R.anim.my_animation, 0)` is the line I was looking for. – Adam Johns Sep 12 '14 at 13:55
  • On support v22 it seems all this does is make the old fragment disappear when the transition starts. – sbaar Apr 07 '15 at 20:05
8

I found a solution which works for me. I ended up using a ViewPager with a FragmentStatePagerAdapter. The ViewPager provides the swiping behavior and the FragmentStatePagerAdapter swaps in the fragments. The final trick to achieve the effect of having one page visible "under" the incoming page is to use a PageTransformer. The PageTransformer overrides the ViewPager's default transition between pages. Here is an example PageTransformer that achieves the effect with translation and a small amount of scaling on the left-hand side page.

public class ScalePageTransformer implements PageTransformer {
    private static final float SCALE_FACTOR = 0.95f;

    private final ViewPager mViewPager;

    public ScalePageTransformer(ViewPager viewPager) {
            this.mViewPager = viewPager;
    }

    @SuppressLint("NewApi")
    @Override
    public void transformPage(View page, float position) {
        if (position <= 0) {
            // apply zoom effect and offset translation only for pages to
            // the left
            final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR;
            int pageWidth = mViewPager.getWidth();
            final float translateValue = position * -pageWidth;
            page.setScaleX(transformValue);
            page.setScaleY(transformValue);
            if (translateValue > -pageWidth) {
                page.setTranslationX(translateValue);
            } else {
                page.setTranslationX(0);
            }
        }
    }

}
Matt Accola
  • 4,090
  • 4
  • 28
  • 37
  • Great solution, but unfortunately does not fit if you want to change the stack, especially in the direction of decreasing - it leads to glare (redrawing the entire element). – Siruk Viktor Mar 27 '14 at 08:27
6

After more experimentation (hence this is my second answer), the problem seems to be that R.anim.nothing means 'disappear' when we want another animation that explicitly says 'stay put.' The solution is to define a true 'do nothing' animation like this:

Make file no_animation.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:interpolator="@android:anim/linear_interpolator"
        android:fromXScale="1.0"
        android:toXScale="1.0"
        android:fromYScale="1.0"
        android:toYScale="1.0"
        android:duration="200"
        />
    <alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="200"
        android:startOffset="200"
        />
</set>

Now simply do as you would otherwise:

getActivity().getSupportFragmentManager().beginTransaction()
                .setCustomAnimations(R.anim.slide_in_right, R.anim.no_animation)
                .replace(R.id.container, inFrag, FRAGMENT_TAG)
                .addToBackStack("Some text")
                .commit();
Merk
  • 1,441
  • 1
  • 15
  • 28
  • I will try this but I had though about doing a proper "stay_put" animation and all it did was cover the sliding animation of the new fragment with the old fragment staying on the foreground. But I see you offset the start of the no_animation so maybe it does the trick. I'll let you know what I think of your solution. – JDenais Feb 23 '15 at 03:42
  • 3
    Nice thought but this is not working as the transaction instantly remove the fragment and i can see the white screen below the second fragment during animation. – Muhammad Babar May 20 '15 at 10:13
  • 1
    Confused as to why you thought it needed this. I just used 0 where I wanted no transition and it worked great. Which is what it looks like you did above. – NineToeNerd Dec 05 '16 at 20:35
  • 1
    In my case, the "do nothing" animation was alright when using `add` but causing trouble when using `replace`. Replacing it with `0` did the trick. Thanks, @NineToeNerd – Cesar Castro Jan 06 '20 at 21:30
2

I found an alternate solution (not heavily tested) that I find more elegant than the proposals so far:

final IncomingFragment newFrag = new IncomingFragment();
newFrag.setEnterAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    clearSelection();
                    inFrag.clearEnterAnimationListener();

                    getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

 getActivity().getSupportFragmentManager().beginTransaction()
                    .setCustomAnimations(R.anim.slide_in_from_right, 0)
                    .add(R.id.container, inFrag)
                    .addToBackStack(null)
                    .commit();

This is being called from within an inner class of the OutgoingFragment class.

A new fragment is being inserted, the animation completes, then the old fragment is being removed.

There may be some memory problems with this in some applications but it is better than retaining both fragments indefinitely.

Merk
  • 1,441
  • 1
  • 15
  • 28
  • For me this is the best solution so far. I used to do the same but by pausing the system with a Handler during the animation and then removing the previous fragment, but this is a lot better. – JDenais Feb 23 '15 at 00:10
  • 1
    Where did the IncomingFragment and OutgoingFragment classes come from? Are they custom? Not quite understanding the implementation behind this. Perhaps when it was written there was such a function as `Fragment.setEnterAnimationListener(...)` ... or is that a custom function for that fragment? – NineToeNerd Dec 05 '16 at 20:33
1

Based on jfrite answer attaching my implementations

import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewCompat;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.util.Log;

public final class AnimationHelper {
    private AnimationHelper() {

    }

    private static String TAG = AnimationHelper.class.getSimpleName();
    private static final float ELEVATION_WHILE_ENTER_ANIMATION_IS_RUNNING = 100f;
    private static final int RESTORE_ANIMATION_DELAY = 16;

    /**
     * When replacing fragments with animations, by default the new fragment is placed below the replaced fragment. This
     * method returns an animation object that sets high elevation at the beginning of the animation and resets the
     * elevation when the animation completes. The {@link Animation} object that is returned is not the actual object
     * that is used for the animating the fragment but the callbacks are called at the appropriate time. The method
     * {@link Fragment#onCreateAnimation(int, boolean, int)} by default returns null, therefor, this method can be used
     * as the return value for {@link Fragment#onCreateAnimation(int, boolean, int)} method although it can return
     * null.
     * @param enter True if fragment is 'entering'.
     * @param nextAnim Animation resource id that is about to play.
     * @param fragment The animated fragment.
     * @return If nextAnim is a valid resource id and 'enter' is true, returns an {@link Animation} object with the
     * described above behavior, otherwise returns null.
     */
    @Nullable
    public static Animation increaseElevationWhileAnimating(boolean enter, int nextAnim,
                                                            @NonNull Fragment fragment) {
        if (!enter || nextAnim == 0) {
            return null;
        }
        Animation nextAnimation;
        try {
            nextAnimation = AnimationUtils.loadAnimation(fragment.getContext(), nextAnim);
        } catch (Resources.NotFoundException e) {
            Log.e(TAG, "Can't find animation resource", e);
            return null;
        }
        nextAnimation.setAnimationListener(new Animation.AnimationListener() {
            private float oldTranslationZ;

            @Override
            public void onAnimationStart(Animation animation) {
                if (fragment.getView() != null && !fragment.isDetached()) {
                    oldTranslationZ = ViewCompat.getTranslationZ(fragment.getView());
                    ViewCompat.setTranslationZ(fragment.getView(), ELEVATION_WHILE_ENTER_ANIMATION_IS_RUNNING);
                }
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                if (fragment.getView() != null && !fragment.isDetached()) {
                    fragment.getView().postDelayed(() -> {
                        // Decreasing the elevation at the ned can cause some flickering because of timing issues,
                        // Meaning that the replaced fragment did not complete yet the animation. Resting the animation
                        // with a minor delay solves the problem.
                        if (!fragment.isDetached()) {
                            ViewCompat.setTranslationZ(fragment.getView(), oldTranslationZ);
                        }
                    }, RESTORE_ANIMATION_DELAY);
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        return nextAnimation;
    }
}

Here is how I use the helper form the fragment.

@Override
    public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
        return AnimationHelper.increaseElevationWhileAnimating(enter, nextAnim, this);
    }

Here is how i start the fragment with animation

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.setCustomAnimations(R.anim.slide_in, R.anim.hold, R.anim.hold, R.anim.slide_out);
Efi G
  • 937
  • 8
  • 15
  • This fixes the issue when a new fragment is loaded, but there's a problem when pressing back button. How could I "reverse" the animation and avoid transition issues? – moyo May 12 '20 at 08:37
  • @moyo not sure i understood your question. anyway, i ended up adding this line: if (!enter || nextAnim == 0 || nextAnim == R.anim.hold) { return null; } – Efi G May 12 '20 at 12:43
1

Answer when using fragment manager

   getSupportFragmentManager()
            .beginTransaction()
            .setCustomAnimations(
                R.anim.slide_in_from_left,
                R.anim.slide_free_animation,
                R.anim.slide_free_animation,
                R.anim.slide_out_to_left
            )
            .replace(R.id.fragmentContainer, currentFragment, "TAG")
            .addToBackStack("TAG")
            .commit()

Answer when using Navigation:

     <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/slide_in_from_left"
            app:exitAnim="@anim/slide_free_animation"
            app:popEnterAnim="@anim/slide_free_animation"
            app:popExitAnim="@anim/slide_out_to_left" />

slide_in_from_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">

    <translate
        android:duration="500"
        android:fromXDelta="-100%p"
        android:fromYDelta="0%"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toXDelta="0%"
        android:toYDelta="0%" />

</set>

slide_out_to_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">

    <translate
        android:duration="500"
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toXDelta="-100%p"
        android:toYDelta="0%" />

</set>

slide_free_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <alpha
        android:duration="300"
        android:fromAlpha="1.0"
        android:startOffset="200"
        android:toAlpha="0.0" />

</set>
Antonis Radz
  • 3,036
  • 1
  • 16
  • 34
1

Quite a lot of steps but was able to do this with a mix of transitions and animations

 supportFragmentManager
         .beginTransaction()
         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN
         .setCustomAnimations(R.anim.nothing, R.anim.scale_out)
         .replace(R.id.fragment_container, fragment)
         .commit()

Nothing.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/transition_animation_time" />

Scale out - Animates old fragment on the way out

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXScale="100%"
    android:fromYScale="100%"
    android:toXScale="90%"
    android:toYScale="90%"
    android:pivotY="50%"
    android:pivotX="50%"/>

Fragment.kotlin

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        enterTransition = inflater.inflateTransition(R.transition.slide)
    }

res/transition/slide.xml - Animates new fragment on the way in

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:slideEdge="bottom" />
Tiago Almeida
  • 137
  • 1
  • 1
  • 9
0
FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction();
                ft.setCustomAnimations(0, R.anim.slide_out_to_right);
            if (!fragment.isAdded())
            {
                ft.add(R.id.fragmentContainerFrameMyOrders, fragment);
                ft.show(fragment);
            }
            else
                ft.replace(R.id.fragmentContainerFrameMyOrders, fragment);
            ft.commit();
0

This is my current workaround for anybody interested.

In the function for adding the new Fragment:

final Fragment toRemove = fragmentManager.findFragmentById(containerID);
if (toRemove != null) {
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                fragmentManager.beginTransaction().hide(toRemove).commit();
            }
        }, 
        getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100);
        // Use whatever duration you chose for your animation for this handler
        // I added an extra 100 ms because the first transaction wasn't always 
        // fast enough
}
fragmentManager.beginTransaction()
    .setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd)
    .addToBackStack(tag).commit();

and in onCreate:

final FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Fragment current = fragmentManager.findFragmentById(containerID);
                if (current != null && current.isHidden()) {
                    fragmentManager.beginTransaction().show(current).commit();
                }
            }
        });

I would prefer some sort of AnimationListener instead of a Handler above, but I didn't see any way you could use one to detect the end of the transaction animation that wasn't tied to the fragment like onCreateAnimation(). Any suggestions/edits with an appropriate listener would be appreciated.

I will point out the Fragments I am adding this way are lightweight so it isn't a problem for me to have them in the fragment container along with the fragment they are on top of.

If you want to remove the fragment you could put fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss(); in the Handler's Runnable, and in the OnBackStackChangedListener:

// Use back stack entry tag to get the fragment
Fragment current = getCurrentFragment(); 
if (current != null && !current.isAdded()) {
    fragmentManager.beginTransaction()
        .add(containerId, current, current.getTag())
        .commitNowAllowingStateLoss();
}

Note the above solution doesn't work for the first fragment in the container (because it isn't in the back stack) so you would have to have another way to restore that one, perhaps save a reference to the first fragment somehow... But if you don't use the back stack and always replace fragments manually, this is not an issue. OR you could add all fragments to the back stack (including the first one) and override onBackPressed to make sure your activity exits instead of showing a blank screen when only one fragment is left in the back stack.

EDIT: I discovered the following functions that could probably replace FragmentTransaction.remove() and FragmentTransaction.add() above:

FragmentTransaction.detach():

Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.

FragmentTransaction.attach():

Re-attach a fragment after it had previously been detached from the UI with detach(Fragment). This causes its view hierarchy to be re-created, attached to the UI, and displayed.

NineToeNerd
  • 307
  • 5
  • 17
0

Add an elevation to the layout.

I added an elevation to the fragment that slides in from the bottom of 30sp and it worked.

I have tried many solutions suggested here. Here are the full code and output that combines all the idea + adding elevation.

Output:

Without elevation:

enter image description here

With elevation:

enter image description here

Full Code:

Add an elevation to the root

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:elevation="30sp">
     ----

     ----
 </RelativeLayout>

How to slide in the fragment from the bottom?

getSupportFragmentManager()
            .beginTransaction()
            .setCustomAnimations(R.anim.slide_in_bottom, R.anim.do_nothing, R.anim.do_nothing, R.anim.slide_out_bottom)
            .replace(R.id.fragmentContainer, currentFragment, "TAG")
            .addToBackStack("TAG")
            .commit();

How to do the reverse when the back button is pressed?

getSupportFragmentManager()
            .popBackStack();

Since we have already defined enter and exit animation on setCustomAnimations() method. Calling popBackStack(); takes care of the reverse animation.

R.anim.slide_in_bottom

<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
       android:duration="500"
       android:fromYDelta="100%"
       android:toYDelta="0%">
    </translate>
</set>

R.anim.slide_out_bottom

<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="500"
        android:fromYDelta="0%"
        android:toYDelta="100%">
    </translate>
</set>

R.anim.do_nothing

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:interpolator="@android:anim/linear_interpolator"
        android:fromXScale="1.0"
        android:toXScale="1.0"
        android:fromYScale="1.0"
        android:toYScale="1.0"
        android:duration="500"/>
     <alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="500"
        android:startOffset="500" />
</set>
Rohit Singh
  • 16,950
  • 7
  • 90
  • 88
0

I have the same problem, and all of the solutions looks like overkill. Hiding fragments lead to errors when you should remove fragment after animation finished, but you don't know when it happens.


I found simplest solution: just set for previous fragment negative translationZ in transaction:

childFragmentManager.fragments.lastOrNull()?.let { it.view?.translationZ = -1f }

Whole transaction looks like:

childFragmentManager.commit {
    setCustomAnimations(...)
    childFragmentManager.fragments.lastOrNull()?.let { it.view?.elevation = -1f }
    replace(..., ...)
    addToBackStack(...)
}

That's all.

It shouldn't affect any behavior of your views. Everything should works as usual. Animation to back also works correctly (with correct order).

Pavel Shorokhov
  • 4,485
  • 1
  • 35
  • 44