102 votes

Les fragments imbriqués disparaissent pendant l'animation de transition

Voici le scénario : L'activité contient un fragment A qui, à son tour, utilise getChildFragmentManager() pour ajouter des fragments A1 y A2 dans son onCreate comme ça :

getChildFragmentManager()
  .beginTransaction()
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

Jusqu'à présent, tout va bien, tout fonctionne comme prévu.

Nous exécutons ensuite la transaction suivante dans l'activité :

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .replace(R.id.fragmentHolder, new FragmentB())
  .addToBackStack(null)
  .commit()

Pendant la transition, le enter animations pour les fragments B fonctionne correctement mais les fragments A1 et A2 disparaissent entièrement . Lorsque nous inversons la transaction avec le bouton Back, ils s'initialisent correctement et s'affichent normalement lors de la popEnter l'animation.

Lors de mes brefs essais, le problème s'est aggravé : si j'ai défini les animations pour les fragments enfants (voir ci-dessous), la fonction exit l'animation fonctionne par intermittence lorsque nous ajoutons un fragment B

getChildFragmentManager()
  .beginTransaction()
  .setCustomAnimations(enter, exit)
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

L'effet que je veux obtenir est simple - je veux que les exit (ou devrait-on dire popExit ?) animation sur le fragment A (anim2) à exécuter, animant l'ensemble du conteneur, y compris ses enfants imbriqués.

Y a-t-il un moyen d'y parvenir ?

Editar : Veuillez trouver un cas de test ici

Edit2 : Merci à @StevenByle de m'avoir poussé à continuer d'essayer avec les animations statiques. Apparemment, vous pouvez définir des animations sur une base par opération (et non globale à l'ensemble de la transaction), ce qui signifie que les enfants peuvent avoir une animation statique indéfinie, tandis que leur parent peut avoir une animation différente et le tout peut être validé en une seule transaction. Voir la discussion ci-dessous et le projet de cas de test mis à jour .

0 votes

Qu'est-ce que R.id.fragmentHolder par rapport à A, A1, A2, etc ?

0 votes

FragmentHolder est un id dans la disposition de l'activité, fragment{One,Two}Holder sont dans la disposition du fragment A. Ces trois éléments sont distincts. Le fragment A a été initialement ajouté dans fragmentHolder (c'est-à-dire que le fragment B remplace le fragment A).

0 votes

J'ai créé un projet type ici : github.com/BurntBrunch/NestedFragmentsAnimationsTest Il y a aussi un apk inclus dans le dépôt. C'est un bug vraiment ennuyeux et je cherche un moyen de le contourner (en supposant que ce n'est pas dans mon code).

68voto

kcoppock Points 57219

Il semble qu'il y ait beaucoup de solutions différentes pour cela, mais en me basant sur la réponse de @Jayd16, je pense avoir trouvé une solution attrape-tout assez solide qui permet toujours de personnaliser les animations de transition sur les fragments enfants, et qui ne nécessite pas de faire un cache bitmap de la mise en page.

Ayez un BaseFragment qui étend la classe Fragment et faites en sorte que tous vos fragments étendent cette classe (pas seulement les fragments enfants).

Dans ce BaseFragment ajoutez ce qui suit :

// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    final Fragment parent = getParentFragment();

    // Apply the workaround only if this is a child fragment, and the parent
    // is being removed.
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}

private static long getNextAnimationDuration(Fragment fragment, long defValue) {
    try {
        // Attempt to get the resource ID of the next animation that
        // will be applied to the given fragment.
        Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
        nextAnimField.setAccessible(true);
        int nextAnimResource = nextAnimField.getInt(fragment);
        Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);

        // ...and if it can be loaded, return that animation's duration
        return (nextAnim == null) ? defValue : nextAnim.getDuration();
    } catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
        Log.w(TAG, "Unable to load next animation from parent.", ex);
        return defValue;
    }
}

Malheureusement, elle nécessite une réflexion. Toutefois, comme cette solution de contournement concerne la bibliothèque de support, vous ne courez pas le risque de voir l'implémentation sous-jacente changer à moins que vous ne mettiez à jour votre bibliothèque de support. Si vous construisez la bibliothèque de support à partir du code source, vous pouvez ajouter un accesseur pour l'ID de la ressource d'animation suivante dans le champ Fragment.java et supprimer le besoin de réflexion.

Cette solution élimine le besoin de "deviner" la durée de l'animation du parent (de sorte que l'animation "ne rien faire" aura la même durée que l'animation de sortie du parent), et vous permet de continuer à faire des animations personnalisées sur les fragments enfants (par exemple, si vous échangez des fragments enfants avec des animations différentes).

5 votes

C'est ma solution préférée dans ce fil. Elle ne nécessite pas de bitmap, ne requiert pas de code supplémentaire dans le fragment enfant et ne fait pas vraiment fuir les informations du parent vers le fragment enfant.

0 votes

Comment synchroniser l'animation de ce fragment imbriqué avec l'animation du fragment parent ? Les fragments imbriqués commencent à s'animer en premier et l'animation semble désordonnée...

0 votes

Vis la réflexion, l'id de la ressource d'animation suivante vous est fourni via int nextAnim paramètre ;) Et super.onCreateAnimation(transit, enter, nextAnim) renvoie une valeur nulle par défaut, de sorte que vous pouvez l'extraire dans une classe utilitaire et ne pas avoir besoin de créer une classe de type BaseFragment .

