17

Bonjour les bons programmeurs de débordement de pile! J'ai passé une bonne semaine avec ce problème et suis maintenant très désespérée pour une solution.Les fragments imbriqués transition incorrectement


Le scénario

J'utilise android.app.Fragment est à ne pas confondre avec les fragments de soutien.

J'ai 6 fragments d'enfant nommé:

  • FragmentOne
  • FragmentTwo
  • FragmentThree
  • FragmentA
  • FragmentB
  • FragmentC

J'ai deux fragments parents nommé:

  • FragmentNumeric
  • FragmentAlpha

J'ai 1 activité nommée:

  • MainActivity

Ils se comportent dans les domaines suivants:

  • fragments d'enfants sont des fragments qui montrent qu'une vue, ils ne montrent pas, ni contiennent des fragments.
  • Les fragments parents remplissent toute leur vue avec un seul fragment enfant. Ils sont capables de remplacer le fragment enfant par d'autres fragments enfants.
  • L'activité remplit la plus grande partie de sa vue avec un fragment parent. Il peut le remplacer par d'autres fragments parents. Ainsi, à tout moment, l'écran ne montre qu'un seul fragment enfant.

Comme vous l'avez sans doute deviné,

FragmentNumeric montre des fragments d'enfant FragmentOne, FragmentTwo et FragmentThree.

FragmentAlpha montre des fragments d'enfant FragmentA, FragmentB et FragmentC.


Le problème

Je suis en train de transition/animer des fragments parents et enfants. L'enfant fragmente la transition en douceur et comme prévu. Cependant, lorsque je passe à un nouveau fragment de parent, il semble terrible. Le fragment enfant ressemble à une transition indépendante de son fragment parent.Et le fragment enfant semble également être supprimé du fragment parent. Un gif de celui-ci peut être consulté ici https://imgur.com/kOAotvk. Notez ce qui se passe lorsque je clique sur Afficher l'Alpha.

La question la plus proche & les réponses que j'ai pu trouver sont ici: Nested fragments disappear during transition animation cependant toutes les réponses sont des hacks insatisfaisants.


XML Animator fichiers

je les effets suivants (animateur durée est longue à des fins de test):

fragment_enter.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="1.0" 
     android:valueTo="0" /> 
</set> 

fragment_exit.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="0" 
     android:valueTo="-1.0" /> 
</set> 

fragment_pop.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="0" 
     android:valueTo="1.0" /> 
</set> 

fragment_push.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="-1.0" 
     android:valueTo="0" /> 
</set> 

fragment_nothing.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" /> 
</set> 

MainActivity.kt

choses à considérer: Le premier fragment de parent, FragmentNumeric, ne pas entrer dans des effets de sorte que son toujours prêt à l'activité et n'a pas d'effets de sortie parce que rien est sortie. J'utilise aussi FragmentTransaction#add avec elle alors que FragmentAlpha utilise FragmentTransaction#replace

class MainActivity : AppCompatActivity { 

    fun showFragmentNumeric(){ 
     this.fragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_nothing, 
            R.animator.fragment_nothing, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .add(this.contentId, FragmentNumeric(), "FragmentNumeric") 
       .addToBackStack("FragmentNumeric") 
       .commit() 
} 

    fun showFragmentAlpha(){ 
     this.fragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_enter, 
            R.animator.fragment_exit, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .replace(this.contentId, FragmentAlpha(), "FragmentAlpha") 
       .addToBackStack("FragmentAlpha") 
       .commit() 
    } 

    override fun onCreate(savedInstanceState: Bundle?) { 
     super.onCreate(savedInstanceState) 
     if (savedInstanceState == null) { 
      showFragmentNumeric() 
     } 
    } 
} 

FragmentNumeric

Est-ce la même chose que l'activité en termes de montrer rapidement son premier fragment d'enfant.

class FragmentNumeric : Fragment { 

    fun showFragmentOne(){ 
      this.childFragmentManager.beginTransaction() 
        .setCustomAnimations(R.animator.fragment_nothing, 
             R.animator.fragment_nothing, 
             R.animator.fragment_push, 
             R.animator.fragment_pop) 
        .add(this.contentId, FragmentOne(), "FragmentOne") 
        .addToBackStack("FragmentOne") 
        .commit() 
    } 

    fun showFragmentTwo(){ 
     this.childFragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_enter, 
            R.animator.fragment_exit, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .replace(this.contentId, FragmentTwo(), "FragmentTwo") 
       .addToBackStack("FragmentTwo") 
       .commit() 
    } 


    fun showFragmentThree(){ 
     this.childFragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_enter, 
            R.animator.fragment_exit, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .replace(this.contentId, FragmentThree(), "FragmentThree") 
       .addToBackStack("FragmentThree") 
       .commit() 
    } 

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { 
     super.onViewCreated(view, savedInstanceState) 
     if (savedInstanceState == null) { 
      if (this.childFragmentManager.backStackEntryCount <= 1) { 
       showFragmentOne() 
      } 
     } 
    } 
} 

autres fragments

FragmentAlpha suit le même schéma que FragmentNumeric, en remplaçant Fragments un, deux et trois avec des fragments A, B et C respectivement. Les fragments enfants affichent simplement la vue XML suivante et définissent son écouteur de texte et de bouton de manière dynamique pour appeler une fonction du fragment parent ou de l'activité.

