5 votes

Transition d'un élément partagé dans Jetpack Navigation de RecyclerView à Detail Fragment

J'essaie de faire une transition avec une simple animation d'un élément partagé entre des fragments. Dans le premier fragment, j'ai des éléments dans RecyclerView, dans le second - exactement le même élément (défini dans une disposition xml séparée, dans la liste les éléments sont également de ce type) en haut et des détails dans le reste de la vue. Je donne différents transitionNames pour tous les éléments dans bindViewHolder et dans onCreateView du fragment cible, je les lis et les attribue à l'élément dont je veux faire la transition. Quoi qu'il en soit, l'animation ne se produit pas et je n'ai pas d'autres idées. Ci-dessous, je mets mes extraits de code des fragments source et cible et de l'adaptateur de liste :

ListAdapter :

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = list[position]
    ViewCompat.setTransitionName(holder.view, item.id)
    holder.view.setOnClickListener {
        listener?.onItemSelected(item, holder.view)
    }
    ...
}

interface interactionListener {
    fun onItemSelected(item: ItemData, view: View)
}

ListFragment (Source) :

override fun onItemSelected(item: ItemData, view: View) {
    val action = ListFragmentDirections.itemDetailAction(item.id)
    val extras = FragmentNavigatorExtras(view to view.transitionName)
    val data = Bundle()
    data.putString("itemId", item.id)
    findNavController().navigate(action.actionId, data, null, extras)
}

SourceFragmentLayout :

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/pullToRefresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/item_overview_row" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

DetailFragment (Cible) :

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val rootView = inflater.inflate(R.layout.fragment_detail, container, false)

    val itemId = ItemDetailFragmentArgs.fromBundle(arguments).itemId

    (rootView.findViewById(R.id.includeDetails) as View).transitionName = itemId

    sharedElementEnterTransition = ChangeBounds().apply {
        duration = 750
    }
    sharedElementReturnTransition= ChangeBounds().apply {
        duration = 750
    }
    return rootView
}

DetailFragmentLayout :

<include
android:id="@+id/includeDetails"
layout="@layout/item_overview_row"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

ItemOverviewRowLayout (celui-ci est inclus en tant qu'élément dans le recyclerView et dans le fragment cible en tant qu'en-tête) :

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical" >

J'ai également créé une autre application utilisant la navigation Jetpack, des éléments partagés et des éléments décrits par le même layout.xml et cela fonctionne puisque je ne fais pas de transition entre le recyclerView et le fragment cible. Peut-être que je me trompe en fixant le nom de la transition à la vue trouvée dans le fragment cible ? Je ne sais pas comment faire autrement, car les ID des layouts inclus dans la cible doivent être uniques à cause des éléments du recyclerView.

8voto

FenderBender Points 111

Ok, j'ai trouvé ce que ça donne d'avoir une animation d'entrée avec un élément partagé : Dans DetailFragment (Target) vous devez exécuter postponeEnterTransition() on start onViewCreated (mon code de onCreateView peut être déplacé vers onViewCreated). Maintenant vous avez le temps de signer l'élément de vue cible avec transitionName. Après avoir terminé le chargement des données et de la vue, vous DEVEZ exécuter startPostponedEnterTransition() . Si vous ne le faites pas, l'interface utilisateur se figera et vous ne pourrez pas effectuer d'opérations fastidieuses entre postponeEnterTransition et startPostponedEnterTransition.

Quoi qu'il en soit, le problème se situe au niveau de la transition vers le retour. Car bien sûr, c'est la même situation - vous devez recharger le recyclerView avant de relâcher l'animation. Bien sûr, vous pouvez aussi utiliser postponeEnterTransition (même s'il s'agit d'une transition de retour). Dans mon cas, j'ai une liste enveloppée par LiveData. Dans le fragment source, l'observateur du cycle de vie vérifie les données. Il y a un autre problème : comment déterminer si les données sont chargées. Théoriquement, avec recyclerView, vous pouvez utiliser une fonction en ligne utile :

inline fun <T : View> T.afterMeasure(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
    override fun onGlobalLayout() {
        if (measuredWidth > 0 && measuredHeight > 0) {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            f()
        }
    }
})

}

...et dans le code où vous appliquez votre gestionnaire de mise en page et votre adaptateur, vous pouvez l'utiliser comme ceci :

recyclerView.afterMeasure { startPostponedEnterTransition() }

il devrait faire le travail en déterminant le moment où l'animation de retour devrait commencer (vous devez vous assurer que les noms de transition sont corrects dans les éléments recyclerView afin que la transition puisse avoir un élément de vue cible).

0voto

Sup.Ia Points 69

D'après la réponse, l'utilisation de ViewTreeObserver consomme beaucoup de ressources et a également beaucoup de processus à exécuter, c'est pourquoi je vous suggère d'utiliser ViewTreeObserver. doOnPreDraw au lieu d'attendre la mesure de la fenêtre de recyclage. Le code mis en œuvre ressemble à ce qui suit.

recyclerView.doOnPreDraw {
    startPostponedEnterTransition()
}

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