FragmentTransaction animation for transition from above

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

  • Fragment A shows
  • Replace fragment A with fragment B. Fragment A should remain visible during replacement. Fragment B should slide to the right. Fragment B should slide at the top of Fragment A.

I have no problem adjusting the slide in the animation. My problem is that I cannot figure out how to make Fragment A where it is and to be UNDER Fragment B while the slide is in animation. 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 defined the R.anim.nothing animation for the "out" animation because I really don't want Fragment A to do anything except to 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" /> 
+61
android android-fragments android-animation
Oct 22 '12 at 6:24
source share
8 answers

I found a solution that works for me. I ended up using ViewPager with the FragmentStatePagerAdapter. ViewPager provides swiping behavior, and the FragmentStatePagerAdapter swaps fragments. The last trick to achieve the effect of having one page visible "below" the incoming page is to use PageTransformer. PageTransformer overrides the default transition in the ViewPager between pages. Here is an example PageTransformer that provides a translation effect and a small amount of scaling on the left side of the 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); } } } } 
+8
Jan 11 '13 at 5:18
source share

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 did 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 am showing my fragment in FrameLayout.

It works fine, but the older fragment is still in my view, I let the android manage it as he wants, because if I put:

 ft.remove(myolderFrag); 

it does not appear during 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> 
+20
Dec 13 '12 at 11:53
source share

Starting with Lollipop, you can increase the translation Z of your incoming fragment. It will appear above the exit.

For example:

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

If you want to change 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; } 
+15
Nov 19 '15 at 23:25
source share

After more experimentation (hence this is my second answer), the problem is that R.anim.nothing means “disappear” when we want another animation to explicitly say “stay”. The solution is to define a true "do nothing" animation:

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 just do as 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(); 
+5
Feb 23 '15 at 2:34
source share

I found an alternative solution (not much tested) that I find more elegant than the suggestions 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 called from the inner class of the OutgoingFragment class.

A new fragment is inserted, the animation ends, then the old fragment is deleted.

Some applications may have memory problems, but this is better than saving both fragments indefinitely.

+2
Nov 06 '14 at 21:12
source share
 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
Sep 17 '16 at 12:55
source share

This is my current solution for anyone interested.

In the function of adding a 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 kind of AnimationListener instead of the handler above, but I did not see that you could use it to detect the end of a transaction animation that was not bound to a fragment, for example onCreateAnimation() . Any suggestions / corrections with the appropriate listener will be appreciated.

I’ll point out that Fragment I’m adding this method, it’s easy, so there’s no problem for me to contain them in the fragment container along with the fragment that they are on top of.

If you want to remove the fragment, you can put fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss(); in Handler Runnable , and in 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(); } 

Please note that the above solution does not work for the first fragment in the container (since it is not in the background stack), so you should have another way to restore it, maybe keep a link to the first fragment anyway ... But if you don’t use the back stack and always replace fragments manually, this is not a problem. OR you can add all the fragments to the back stack (including the first one) and redefine onBackPressed to make sure your activity is complete and not display a blank screen when only one fragment remains in the back stack.

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

FragmentTransaction . detach () :

Disconnect this fragment from the user interface. This is the same state as when stacked on the back stack: the fragment is deleted from the user interface, however, its state is still actively controlled by the fragment manager. Upon entering this state, its hierarchy of representations is destroyed.

FragmentTransaction . attach () :

Reattach the fragment after it has previously been disconnected from the user interface with disconnection (fragment). This will cause its view hierarchy to be recreated, attached to the user interface, and displayed.

0
Nov 12 '16 at 20:15
source share

Based on jfrite answer enclosing 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; } } 

This is how I use the helper snippet.

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

This is how I start the animation fragment

 FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.slide_in, R.anim.hold, R.anim.hold, R.anim.slide_out); 
0
Jan 21 '19 at 9:03
source share



All Articles