UI yang cantik dan menarik itu penting. Oleh karena itu, untuk Android, ada sejumlah besar perpustakaan untuk tampilan elemen desain yang indah. Seringkali dalam aplikasi Anda perlu menunjukkan bidang dengan nomor atau semacam penghitung. Misalnya, penghitung untuk jumlah item daftar yang dipilih atau jumlah pengeluaran selama sebulan. Tentu saja, tugas seperti itu dapat dengan mudah diselesaikan dengan bantuan TextView
biasa, tetapi Anda dapat menyelesaikannya dengan elegan dan menambahkan animasi mengubah nomor:

Video demo tersedia di YouTube.
Artikel ini akan berbicara tentang bagaimana menerapkan semua ini.
Satu digit statis
Untuk masing-masing angka ada gambar vektor, misalnya, untuk 8 itu adalah 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>
Selain angka 0-9, gambar tanda minus ( viv_vd_pathmorph_digits_minus.xml
) dan gambar kosong ( viv_vd_pathmorph_digits_nth.xml
), yang akan menyimbolkan hilangnya angka nomor selama animasi, juga diperlukan.
File gambar XML hanya berbeda di android:pathData
. Semua atribut lainnya diatur untuk kenyamanan melalui sumber daya yang terpisah dan sama untuk semua gambar vektor.
Gambar untuk angka 0-9 diambil di sini .
Animasi Transisi
Gambar vektor yang dijelaskan adalah gambar statis. Untuk animasi, Anda perlu menambahkan gambar vektor animasi ( <animated-vector>
). Misalnya, untuk menghidupkan angka 2, tambahkan file res/drawable/viv_avd_pathmorph_digits_2_to_5.xml
ke angka 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>
Di sini, untuk kenyamanan, kami mengatur durasi animasi melalui sumber yang terpisah. Secara total, kami memiliki 12 gambar statis (0 - 9 + "minus" + "void"), masing-masing dapat dianimasikan pada gambar lainnya. Ternyata untuk kelengkapan, 12 * 11 = 132 file animasi diperlukan. Mereka hanya akan android:valueFrom
dalam atribut android:valueFrom
dan android:valueTo
, dan membuatnya secara manual bukanlah suatu pilihan. Karena itu, kami akan menulis generator sederhana:
Pembuat File Animasi 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()) } } } }
Semuanya bersama
Sekarang Anda perlu menghubungkan gambar vektor statis dan animasi transisi dalam satu file <animated-selector>
, yang, seperti <selector>
, menampilkan salah satu gambar tergantung pada kondisi saat ini. Sumber daya yang dapat res/drawable/viv_asl_pathmorph_digits.xml
ini ( res/drawable/viv_asl_pathmorph_digits.xml
) berisi deklarasi status gambar dan transisi di antara mereka.
Negara ditetapkan menggunakan <item>
dengan gambar dan atribut status (dalam kasus ini, app:viv_state_three
) yang mendefinisikan gambar ini. Setiap negara memiliki id
, yang digunakan untuk menentukan animasi transisi yang diinginkan:
<item android:id="@+id/three" android:drawable="@drawable/viv_vd_pathmorph_digits_three" app:viv_state_three="true" />
Atribut state diatur dalam file 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>
Animasi transisi antar negara diatur oleh <transition>
dengan indikasi <animated-vector>
, yang melambangkan transisi, serta id
awal dan akhir:
<transition android:drawable="@drawable/viv_avd_pathmorph_digits_6_to_2" android:fromId="@id/six" android:toId="@id/two" />
Isi res/drawable/viv_asl_pathmorph_digits.xml
hampir sama, dan generator juga digunakan untuk membuatnya. Sumber daya yang dapat digambar ini terdiri dari 12 negara bagian dan 132 transisi di antaranya.
Customview
Sekarang kita memiliki drawable
yang memungkinkan kita untuk menampilkan satu digit dan menganimasikan perubahannya, kita perlu membuat VectorIntegerView
yang akan berisi sejumlah beberapa digit dan mengendalikan animasi. RecyclerView
dipilih sebagai dasar, karena jumlah digit dalam angka adalah nilai variabel, dan RecyclerView
adalah cara terbaik di Android untuk menampilkan sejumlah variabel elemen (angka) dalam satu baris. Selain itu, RecyclerView
memungkinkan Anda untuk mengontrol animasi item melalui ItemAnimator
.
DigitAdapter dan DigitViewHolder
Anda harus mulai dengan membuat DigitViewHolder
mengandung satu digit. View
DigitViewHolder
akan terdiri dari satu ImageView
dengan android:src="@drawable/viv_asl_pathmorph_digits"
. Untuk menampilkan digit yang diinginkan di ImageView
, metode mImageView.setImageState(state, true);
digunakan mImageView.setImageState(state, true);
. Array state state
dibentuk berdasarkan digit yang ditampilkan menggunakan atribut status viv_DigitState
ditentukan di atas.
Tampilkan angka yang diinginkan di `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); }
Adaptor DigitAdapter
bertanggung jawab untuk membuat DigitViewHolder
dan untuk menampilkan digit yang diinginkan di DigitViewHolder
diinginkan.
Untuk animasi yang benar untuk mengubah satu nomor menjadi yang lain, DiffUtil
digunakan. Dengan itu, pangkat puluhan dianimasikan ke pangkat puluhan, ratusan hingga ratusan, puluhan juta hingga puluhan juta, dan seterusnya. Simbol minus selalu tetap dengan sendirinya dan hanya bisa muncul atau menghilang, berubah menjadi gambar kosong ( viv_vd_pathmorph_digits_nth.xml
).
Untuk melakukan ini, DiffUtil.Callback
dalam metode DiffUtil.Callback
mengembalikan true
hanya jika digit angka yang identik dibandingkan. Minus adalah kategori khusus, dan minus dari angka sebelumnya sama dengan minus dari nomor baru.
Metode areContentsTheSame
membandingkan karakter pada posisi tertentu di angka sebelumnya dan yang baru. Implementasinya sendiri dapat dilihat di DigitAdapter
.
DigitItemAnimator
Animasi perubahan angka, yaitu transformasi, tampilan, dan hilangnya angka, akan dikontrol oleh animator khusus untuk RecyclerView
- DigitItemAnimator
. Untuk menentukan durasi animasi, sumber integer
sama digunakan seperti pada <animated-vector>
dijelaskan di atas:
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; }
Sebagian besar DigitItemAnimator
menimpa metode aminasi. Animasi penampilan digit (metode animateAdd
) dilakukan sebagai transisi dari gambar kosong ke digit yang diinginkan atau tanda minus. Animasi penghilangan (metode animateRemove
) dilakukan sebagai transisi dari digit yang ditampilkan atau tanda minus ke gambar kosong.
Untuk melakukan animasi perubahan digit, informasi pada digit yang ditampilkan sebelumnya disimpan terlebih dahulu dengan recordPreLayoutInformation
metode recordPreLayoutInformation
. Kemudian, dalam metode animateChange
, transisi dari digit yang ditampilkan sebelumnya ke yang baru dilakukan.
RecyclerView.ItemAnimator
mensyaratkan bahwa metode animasi utama harus menggunakan metode yang melambangkan akhir animasi. Oleh karena itu, di setiap metode animateAdd
, animateRemove
dan animateChange
, ada panggilan ke metode yang sesuai dengan penundaan yang sama dengan durasi animasi. Misalnya, metode animateAdd
memanggil metode dispatchAddFinished
dengan penundaan yang sama dengan @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
Sebelum membuat CustomView, Anda perlu mendefinisikan atribut xml-nya. Untuk melakukan ini, tambahkan <declare-styleable>
ke file res/values/attrs.xml
:
<declare-styleable name="VectorIntegerView"> <attr name="viv_vector_integer" format="integer" /> <attr name="viv_digit_color" format="color" /> </declare-styleable>
VectorIntegerView
dibuat akan memiliki 2 atribut xml untuk penyesuaian:
viv_vector_integer
angka yang ditampilkan saat membuat tampilan (0 secara default).viv_digit_color
warna angka (hitam secara default).
Parameter VectorIntegerView
lainnya dapat diubah dengan menimpa sumber daya dalam aplikasi (seperti yang dilakukan dalam aplikasi demo ):
@integer/viv_animation_duration
menentukan durasi animasi (400ms secara default).@dimen/viv_digit_size
menentukan ukuran satu digit ( 24dp
secara default).@dimen/viv_digit_translateX
berlaku untuk semua gambar vektor digit untuk menyelaraskannya secara horizontal.@dimen/viv_digit_translateY
diterapkan ke semua gambar vektor digit untuk menyelaraskannya secara vertikal.@dimen/viv_digit_strokewidth
berlaku untuk semua gambar vektor digit.@dimen/viv_digit_margin_horizontal
berlaku untuk semua digit tampilan ( DigitViewHolder
) ( -3dp
secara default). Ini diperlukan untuk membuat jarak antar angka lebih kecil, karena gambar vektor dari angka tersebut berbentuk persegi.
Sumber daya yang diganti akan diterapkan ke semua VectorIntegerView
dalam aplikasi.
Semua parameter ini ditetapkan melalui sumber daya, karena mengubah ukuran VectorDrawable
atau durasi animasi AnimatedVectorDrawable
melalui kode tidak mungkin.
Menambahkan VectorIntegerView
ke markup XML terlihat seperti ini:
<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>
Selanjutnya, Anda dapat mengubah nomor yang ditampilkan dalam kode dengan mengirimkan BigInteger
:
final VectorIntegerView vectorIntegerView = findViewById(R.id.vectorIntegerView); vectorIntegerView.setInteger( vectorIntegerView.getInteger().add(BigInteger.ONE), true );
Demi kenyamanan, ada metode untuk mentransmisikan sejumlah tipe long
:
vectorIntegerView.setInteger(1918L, false);
Jika false
dilewatkan sebagai animated
, maka metode notifyDataSetChanged
akan dipanggil untuk notifyDataSetChanged
, dan nomor baru akan ditampilkan tanpa animasi.
Saat Anda membuat kembali VectorIntegerView
ditampilkan disimpan menggunakan metode onSaveInstanceState
dan onRestoreInstanceState
.
Kode sumber
Kode sumber tersedia di github (direktori perpustakaan). Ada juga aplikasi demo menggunakan VectorIntegerView
(direktori aplikasi).
Ada juga apk demo ( minSdkVersion 21
).