Numéros animés sur Android

Une interface utilisateur belle et attrayante est importante. Par conséquent, pour Android, il existe un grand nombre de bibliothèques pour un bel affichage des éléments de conception. Souvent, dans l'application, vous devez afficher un champ avec un nombre ou une sorte de compteur. Par exemple, un compteur pour le nombre d'éléments de liste sélectionnés ou le montant des dépenses pour un mois. Bien sûr, une telle tâche peut être facilement résolue à l'aide d'une TextView régulière, mais vous pouvez la résoudre avec élégance et ajouter une animation de modification du nombre:


démo


Des vidéos de démonstration sont disponibles sur YouTube.


L'article expliquera comment mettre en œuvre tout cela.


Un chiffre statique


Pour chacun des nombres il y a une image vectorielle, par exemple, pour 8 c'est res/drawable/viv_vd_pathmorph_digits_eight.xml :


 <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="@dimen/viv_digit_size" android:height="@dimen/viv_digit_size" android:viewportHeight="1" android:viewportWidth="1"> <group android:translateX="@dimen/viv_digit_translateX" android:translateY="@dimen/viv_digit_translateY"> <path android:name="iconPath" android:pathData="@string/viv_path_eight" android:strokeColor="@color/viv_digit_color_default" android:strokeWidth="@dimen/viv_digit_strokewidth"/> </group> </vector> 

En plus des nombres 0-9, des images de signe moins ( viv_vd_pathmorph_digits_minus.xml ) et une image vierge ( viv_vd_pathmorph_digits_nth.xml ), qui symboliseront le chiffre disparaissant du nombre pendant l'animation, sont également requises.
Les fichiers d'image XML ne diffèrent que dans l' android:pathData . Tous les autres attributs sont définis pour plus de commodité via des ressources distinctes et sont les mêmes pour toutes les images vectorielles.
Les images pour les numéros 0-9 ont été prises ici .


Animation de transition


Les images vectorielles décrites sont des images statiques. Pour l'animation, vous devez ajouter des images vectorielles animées ( <animated-vector> ). Par exemple, pour animer le chiffre 2, ajoutez le fichier res/drawable/viv_avd_pathmorph_digits_2_to_5.xml au chiffre 5:


 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:drawable="@drawable/viv_vd_pathmorph_digits_zero"> <target android:name="iconPath"> <aapt:attr name="android:animation"> <objectAnimator android:duration="@integer/viv_animation_duration" android:propertyName="pathData" android:valueFrom="@string/viv_path_two" android:valueTo="@string/viv_path_five" android:valueType="pathType"/> </aapt:attr> </target> </animated-vector> 

Ici, pour plus de commodité, nous définissons la durée de l'animation via une ressource distincte. Au total, nous avons 12 images statiques (0 - 9 + "moins" + "vide"), chacune d'elles peut être animée dans n'importe laquelle des autres. Il s'avère que pour être complet, 12 * 11 = 132 fichiers d'animation sont nécessaires. Ils ne android:valueFrom que par les attributs android:valueFrom et android:valueTo , et les créer manuellement n'est pas une option. Par conséquent, nous allons écrire un générateur simple:


