FragmentTransaction animation to slide in over top

Solution 1:

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

Solution 2:

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>

Solution 3:

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

}

Solution 4:

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

Solution 5:

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.