Erreurs cachées avec les transitions d'élément partagé



Il existe une bonne pratique pour rendre votre application belle et vivante, et il existe aujourd'hui de nombreux outils et moyens pour y parvenir. L'un d'eux est la transition des éléments partagés.

Dans cet article, je couvrirai quelques erreurs qui m'ont coûté beaucoup de temps; Je vais vous montrer comment les éviter si vous décidez d'implémenter ce type de transitions avec Fragments sur application.

Commencez


Avant de faire l'animation, j'ai lu des dizaines d'articles, mais la plupart portaient sur la transition d'activité. Cependant, je suis tombé sur un très bon à propos des fragments et je veux donner un petit récapitulatif sur la façon de créer une transition d'élément partagé.

Voici les étapes principales pour créer une animation:

  1. Activez setReorderingAllowed(true) . Il permet de réorganiser un appel aux méthodes de cycle de vie et votre animation s'affichera correctement.
  2. Appelez addSharedElement() et ajoutez les vues qui seront partagées entre les écrans
  3. Ajouter un android:transitionName unique android:transitionName sur chaque vue en transition
  4. Ajoutez sharedElementEnterTransition/sharedElementReturnTransition dans le fragment de destination. En option: pour un meilleur effet, nous pouvons également définir enterTransition/exitTransition .
  5. Ajoutez postponeEnterTransition/startPostponedEnterTransition pour définir le moment où les données sont chargées et l'interface utilisateur est prête à être dessinée

On dirait que cela suffit pour créer une animation et rendre vos concepteurs et utilisateurs heureux. MAIS il y a toujours des accidents. Jetons un coup d'œil à ce que nous aurons si nous prenons les mesures énumérées ci-dessus:

Animation cassée


Ce n'est pas ce que nous attendions. Voyons cela.

Erreur # 1. Transition statiqueNoms (une demi-journée perdue)


Comme je l'ai déjà dit, nos vues doivent avoir des noms de transition uniques - sinon le cadre de transition ne pourra pas reconnaître quelle vue participera à la transition et la fera sans eux. Alors où est le problème?

Le truc, c'est RecyclerView. Voilà.

S'il existe RecyclerView et qu'une vue animée fait partie de l'élément RecyclerView, vous devez créer et définir dynamiquement transitionName (oubliez XML). De plus, vous devez le faire dans les deux fragments même si le second n'a pas RecyclerView.

Le correctif est donc:

 val transitionNameImage = context.getString(R.string.transition_image, title) 

Vous avez peut-être remarqué que j'ai mis "title" comme argument pour obtenir un nom unique. Il est préférable d'utiliser le modèle de domaine au lieu, par exemple, de la position de l'élément à l'écran, car il n'est pas nécessaire de passer ces noms comme arguments à Bundle et de les analyser à partir du deuxième fragment.

Erreur # 2. Ne pas considérer le fragment parent (1,5 jour perdu)


Je sais, vous pourriez demander "Comment ça se fait?" Mais quand je lisais les articles sur l'animation partagée, personne n'a considéré un exemple dans la hiérarchie d'un fragment complexe. C'est pourquoi vous pourriez parfois ne pas y prêter attention.

Donc, mon premier fragment était un enfant du conteneur de fragments et il n'est pas étonnant que postponeEnterTransition()/startPostponedEnterTransition() n'ait eu aucun effet.

Le voici
 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) parentFragment?.also { parentFragment -> NewsTransitioner.setupFirstFragment(parentFragment) parentFragment.postponeEnterTransition() } // Initialization UI } 


Ce que vous devez faire ici est d'appeler ces méthodes à partir du fragment parent.

Erreur # 3. Glissement sous-estimé (2 jours perdus)


"Ok, j'ai appris à faire une transition partagée, quand appeler les méthodes requises concernant le cycle de vie et le chargement. Cette fois, ça va marcher! "

Et bien j'avais tort. C'est peut-être l'erreur la plus délicate à laquelle j'ai été confrontée. Jetons un coup d'œil à ce que nous avons jusqu'à présent:

Animation en cours


Vous remarquerez peut-être qu'il y a un problème étrange avec la transition d'entrée. Au démarrage, l'image a déjà changé de matrice, puis se déplace simplement vers la position finale.

Je ne veux pas décrire toute l'enquête ici. Pour faire court, j'ai eu la chance de tomber sur un bel article .

Où j'ai trouvé la solution. Le voici:
«Nous avons ce problème car Glide essaie d'optimiser le chargement des images. Par défaut, Glide redimensionne et recadre les images pour qu'elles correspondent à la vue cible. »

Afin de le corriger, j'ai ajouté, sans blague, une seule ligne de code comme celle-ci à la chaîne d'initialisation de Glide:

 Glide .with(target) .load(url) .apply( RequestOptions().dontTransform() // this line ) 

Donc, vous devez désactiver toutes les transformations de Glide sur les images si elles sont impliquées dans une transition partagée.

Erreur # 4. Gestion incorrecte de postPostponeTransition ()


Honnêtement, ce n'est pas exactement une erreur, mais je suppose que ce serait bien de le mentionner.

Lorsqu'il s'agit de gérer les postPostponeTransition() et startPostponedEnterTransition() , vous devez sélectionner le bon moment. Le moment est où l'interface utilisateur doit déjà être dessinée.

Il y a deux points principaux à connaître avant d'appeler les méthodes:

  • d'une part, lorsque les images avec transition sont chargées et prêtes
  • d'autre part, la hiérarchie des vues est mesurée et présentée

Pour les images, nous utilisons généralement Glide et il a un écouteur de fantaisie:

 RequestListener<Drawable> { override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady( resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } } 

Notez que nous appelons startPostponedEnterTransition() dans les deux cas: succès et erreur. C'est vital parce que si nous ne le faisons pas, votre application se bloquera.

Dans les cas où la transition se fait sans images, vous pouvez utiliser la méthode d'extension doOnPreDraw() de ktx sur la disposition racine et faire startPostponedEnterTransition() là-bas.

BONUS : En parlant de RecyclerView, il ne suffit pas d'ajouter simplement un écouteur pour les images. Nous devons conserver une position d'article de RecyclerView à partir de laquelle la transition commence. Lorsque l'utilisateur revient à l'écran précédent, nous devons comparer la position chargée de l'image avec la position retenue au niveau de l'auditeur et commencer la transition uniquement lorsqu'elles correspondent.

Tout mettre ensemble


Dans cet article, j'ai montré quelques problèmes auxquels vous pourriez être confronté lors de la mise en œuvre d'une transition partagée avec des fragments et les moyens de les gérer.

En bref, les voici:

  1. Gardez à l'esprit la hiérarchie des fragments (n'oubliez pas le fragment parent)
  2. Dans le cas de RecyclerView, créez toujours des noms de transition de manière dynamique (source + destination)
  3. Désactivez toutes les transformations Glide
  4. postPostponeTransition() et startPostponedEnterTransition() concernant votre logique.

Animation finale


Merci d'avoir lu et à la prochaine fois!

PS Si vous vous demandez comment animer des coins d'image, voici le code , ajoutez simplement au reste des transitions d'animation partagées.

Exemple
 fragment.sharedElementEnterTransition = TransitionSet().apply { addTransition(ChangeImageTransform()) addTransition(ChangeBounds()) addTransition(ChangeTransform()) addTransition(ChangeOutlineRadiusTransition(animationRadiusData.startRadius, animationRadiusData.endRadius) ) } 

Source: https://habr.com/ru/post/fr463459/


All Articles