Eine schöne und attraktive Benutzeroberfläche ist wichtig. Daher gibt es für Android eine große Anzahl von Bibliotheken für die schöne Anzeige von Designelementen. Oft müssen Sie in der Anwendung ein Feld mit einer Nummer oder einer Art Zähler anzeigen. Zum Beispiel ein Zähler für die Anzahl der ausgewählten Listenelemente oder die Höhe der Ausgaben für einen Monat. Natürlich kann eine solche Aufgabe leicht mit Hilfe einer normalen TextView
, aber Sie können sie elegant lösen und die Animation zum Ändern der Nummer hinzufügen:

Demo- Videos sind auf YouTube verfügbar.
Der Artikel wird darüber sprechen, wie all dies implementiert werden kann.
Eine statische Ziffer
Für jede der Zahlen gibt es ein Vektorbild, zum Beispiel für 8 ist es 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>
Zusätzlich zu den Zahlen 0-9 sind auch Minuszeichenbilder ( viv_vd_pathmorph_digits_minus.xml
) und ein leeres Bild ( viv_vd_pathmorph_digits_nth.xml
) viv_vd_pathmorph_digits_nth.xml
, das die verschwindende Ziffer der Zahl während der Animation symbolisiert.
XML-Bilddateien unterscheiden sich nur im android:pathData
. Alle anderen Attribute werden der Einfachheit halber über separate Ressourcen festgelegt und sind für alle Vektorbilder gleich.
Hier wurden Bilder für die Nummern 0-9 aufgenommen.
Übergangsanimation
Die beschriebenen Vektorbilder sind statische Bilder. Für die Animation müssen Sie animierte Vektorbilder hinzufügen ( <animated-vector>
). Um beispielsweise die Nummer 2 zu animieren, fügen res/drawable/viv_avd_pathmorph_digits_2_to_5.xml
der Nummer 5 die Datei res/drawable/viv_avd_pathmorph_digits_2_to_5.xml
:
<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>
Hier legen wir der Einfachheit halber die Dauer der Animation über eine separate Ressource fest. Insgesamt haben wir 12 statische Bilder (0 - 9 + "minus" + "void"), von denen jedes in einem der anderen animiert werden kann. Es stellt sich heraus, dass der Vollständigkeit halber 12 * 11 = 132 Animationsdateien erforderlich sind. Sie android:valueFrom
nur in den Attributen android:valueFrom
und android:valueTo
, und das manuelle Erstellen ist keine Option. Deshalb werden wir einen einfachen Generator schreiben:
Generator für Animationsdateien 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()) } } } }
Alle zusammen
Jetzt müssen Sie die statischen Vektorgrafiken und Übergangsanimationen in einer <animated-selector>
-Datei verbinden, die wie ein normaler <selector>
je nach aktuellem Status eines der Bilder anzeigt. Diese zeichnbare Ressource ( res/drawable/viv_asl_pathmorph_digits.xml
) enthält Deklarationen von res/drawable/viv_asl_pathmorph_digits.xml
und Übergängen zwischen ihnen.
Status werden mithilfe von <item>
-Tags mit einem Bild und einem app:viv_state_three
(in diesem Fall app:viv_state_three
) festgelegt, die dieses Bild definieren. Jeder Zustand hat eine id
, mit der die gewünschte Übergangsanimation bestimmt wird:
<item android:id="@+id/three" android:drawable="@drawable/viv_vd_pathmorph_digits_three" app:viv_state_three="true" />
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>
Animationen von Übergängen zwischen Zuständen werden durch <transition>
-Tags mit dem Hinweis <animated-vector>
, der den Übergang sowie die id
Anfangs- und Endzustands symbolisiert:
<transition android:drawable="@drawable/viv_avd_pathmorph_digits_6_to_2" android:fromId="@id/six" android:toId="@id/two" />
Der Inhalt von res/drawable/viv_asl_pathmorph_digits.xml
ziemlich gleich, und ein Generator wurde auch verwendet, um ihn zu erstellen. Diese zeichnbare Ressource besteht aus 12 Zuständen und 132 Übergängen zwischen ihnen.
Benutzerdefinierte Ansicht
drawable
wir nun ein drawable
, mit dem wir eine Ziffer anzeigen und ihre Änderung animieren können, müssen wir eine VectorIntegerView
erstellen, die mehrere Ziffern enthält und die Animationen steuert. RecyclerView
wurde als Basis ausgewählt, da die Anzahl der Ziffern in der Zahl ein variabler Wert ist und RecyclerView
in Android die beste Möglichkeit ist, eine variable Anzahl von Elementen (Zahlen) in einer Reihe anzuzeigen. Darüber hinaus können Sie mit RecyclerView
ItemAnimator
über ItemAnimator
.
DigitAdapter und DigitViewHolder
Sie müssen zunächst einen DigitViewHolder
mit einer Ziffer DigitViewHolder
. View
solchen DigitViewHolder
besteht aus einer einzelnen ImageView
mit android:src="@drawable/viv_asl_pathmorph_digits"
. Um die gewünschte Ziffer in ImageView
, wird die Methode mImageView.setImageState(state, true);
verwendet mImageView.setImageState(state, true);
. Das viv_DigitState
wird basierend auf der angezeigten Ziffer unter Verwendung der viv_DigitState
definierten viv_DigitState
.
Zeigen Sie die gewünschte Ziffer in der ImageView an 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); }
Der DigitAdapter
Adapter DigitAdapter
verantwortlich, den DigitViewHolder
und die gewünschte Ziffer im gewünschten DigitViewHolder
.
Für die korrekte Animation, eine Zahl in eine andere DiffUtil
, wird DiffUtil
verwendet. Damit wird der Rang der Zehner auf den Rang der Zehner, Hunderte bis Hunderte, Zehn Millionen bis Zehn Millionen usw. animiert. Das Minus-Symbol bleibt immer für sich und kann nur viv_vd_pathmorph_digits_nth.xml
oder viv_vd_pathmorph_digits_nth.xml
und sich in ein leeres Bild viv_vd_pathmorph_digits_nth.xml
( viv_vd_pathmorph_digits_nth.xml
).
Zu diesem DiffUtil.Callback
gibt DiffUtil.Callback
in der DiffUtil.Callback
Methode nur dann true
zurück true
wenn identische Ziffern von Zahlen verglichen werden. Das Minus ist eine spezielle Kategorie, und das Minus der vorherigen Zahl entspricht dem Minus der neuen Zahl.
Die Methode areContentsTheSame
vergleicht Zeichen an bestimmten Positionen in der vorherigen und der neuen Nummer. Die Implementierung selbst ist im DigitAdapter
zu sehen.
DigitItemAnimator
Die Animation der Änderung der Zahl, nämlich die Umwandlung, das Erscheinen und das Verschwinden von Zahlen, wird von einem speziellen Animator für RecyclerView
- DigitItemAnimator
. Um die Dauer von Animationen zu bestimmen, wird dieselbe integer
Ressource verwendet wie im oben beschriebenen <animated-vector>
:
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; }
Der Großteil von DigitItemAnimator
überschreibt die Aminierungsmethoden. Die Animation des Erscheinungsbilds einer Ziffer ( animateAdd
Methode) erfolgt als Übergang von einem leeren Bild zur gewünschten Ziffer oder zum Minuszeichen. Die Verschwinden-Animation ( animateRemove
Methode) wird als Übergang von der angezeigten Ziffer oder dem Minuszeichen zu einem leeren Bild ausgeführt.
Um eine Animation zum Ändern von Ziffern durchzuführen, werden zunächst Informationen zur vorherigen angezeigten Ziffer gespeichert, indem die Methode recordPreLayoutInformation
wird. Anschließend wird in der animateChange
Methode der Übergang von der zuvor angezeigten zur neuen Ziffer durchgeführt.
RecyclerView.ItemAnimator
erfordert, dass beim Überschreiben von Animationsmethoden Methoden aufgerufen werden müssen, die das Ende der Animation symbolisieren. Daher wird in jeder der Methoden animateAdd
, animateRemove
und animateChange
die entsprechende Methode mit einer Verzögerung aufgerufen, die der Dauer der Animation entspricht. Beispielsweise ruft die animateAdd
Methode die dispatchAddFinished
Methode mit einer Verzögerung auf, die @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
Bevor Sie eine benutzerdefinierte Ansicht erstellen, müssen Sie deren XML-Attribute definieren. <declare-styleable>
Sie dazu <declare-styleable>
zur res/values/attrs.xml
:
<declare-styleable name="VectorIntegerView"> <attr name="viv_vector_integer" format="integer" /> <attr name="viv_digit_color" format="color" /> </declare-styleable>
Die erstellte VectorIntegerView
über 2 XML-Attribute zur Anpassung:
viv_vector_integer
Nummer, die beim Erstellen der Ansicht angezeigt wird (standardmäßig 0).viv_digit_color
Farbe der Zahlen (standardmäßig schwarz).
Andere VectorIntegerView
Parameter können durch Überschreiben von Ressourcen in der Anwendung geändert werden (wie in der Demo-Anwendung ):
@integer/viv_animation_duration
bestimmt die Dauer der Animation (standardmäßig 400 ms).@dimen/viv_digit_size
bestimmt die Größe einer Ziffer (standardmäßig 24dp
).@dimen/viv_digit_translateX
gilt für alle Vektorbilder von Ziffern, um sie horizontal auszurichten.@dimen/viv_digit_translateY
wird auf alle Vektorbilder von Ziffern angewendet, um sie vertikal auszurichten.@dimen/viv_digit_strokewidth
gilt für alle @dimen/viv_digit_strokewidth
.@dimen/viv_digit_margin_horizontal
gilt für alle Ansichtsziffern ( DigitViewHolder
) (standardmäßig -3dp
). Dies ist notwendig, um die Zwischenräume zwischen den Zahlen zu verkleinern, da die Vektorbilder der Zahlen quadratisch sind.
Überschriebene Ressourcen werden auf alle VectorIntegerView
in der Anwendung angewendet.
Alle diese Parameter werden über Ressourcen festgelegt, da eine Größenänderung von VectorDrawable
oder die Dauer der AnimatedVectorDrawable
Animation über Code nicht möglich ist.
Das Hinzufügen von VectorIntegerView
zum XML-Markup sieht folgendermaßen aus:
<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>
Anschließend können Sie die angezeigte Nummer im Code ändern, indem Sie BigInteger
:
final VectorIntegerView vectorIntegerView = findViewById(R.id.vectorIntegerView); vectorIntegerView.setInteger( vectorIntegerView.getInteger().add(BigInteger.ONE), true );
Der Einfachheit halber gibt es eine Methode zum Übertragen einer Reihe von long
Typen:
vectorIntegerView.setInteger(1918L, false);
Wenn false
als animated
, wird die notifyDataSetChanged
Methode für den notifyDataSetChanged
aufgerufen und die neue Nummer wird ohne Animationen angezeigt.
Wenn Sie VectorIntegerView
neu VectorIntegerView
angezeigte Nummer mit den onRestoreInstanceState
onSaveInstanceState
und onRestoreInstanceState
.
Quellcode
Der Quellcode ist auf github (Bibliotheksverzeichnis) verfügbar. Es gibt auch eine Demo-Anwendung, die VectorIntegerView
(App-Verzeichnis) verwendet.
Es gibt auch eine Demo-Apk ( minSdkVersion 21
).