Générateur de fichiers d'animation
 import java.io.File import java.io.FileWriter fun main(args: Array<String>) { val names = arrayOf( "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "nth", "minus" ) fun getLetter(i: Int) = when (i) { in 0..9 -> i.toString() 10 -> "n" 11 -> "m" else -> null!! } val dirName = "viv_out" File(dirName).mkdir() for (from in 0..11) { for (to in 0..11) { if (from == to) continue FileWriter(File(dirName, "viv_avd_pathmorph_digits_${getLetter(from)}_to_${getLetter(to)}.xml")).use { it.write(""" <?xml version="1.0" encoding="utf-8"?> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:drawable="@drawable/viv_vd_pathmorph_digits_zero"> <target android:name="iconPath"> <aapt:attr name="android:animation"> <objectAnimator android:duration="@integer/viv_animation_duration" android:propertyName="pathData" android:valueFrom="@string/viv_path_${names[from]}" android:valueTo="@string/viv_path_${names[to]}" android:valueType="pathType"/> </aapt:attr> </target> </animated-vector> """.trimIndent()) } } } } 

Tous ensemble


Maintenant, vous devez connecter les images vectorielles statiques et les animations de transition dans un fichier <animated-selector> , qui, comme un <selector> normal, affiche l'une des images en fonction de l'état actuel. Cette ressource pouvant être dessinée ( res/drawable/viv_asl_pathmorph_digits.xml ) contient des déclarations d'états d'image et des transitions entre elles.


Les états sont définis à l'aide de balises <item> avec une image et un attribut d'état (dans ce cas, app:viv_state_three ) qui définit cette image. Chaque état a un id , qui est utilisé pour déterminer l'animation de transition souhaitée:


 <item android:id="@+id/three" android:drawable="@drawable/viv_vd_pathmorph_digits_three" app:viv_state_three="true" /> 

Les attributs d'état sont définis dans le res/values/attrs.xml :


 <resources> <declare-styleable name="viv_DigitState"> <attr name="viv_state_zero" format="boolean" /> <attr name="viv_state_one" format="boolean" /> <attr name="viv_state_two" format="boolean" /> <attr name="viv_state_three" format="boolean" /> <attr name="viv_state_four" format="boolean" /> <attr name="viv_state_five" format="boolean" /> <attr name="viv_state_six" format="boolean" /> <attr name="viv_state_seven" format="boolean" /> <attr name="viv_state_eight" format="boolean" /> <attr name="viv_state_nine" format="boolean" /> <attr name="viv_state_nth" format="boolean" /> <attr name="viv_state_minus" format="boolean" /> </declare-styleable> </resources> 

Les animations de transitions entre les états sont définies par <transition> balises <transition> avec une indication de <animated-vector> , qui symbolise la transition, ainsi que l' id initial et final:


 <transition android:drawable="@drawable/viv_avd_pathmorph_digits_6_to_2" android:fromId="@id/six" android:toId="@id/two" /> 

Le contenu de res/drawable/viv_asl_pathmorph_digits.xml peu res/drawable/viv_asl_pathmorph_digits.xml même, et un générateur a également été utilisé pour le créer. Cette ressource utilisable comprend 12 états et 132 transitions entre eux.


Vue personnalisée


Maintenant que nous avons un drawable qui nous permet d'afficher un chiffre et d'animer son changement, nous devons créer un VectorIntegerView qui contiendra un certain nombre de chiffres et contrôler les animations. RecyclerView été choisi comme base, car le nombre de chiffres dans le numéro est une valeur variable, et RecyclerView est le meilleur moyen dans Android pour afficher un nombre variable d'éléments (nombres) dans une rangée. De plus, RecyclerView vous permet de contrôler les animations des éléments via ItemAnimator .


DigitAdapter et DigitViewHolder


Vous devez commencer par créer un DigitViewHolder contenant un chiffre. View un tel DigitViewHolder consistera en une seule ImageView avec android:src="@drawable/viv_asl_pathmorph_digits" . Pour afficher le chiffre souhaité dans ImageView , la mImageView.setImageState(state, true); est utilisée mImageView.setImageState(state, true); . Le tableau d'état d' state est formé sur la base du chiffre affiché à l'aide des viv_DigitState état viv_DigitState définis ci-dessus.


Affichez le chiffre souhaité dans le `ImageView`
 private static final int[] ATTRS = { R.attr.viv_state_zero, R.attr.viv_state_one, R.attr.viv_state_two, R.attr.viv_state_three, R.attr.viv_state_four, R.attr.viv_state_five, R.attr.viv_state_six, R.attr.viv_state_seven, R.attr.viv_state_eight, R.attr.viv_state_nine, R.attr.viv_state_nth, R.attr.viv_state_minus, }; void setDigit(@IntRange(from = 0, to = VectorIntegerView.MAX_DIGIT) int digit) { int[] state = new int[ATTRS.length]; for (int i = 0; i < ATTRS.length; i++) { if (i == digit) { state[i] = ATTRS[i]; } else { state[i] = -ATTRS[i]; } } mImageView.setImageState(state, true); } 

L'adaptateur DigitAdapter responsable de la création du DigitViewHolder et de l'affichage du chiffre souhaité dans le DigitViewHolder souhaité.


Pour l'animation correcte de la transformation d'un nombre en un autre, DiffUtil utilisé. Avec lui, le rang des dizaines s'anime au rang des dizaines, des centaines à des centaines, des dizaines de millions à des dizaines de millions, etc. Le symbole moins reste toujours seul et ne peut qu'apparaître ou disparaître, se transformant en une image vide ( viv_vd_pathmorph_digits_nth.xml ).


Pour ce faire, DiffUtil.Callback dans la méthode DiffUtil.Callback renvoie true uniquement si des chiffres identiques de nombres sont comparés. Le moins est une catégorie spéciale et le moins du numéro précédent est égal au moins du nouveau numéro.


La méthode areContentsTheSame compare les caractères à certaines positions des numéros précédents et nouveaux. L'implémentation elle-même peut être vue dans le DigitAdapter .


DigitItemAnimator


L'animation du changement de nombre, à savoir la transformation, l'apparition et la disparition des nombres, sera contrôlée par un animateur spécial pour RecyclerView - DigitItemAnimator . Pour déterminer la durée des animations, la même ressource integer est utilisée que dans le <animated-vector> décrit ci-dessus:


 private final int animationDuration; DigitItemAnimator(@NonNull Resources resources) { animationDuration = resources.getInteger(R.integer.viv_animation_duration); } @Override public long getMoveDuration() { return animationDuration; } @Override public long getAddDuration() { return animationDuration; } @Override public long getRemoveDuration() { return animationDuration; } @Override public long getChangeDuration() { return animationDuration; } 

La majeure partie de DigitItemAnimator les méthodes d'amination. L'animation de l'apparition d'un chiffre (méthode animateAdd ) est effectuée comme une transition d'une image vide au chiffre ou au signe moins souhaité. L'animation de disparition (méthode animateRemove ) est effectuée comme une transition du chiffre ou signe moins affiché à une image vierge.


Pour effectuer une animation de changement de chiffre, les informations sur le chiffre affiché précédent sont d'abord stockées en recordPreLayoutInformation méthode recordPreLayoutInformation . Ensuite, dans la méthode animateChange , la transition du chiffre affiché précédent au nouveau est effectuée.


RecyclerView.ItemAnimator requiert que les méthodes d'animation de substitution doivent appeler des méthodes qui symbolisent la fin de l'animation. Par conséquent, dans chacune des animateAdd , animateRemove et animateChange , il existe un appel à la méthode correspondante avec un délai égal à la durée de l'animation. Par exemple, la méthode animateAdd appelle la méthode dispatchAddFinished avec un délai égal à @integer/viv_animation_duration :


 @Override public boolean animateAdd(final RecyclerView.ViewHolder holder) { final DigitAdapter.DigitViewHolder digitViewHolder = (DigitAdapter.DigitViewHolder) holder; int a = digitViewHolder.d; digitViewHolder.setDigit(VectorIntegerView.DIGIT_NTH); digitViewHolder.setDigit(a); holder.itemView.postDelayed(new Runnable() { @Override public void run() { dispatchAddFinished(holder); } }, animationDuration); return false; } 

VectorIntegerView


Avant de créer un CustomView, vous devez définir ses attributs xml. Pour ce faire, ajoutez <declare-styleable> au res/values/attrs.xml :


 <declare-styleable name="VectorIntegerView"> <attr name="viv_vector_integer" format="integer" /> <attr name="viv_digit_color" format="color" /> </declare-styleable> 

Le VectorIntegerView créé aura 2 attributs xml pour la personnalisation:


  • viv_vector_integer nombre affiché lors de la création de la vue (0 par défaut).
  • viv_digit_color couleur des nombres (noir par défaut).

D'autres paramètres VectorIntegerView peuvent être modifiés en remplaçant les ressources de l'application (comme cela se fait dans l' application de démonstration ):


  • @integer/viv_animation_duration détermine la durée de l'animation (400 ms par défaut).
  • @dimen/viv_digit_size détermine la taille d'un chiffre ( 24dp par défaut).
  • @dimen/viv_digit_translateX s'applique à toutes les images vectorielles des chiffres pour les aligner horizontalement.
  • @dimen/viv_digit_translateY est appliqué à toutes les images vectorielles des chiffres pour les aligner verticalement.
  • @dimen/viv_digit_strokewidth s'applique à toutes les images vectorielles à chiffres.
  • @dimen/viv_digit_margin_horizontal s'applique à tous les chiffres de la vue ( DigitViewHolder ) ( -3dp par défaut). Cela est nécessaire pour réduire les espaces entre les nombres, car les images vectorielles des nombres sont carrées.

Les ressources remplacées seront appliquées à tous les VectorIntegerView de l'application.


Tous ces paramètres sont définis via des ressources, car le redimensionnement de VectorDrawable ou la durée de l' AnimatedVectorDrawable via le code est impossible.


L'ajout de VectorIntegerView au balisage XML ressemble à ceci:


 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.qwert2603.vector_integer_view.VectorIntegerView android:id="@+id/vectorIntegerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:viv_digit_color="#ff8000" app:viv_vector_integer="14" /> </FrameLayout> 

Par la suite, vous pouvez modifier le nombre affiché dans le code en passant BigInteger :


 final VectorIntegerView vectorIntegerView = findViewById(R.id.vectorIntegerView); vectorIntegerView.setInteger( vectorIntegerView.getInteger().add(BigInteger.ONE), /* animated = */ true ); 

Par souci de commodité, il existe une méthode pour transmettre un certain nombre de type long :


 vectorIntegerView.setInteger(1918L, false); 

Si false transmis comme animated , la méthode notifyDataSetChanged sera appelée pour l' notifyDataSetChanged et le nouveau numéro sera affiché sans animations.


Lorsque vous recréez VectorIntegerView nombre affiché est enregistré à l'aide des onRestoreInstanceState onSaveInstanceState et onRestoreInstanceState .


Code source


Le code source est disponible sur github (répertoire de la bibliothèque). Il existe également une application de démonstration utilisant VectorIntegerView (répertoire d'application).


Il existe également une minSdkVersion 21 démonstration ( minSdkVersion 21 ).

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


All Articles