78 votes

Animation de FragmentTransaction à glisser par-dessus

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

  1. Le fragment A montre
  2. Remplacez le Fragment A par le Fragment B. Le Fragment A doit rester visible pendant le remplacement. Le fragment B doit être inséré par la droite. Le fragment B doit être glissé par-dessus le fragment A.

Je n'ai aucun problème pour configurer l'animation de la diapositive. Mon problème est que je n'arrive pas à trouver comment faire en sorte que le fragment A reste là où il est et soit SOUS le fragment B pendant que l'animation de la diapositive est en cours. Quoi que je fasse, il semble que le fragment A se trouve au-dessus.

Comment puis-je y parvenir ?

Voici le code de 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 voir, j'ai défini une animation R.anim.nothing pour l'animation de "sortie" car je ne veux pas que le fragment A fasse autre chose que de rester où il est 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" />

1 votes

J'ai déposé un rapport de bogue car l'ordre Z actuel est tout simplement erroné. code.google.com/p/Android/issues/

28voto

JFrite Points 264

Mise à jour (16 juin 2020)

Démarrage à partir de la bibliothèque de fragments 1.2.0 la manière recommandée pour résoudre ce problème est d'utiliser FragmentContainerView con FragmentTransaction.setCustomAnimations() .

Según el documentation :

Les fragments utilisant des animations de sortie sont dessinés avant tous les autres pour des FragmentContainerView. Cela garantit que les fragments qui sortent n'apparaissent pas n'apparaissent pas au dessus de la vue.

Les étapes à suivre pour résoudre ce problème sont les suivantes :

  1. Mise à jour de la bibliothèque de fragments vers 1.2.0 ou plus androidx.fragment:fragment:1.2.0 ;
  2. Remplacez votre conteneur de fragments xml ( <fragment> , <FrameLayout> ou autre) par <androidx.fragment.app.FragmentContainerView> ;
  3. Utilisez FragmentTransaction.setCustomAnimations() pour animer les transitions de vos fragments.

Réponse précédente (19 nov. 2015)

A partir de Lollipop, vous pouvez augmenter la translationZ de votre fragment entrant. Il apparaîtra au-dessus du fragment sortant.

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 de translationZ uniquement pour la durée de l'animation, vous devez procéder 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;
}

0 votes

Bon travail pour avoir trouvé cette solution, je la cherchais depuis longtemps et maintenant elle est résolue.

0 votes

Merci. Il semble que cela fonctionne. Puis-je savoir pourquoi le remplacement de onCreateAnimation nous devons construire manuellement l'animation via AnimationUtils.loadAnimation ? Je réalise que si j'essaie d'obtenir Animation via super.onCreateAnimation, il est nul.

0 votes

@CheokYanCheng Parce que Fragment.onCreateAnimation() ne fait rien par défaut et renvoie simplement null. Cette méthode est juste là pour vous laisser la possibilité de créer vous-même l'animation. Si vous jetez un coup d'oeil à la méthode FragmentManager.loadAnimation() vous verrez qu'il essaie d'abord d'obtenir une animation à partir de fragment.onCreateAnimation() et s'il retourne null, le FragmentManager crée l'animation lui-même.

20voto

Meph Points 128

Je ne sais pas si vous avez encore besoin d'une réponse mais j'ai récemment eu besoin de faire la même chose 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, je laisse Android le gérer comme il veut car si je mets :

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>

0 votes

Je vous remercie et je suis toujours à la recherche d'une solution. Votre réponse est exacte mais malheureusement elle ne résout pas complètement le problème. Je ne peux pas laisser l'ancien fragment en mémoire, sinon il consommerait trop de mémoire. J'ai adopté l'approche consistant à créer un bitmap à partir de la vue sortante et à le placer sous la vue à laquelle le nouveau fragment est ajouté. Cela permet d'obtenir l'effet recherché, mais j'ai à nouveau des problèmes de mémoire car le bitmap est trop grand. Je suis en train d'étudier quelques autres approches, dont le ViewPager avec FragmentStatePagerAdapter et un FrameLayout par fragment. Je reviendrai plus tard.

1 votes

@domji84 <?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 votes

Gracias. overridePendingTransition(R.anim.my_animation, 0) est la ligne que je cherchais.

8voto

Matt Accola Points 1163

J'ai trouvé une solution qui fonctionne pour moi. J'ai fini par utiliser un ViewPager avec un FragmentStatePagerAdapter. Le ViewPager fournit le comportement de balayage et le FragmentStatePagerAdapter échange les fragments. La dernière astuce pour obtenir l'effet d'une page visible "sous" la page entrante consiste à utiliser un PageTransformer. Le PageTransformer remplace la transition par défaut entre les pages du ViewPager. Voici un exemple de PageTransformer qui permet d'obtenir cet effet grâce à une traduction et à une légère mise à l'échelle de 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);
            }
        }
    }

}

0 votes

Excellente solution, mais malheureusement, elle ne convient pas si l'on veut modifier la pile, surtout dans le sens de la diminution - cela conduit à un éblouissement (redessiner l'élément entier).

6voto

Merk Points 550

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

Faire un 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 ce que vous 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();

0 votes

Je vais essayer, mais j'avais pensé à faire une animation "stay_put" correcte et tout ce que cela faisait était de couvrir l'animation de glissement du nouveau fragment avec l'ancien fragment restant au premier plan. Mais je vois que vous avez décalé le début de la no_animation, donc peut-être que cela fait l'affaire. Je vous ferai savoir ce que je pense de votre solution.

3 votes

Bien pensé mais cela ne fonctionne pas car la transaction supprime instantanément le fragment et je peux voir l'écran blanc sous le deuxième fragment pendant l'animation.

1 votes

Je ne comprends pas pourquoi vous avez pensé que c'était nécessaire. J'ai simplement utilisé 0 là où je ne voulais pas de transition et ça a très bien marché. C'est ce que vous semblez avoir fait ci-dessus.

2voto

Merk Points 550

J'ai trouvé une solution alternative (pas très testée) que je trouve plus élégante que les propositions faites 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();

Cet appel est effectué à partir d'une classe interne de la classe OutgoingFragment.

Un nouveau fragment est inséré, l'animation se termine, puis l'ancien fragment est retiré.

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

0 votes

Pour moi, c'est la meilleure solution à ce jour. J'avais l'habitude de faire la même chose mais en mettant le système en pause avec un Handler pendant l'animation et en supprimant ensuite le fragment précédent, mais cette solution est bien meilleure.

1 votes

D'où viennent les classes IncomingFragment et OutgoingFragment ? Sont-elles personnalisées ? Je ne comprends pas bien l'implémentation derrière tout ça. Peut-être que lorsqu'il a été écrit, il y avait une fonction telle que Fragment.setEnterAnimationListener(...) ... ou est-ce une fonction personnalisée pour ce fragment ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X