
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:
- Activez
setReorderingAllowed(true)
. Il permet de réorganiser un appel aux méthodes de cycle de vie et votre animation s'affichera correctement. - Appelez
addSharedElement()
et ajoutez les vues qui seront partagées entre les écrans - Ajouter un
android:transitionName
unique android:transitionName
sur chaque vue en transition - Ajoutez
sharedElementEnterTransition/sharedElementReturnTransition
dans le fragment de destination. En option: pour un meilleur effet, nous pouvons également définir enterTransition/exitTransition
. - 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:
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() }
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:
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()
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:
- Gardez à l'esprit la hiérarchie des fragments (n'oubliez pas le fragment parent)
- Dans le cas de RecyclerView, créez toujours des noms de transition de manière dynamique (source + destination)
- Désactivez toutes les transformations Glide
postPostponeTransition()
et startPostponedEnterTransition()
concernant votre logique.
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) ) }