Bonjour les bons programmeurs de stack overflow ! J'ai passé une bonne semaine avec ce problème et je suis maintenant très désespérée pour une solution.
Le scénario
J'utilise Android.app.Fragment, à ne pas confondre avec les fragments de support.
J'ai 6 fragments d'enfants nommés :
FragmentOne
FragmentTwo
FragmentThree
FragmentA
FragmentB
FragmentC
J'ai 2 fragments de parents nommés :
FragmentNumeric
FragmentAlpha
J'ai une activité nommée :
MainActivity
Ils se comportent de la manière suivante :
- Les fragments enfants sont des fragments qui ne montrent qu'une vue, ils ne montrent ni ne contiennent de fragments.
- Les fragments parents remplissent leur vue entière avec un seul fragment enfant. Ils sont capables de remplacer le fragment enfant par d'autres fragments enfants.
- L'activité remplit la majeure partie de sa vue avec un fragment parent. Elle peut le remplacer par d'autres fragments parents. Ainsi, à tout moment, l'écran n'affiche qu'un seul fragment enfant.
Comme vous l'avez probablement deviné,
FragmentNumeric
montre les fragments d'enfants FragmentOne
, FragmentTwo
y FragmentThree
.
FragmentAlpha
montre les fragments d'enfants FragmentA
, FragmentB
et FragmentC
.
Le problème
J'essaie d'animer des fragments de parents et d'enfants. La transition des fragments enfants se fait en douceur et comme prévu. Cependant, lorsque je fais la transition vers un nouveau fragment parent, l'aspect est terrible. Le fragment enfant semble exécuter une transition indépendante de son fragment parent. Et le fragment enfant semble être retiré du fragment parent également. Un gif de ce phénomène peut être visualisé ici https://imgur.com/kOAotvk . Remarquez ce qui se passe lorsque je clique sur Show Alpha.
Les questions et réponses les plus proches que j'ai pu trouver sont ici : Les fragments imbriqués disparaissent pendant l'animation de transition mais toutes les réponses sont des bidouillages insatisfaisants.
Fichiers XML de l'animateur
J'ai les effets d'animation suivants (la 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_rien.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
Les choses à considérer : Le premier fragment parent, FragmentNumeric, n'a pas d'effets d'entrée, il est donc toujours prêt avec l'activité et n'a pas d'effets de sortie car rien ne sort. J'utilise également 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()
}
}
}
FragmentNumérique
Fait la même chose que l'activité en termes d'affichage rapide de son premier fragment 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
Le FragmentAlpha suit le même schéma que le FragmentNumeric, en remplaçant les Fragments Un, Deux et Trois par les Fragments A, B et C respectivement.
Les fragments enfants affichent simplement la vue XML suivante et définissent dynamiquement son texte et son écouteur de clic de bouton pour appeler une fonction du fragment ou de l'activité parent.
vue_enfant_exemple.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 dagger et quelques contrats, j'ai fait en sorte que les fragments enfants fassent un appel à leurs fragments parents et accueillent des activités en faisant quelque chose comme ce qui suit :
FragmentOne définit l'écouteur de clic de bouton à faire :
(parentFragment as FragmentNumeric).showFragmentTwo()
FragmentTwo définit l'écouteur de clic de bouton à faire :
(parentFragment as FragmentNumeric).showFragmentThree()
FragmentThree est différent, il définira l'écouteur de clic à faire :
(activity as MainActivity).showFragmentAlpha()
Quelqu'un a-t-il une solution pour ce problème ?
Mise à jour 1
J'ai ajouté un projet d'exemple comme demandé : https://github.com/zafrani/NestedFragmentTransitions
Une différence entre ce fragment et celui de ma vidéo originale est que le fragment parent n'utilise plus une vue avec la propriété xFraction. Il semble donc que l'animation d'entrée n'ait plus cet effet de chevauchement. Cependant, elle retire le fragment enfant du parent et les anime côte à côte. Une fois l'animation terminée, le Fragment Trois est remplacé par le Fragment A instantanément.
Mise à jour 2
Les deux vues de fragment parent et enfant utilisent la propriété xFraction. La clé est de supprimer l'animation de l'enfant lorsque le parent s'anime.