36voto

Luksprog Points 52767

Afin d'éviter que l'utilisateur ne voie les fragments imbriqués disparaître lorsque le fragment parent est supprimé/remplacé dans une transaction, vous pourriez "simuler" la présence de ces fragments en fournissant une image de ceux-ci, tels qu'ils apparaissent à l'écran. Cette image sera utilisée comme arrière-plan pour le conteneur des fragments imbriqués. Ainsi, même si les vues du fragment imbriqué disparaissent, l'image simulera leur présence. De plus, je ne pense pas que la perte de l'interactivité avec les vues du fragment imbriqué soit un problème, car je ne pense pas que vous voudriez que l'utilisateur agisse sur ces vues lorsqu'elles sont sur le point d'être supprimées (probablement comme une action de l'utilisateur également).

J'ai fait un petit exemple avec la mise en place de l'image de fond (quelque chose de basique).

1 votes

J'ai décidé de vous attribuer la prime, puisque c'est la solution que j'ai fini par utiliser. Merci beaucoup pour votre temps !

19 votes

Wow, c'est tellement dégoûtant. Ce que nous, développeurs Android, devons faire pour avoir un peu de finesse.

1 votes

J'ai un Viewpager du deuxième onglet, je remplace un autre fragment et lorsque j'appuie dessus, j'ai besoin d'afficher le deuxième onglet, il s'ouvre mais affiche une page blanche. J'ai essayé ce que vous avez suggéré dans le fil de discussion ci-dessus, mais le problème reste le même.

32voto

Jayd16 Points 28

J'ai réussi à trouver une solution assez propre. C'est la moins compliquée, et même si c'est techniquement la solution "dessiner un bitmap", au moins elle est abstraite grâce à la librairie fragment.

Assurez-vous que vos frags enfants surchargent une classe parent avec ceci :

private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
    dummyAnimation.setDuration(500);
}

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if(!enter && getParentFragment() != null){
        return dummyAnimation;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

Si nous avons une animation de sortie sur les frags enfants, ils seront animés au lieu de disparaître en clignant des yeux. Nous pouvons exploiter cela en ayant une animation qui dessine simplement les fragments enfants en alpha complet pendant une certaine durée. De cette façon, ils resteront visibles dans le fragment parent pendant qu'il s'anime, ce qui donne le comportement souhaité.

Le seul problème auquel je pense est de garder la trace de cette durée. Je pourrais peut-être la définir sur un nombre assez grand, mais j'ai peur que cela pose des problèmes de performance si l'animation est toujours dessinée quelque part.

0 votes

Cela aide, merci. La valeur de la durée du temps n'a pas d'importance

0 votes

La solution la plus propre à ce jour

16voto

Maurycy Points 1398

Je publie ma solution pour plus de clarté. La solution est assez simple. Si vous essayez d'imiter l'animation de la transaction du fragment parent, il suffit d'ajouter une animation personnalisée à la transaction du fragment enfant avec la même durée. Oh et assurez-vous que vous définissez l'animation personnalisée avant add().

getChildFragmentManager().beginTransaction()
        .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
        .add(R.id.container, nestedFragment)
        .commit();

Le xml pour R.anim.none (Le temps d'animation d'entrée/sortie de mes parents est de 250ms)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" />
</set>

0 votes

J'ai fait quelque chose de très similaire, mais j'ai utilisé "show" plutôt que "add" pour mettre à jour l'enfant. J'ai également ajouté "getChildFragmentManager().executePendingTransactions()", bien que je ne sois pas sûr que cela soit strictement nécessaire. Cette solution fonctionne très bien, cependant, et ne nécessite pas de "fournir une image" du fragment comme certains le suggèrent.

0 votes

C'était génial. Cependant, j'obtenais un retard lorsque je changeais de fragments enfants. Pour éviter cela, il suffit de définir le 2ème paramètre sans anim : fragmentTransaction.setCustomAnimations(R.anim.none, 0, R.anim.none, R.anim.none)

7voto

Steven Byle Points 4888

Je comprends que cela ne puisse pas résoudre complètement votre problème, mais peut-être que cela répondra aux besoins de quelqu'un d'autre, vous pouvez ajouter enter / exit y popEnter / popExit animations pour vos enfants Fragment qui n'ont pas pour effet de déplacer ou d'animer l'objet. Fragment s. Tant que les animations ont la même durée/le même décalage que leur parent Fragment ils sembleront se déplacer/animer avec l'animation du parent.

1 votes

J'ai attribué la prime à Luksprog puisque sa solution fonctionne universellement. J'ai essayé l'astuce des animations statiques (la durée n'a en fait pas d'importance - une fois le parent parti, les vues disparaissent évidemment) mais elles n'ont pas fonctionné dans tous les cas possibles (voir mes commentaires sous la question). De plus, cette approche entraîne une perte d'abstraction, puisque le parent d'un fragment avec des enfants doit savoir ce fait et prendre des mesures supplémentaires pour définir les animations des enfants. Dans tous les cas, merci beaucoup pour votre temps !

0 votes

Je suis d'accord, il s'agit plus d'une solution de contournement que d'une solution étanche, et elle peut être considérée comme légèrement fragile. Mais elle fonctionnera pour les cas simples.

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