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:

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), 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
).