FragmentTransaction animation to slide in over top

前端 未结 9 1329
星月不相逢
星月不相逢 2020-11-30 20:26

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

  1. Fragment A is showing
  2. Replace Fragment A with Fragment B. F
相关标签:
9条回答
  • 2020-11-30 20:36

    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:

    With elevation:

    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>
    
    0 讨论(0)
  • 2020-11-30 20:40
    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 讨论(0)
  • 2020-11-30 20:42

    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();
    
    0 讨论(0)
  • 2020-11-30 20:49

    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.

    0 讨论(0)
  • 2020-11-30 20:53

    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);
    
    0 讨论(0)
  • 2020-11-30 20:55

    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>
    
    0 讨论(0)
提交回复
热议问题