web-dev-qa-db-fra.com

Animation FragmentTransaction à glisser par-dessus

J'essaie d'obtenir l'effet suivant en utilisant FragmentTransaction.setCustomAnimations.

  1. Le fragment A est visible
  2. Remplacez le fragment A par le fragment B. Le fragment A doit rester visible pendant le remplacement. Le fragment B devrait glisser de la droite. Le fragment B doit glisser au-dessus du fragment A.

Je n'ai aucun problème pour obtenir la diapositive dans la configuration de l'animation. Mon problème est que je ne peux pas comprendre comment faire en sorte que le fragment A reste là où il se trouve et soit SOUS le fragment B pendant l'exécution de la diapositive dans l'animation. Quoi que je fasse, il semble que le fragment A soit au top.

Comment puis-je atteindre cet objectif?

Voici le code FragmentTransaction:

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

Comme vous pouvez le constater, j'ai défini une animation avec R.anim.one pour l'animation "out", car je ne souhaite pas que le fragment A fasse autre chose que de rester à sa place pendant la transaction.

Voici les ressources d'animation:

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" />

rien.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" />
62
Matt Accola

J'ai trouvé une solution qui fonctionne pour moi. J'ai fini par utiliser un ViewPager avec un FragmentStatePagerAdapter. ViewPager fournit le comportement de balayage et le FragmentStatePagerAdapter échange dans les fragments. La dernière astuce pour obtenir l'effet d'une page visible "sous" la page entrante consiste à utiliser un PageTransformer. PageTransformer annule la transition par défaut du ViewPager entre les pages. Voici un exemple de PageTransformer qui réalise l'effet avec la traduction et une petite mise à l'échelle sur la page de gauche.

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
Matt Accola

Je ne sais pas si vous avez toujours besoin d'une réponse, mais j'ai récemment eu besoin de faire de même et j'ai trouvé un moyen de faire ce que vous voulez.

J'ai fait quelque chose comme ça:

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

J'affiche mon fragment dans un FrameLayout.

Cela fonctionne bien, mais l'ancien fragment est toujours dans ma vue, j'ai laissé Android le gérer comme il le voulait car si je mettais:

ft.remove(myolderFrag);

il n'est pas affiché pendant l'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
Meph

À partir de Lollipop, vous pouvez augmenter de translationZ de votre fragment entrant. Il apparaîtra au dessus de celui qui sort.

Par exemple:

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

Si vous souhaitez modifier la valeur translationZ uniquement pendant la durée de l'animation, procédez comme suit:

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

Après plus d'expérimentation (d'où ma deuxième réponse), le problème semble être que R.anim. Rien ne signifie "disparaître" lorsque nous voulons une autre animation qui dit explicitement "restez sur place". La solution consiste à définir une véritable animation "ne rien faire" comme ceci:

Créer le fichier 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>

Maintenant, faites simplement comme vous le feriez autrement:

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
Merk

J'ai trouvé une solution alternative (peu éprouvée) que je trouve plus élégante que les propositions proposées jusqu'à présent:

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

Ceci est appelé depuis une classe interne de la classe OutgoingFragment.

Un nouveau fragment est inséré, l'animation est terminée, puis l'ancien fragment est supprimé.

Cela peut poser quelques problèmes de mémoire dans certaines applications, mais cela vaut mieux que de conserver les deux fragments indéfiniment.

2
Merk

Basé sur jfrite answer en joignant mes implémentations 

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

Voici comment j'utilise la forme d'assistance du fragment. 

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

Voici comment je commence le fragment avec animation 

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.setCustomAnimations(R.anim.slide_in, R.anim.hold, R.anim.hold, R.anim.slide_out);
0
Efi G
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
Bishwajeet Biswas