view_child_example.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:background="@color/background" 
    android:clickable="true" 
    android:focusable="true" 
    android:orientation="vertical"> 

    <TextView 
     android:id="@+id/view_child_example_header" 
     style="@style/Header" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" /> 


    <Button 
     android:id="@+id/view_child_example_button" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" /> 
</LinearLayout> 

En utilisant dague et quelques contrats que j'ai les fragments enfants rappel à leurs fragments parents et des activités d'hébergement en faisant quelque chose comme ci-dessous:

FragmentOne définit le bouton clic auditeur à faire:

(parentFragment as FragmentNumeric).showFragmentTwo() 

FragmentTwo définit le bouton clic auditeur à faire:

(parentFragment as FragmentNumeric).showFragmentThree() 

FragmentThree est différent, il sera mis à l'auditeur de clic à faire:

(activity as MainActivity).showFragmentAlpha() 

Quelqu'un at-il une solution à ce problème?


Update 1

J'ai ajouté un exemple de projet tel que demandé: https://github.com/zafrani/NestedFragmentTransitions

Une différence et que de celui de ma vidéo d'origine est le fragment parent n'utilise une vue avec la propriété xFraction. Il semble donc que l'animation d'entrée n'a plus cet effet de chevauchement. Cependant, il supprime toujours le fragment enfant du parent et les anime côte à côte. Une fois l'animation terminée, le fragment 3 est remplacé par le fragment A instantanément.

Update 2

Les deux vues fragment d'enfant et le parent utilisent la propriété xFraction. La clé consiste à supprimer l'animation childs lorsque le parent est en train d'animer.

+3

La question est déjà super claire, mais puisque vous semblez avoir un projet propre montrant le problème, il serait cool si vous pouviez le télécharger quelque part. – natario

+0

S'il vous plaît mettre à jour Gifs, semble qu'il manque. UPD. Désolé, mon mal, a trouvé l'image. – GensaGames

+0

@natario J'ai ajouté un lien github. – zafrani

Répondre

4

Je pense que j'ai trouvé un moyen de résoudre ce problème en utilisant Fragment # onCreateAnimator. Un gif de la transition peut être vu ici: https://imgur.com/94AvrW4.

J'ai fait un PR pour les tests, jusqu'à présent, il fonctionne comme je l'espère et survit aux changements de configuration et de soutenir le bouton de retour.Voici le lien https://github.com/zafrani/NestedFragmentTransitions/pull/1/files#diff-c120dd82b93c862b01c2548bdcafcb20R25

Le BaseFragment pour les deux parents et fragments enfants fait cela pour onCreateAnimator()

override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator { 
    if (isConfigChange) { 
     resetStates() 
     return nothingAnim() 
    } 

    if (parentFragment is ParentFragment) { 
     if ((parentFragment as BaseFragment).isPopping) { 
      return nothingAnim() 
     } 
    } 

    if (parentFragment != null && parentFragment.isRemoving) { 
     return nothingAnim() 
    } 

    if (enter) { 
     if (isPopping) { 
      resetStates() 
      return pushAnim() 
     } 
     if (isSuppressing) { 
      resetStates() 
      return nothingAnim() 
     } 
     return enterAnim() 
    } 

    if (isPopping) { 
     resetStates() 
     return popAnim() 
    } 

    if (isSuppressing) { 
     resetStates() 
     return nothingAnim() 
    } 

    return exitAnim() 
} 

Les booléens sont répartis dans différents scénarios qui sont plus faciles à voir dans le PR.

Les fonctions d'animation sont:

private fun enterAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_enter) 
    } 

    private fun exitAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_exit) 
    } 

    private fun pushAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_push) 
    } 

    private fun popAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_pop) 
    } 

    private fun nothingAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_nothing) 
    } 

Est-ce que laisser la question ouverte Incase quelqu'un trouve une meilleure façon.

0

Vous obtenez un résultat si câblé, car vous utilisez des animations de sortie pour votre fragment. Fondamentalement, nous avons chaque fois de tels problèmes avec l'animation Fragment, et finalement nous passons de l'animation Fragment source à l'utilisation propre, chaque fois que nous effectuons la transition.

1) Pour vérifier ce comportement, vous pouvez simplement supprimer l'animation de sortie de fragment, et tout ira bien. Dans la plupart des cas, il devrait être suffisant, parce que l'animation de sortie très spécifique et utilisé uniquement pour la gestion de fragment unique (pas en vous cas, avec Childs)

getFragmentManager().beginTransaction() 
       .setCustomAnimations(R.animator.enter_anim_frag1, 
            0, 
            R.animator.enter_anim_frag2, 
            0) 
       .replace(xxx, Xxx1, Xxx2) 
       .addToBackStack(null) 
       .commit() 

2) Une autre option, qui pourrait suite, ce Réfléchissons sur la structure d'application et essayez d'éviter de remplacer Fragment par une animation. Dans le cas où vous avez besoin de remplacer, n'utilisez pas d'animation, et vous pouvez ajouter n'importe quelle animation (y compris entrer et sortir), mais seulement avec l'ajout.