واجهة مستخدم جميلة وجذابة مهمة. لذلك ، بالنسبة إلى Android ، هناك عدد كبير من المكتبات لعرض جميل لعناصر التصميم. غالبًا ما تحتاج في التطبيق إلى إظهار حقل به رقم أو نوع من العداد. على سبيل المثال ، عداد لعدد عناصر القائمة المحددة أو مقدار النفقات لشهر. بالطبع ، يمكن حل هذه المهمة بسهولة بمساعدة TextView
عادية ، ولكن يمكنك حلها بأناقة وإضافة رسوم متحركة لتغيير الرقم:

تتوفر مقاطع الفيديو التجريبية على موقع يوتيوب.
ستتحدث المقالة عن كيفية تنفيذ كل هذا.
رقم واحد ثابت
لكل صورة هناك صورة متجهة ، على سبيل المثال ، بالنسبة لـ 8 هي 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>
بالإضافة إلى الأرقام من 0 إلى 9 ، viv_vd_pathmorph_digits_minus.xml
أيضًا وجود صور علامة الطرح ( viv_vd_pathmorph_digits_minus.xml
) وصورة فارغة ( viv_vd_pathmorph_digits_nth.xml
) ، والتي ترمز إلى اختفاء الرقم أثناء الرسوم المتحركة.
تختلف ملفات صور XML فقط في android:pathData
. يتم تعيين جميع السمات الأخرى للراحة من خلال موارد منفصلة وهي نفسها لجميع الصور المتجهة.
تم التقاط صور للأرقام من 0 إلى 9 هنا .
الرسوم المتحركة الانتقالية
الصور المتجهة الموصوفة هي صور ثابتة. بالنسبة للرسوم المتحركة ، تحتاج إلى إضافة صور متجهة متحركة ( <animated-vector>
). على سبيل المثال ، لتحريك الرقم 2 ، أضف ملف res/drawable/viv_avd_pathmorph_digits_2_to_5.xml
إلى الرقم 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>
هنا ، من أجل الراحة ، قمنا بتعيين مدة الرسوم المتحركة من خلال مورد منفصل. في المجموع ، لدينا 12 صورة ثابتة (0 - 9 + "ناقص" + "فراغ") ، يمكن تحريك كل منها في أي من الصور الأخرى. اتضح أنه للاكتمال ، مطلوب 12 * 11 = 132 ملفات الرسوم المتحركة. android:valueFrom
فقط في سمات android:valueFrom
و android:valueTo
، ولا يعد إنشائها يدويًا خيارًا. لذلك ، سنكتب مولدًا بسيطًا:
مولد ملف الرسوم المتحركة 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()) } } } }
معًا
تحتاج الآن إلى ربط الصور المتجهة الثابتة والرسوم المتحركة الانتقالية في ملف <animated-selector>
، والذي يعرض ، مثل ملف <selector>
عادي ، إحدى الصور اعتمادًا على الحالة الحالية. يحتوي هذا المورد res/drawable/viv_asl_pathmorph_digits.xml
( res/drawable/viv_asl_pathmorph_digits.xml
) على إعلانات res/drawable/viv_asl_pathmorph_digits.xml
الصورة والانتقالات بينها.
يتم تعيين الولايات باستخدام علامات <item>
مع صورة app:viv_state_three
حالة (في هذه الحالة ، app:viv_state_three
) التي تحدد هذه الصورة. لكل ولاية id
، يتم استخدامه لتحديد حركة الانتقال المطلوبة:
<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>
يتم تعيين الرسوم المتحركة للانتقالات بين الحالات بواسطة علامات <transition>
مع الإشارة إلى <animated-vector>
، الذي يرمز إلى الانتقال ، بالإضافة إلى id
الأولية والنهائية:
<transition android:drawable="@drawable/viv_avd_pathmorph_digits_6_to_2" android:fromId="@id/six" android:toId="@id/two" />
محتويات res/drawable/viv_asl_pathmorph_digits.xml
حد res/drawable/viv_asl_pathmorph_digits.xml
، كما تم استخدام مولد لإنشائه. يتكون هذا المورد القابل للرسم من 12 حالة و 132 انتقال بينهما.
Customview
الآن بعد أن أصبح لدينا رسمًا يسمح لنا بعرض رقم واحد وتحريك تغييره ، نحتاج إلى إنشاء VectorIntegerView
الذي سيحتوي على عدد من الأرقام المتعددة والتحكم في الرسوم المتحركة. تم اختيار RecyclerView
كأساس ، لأن عدد الأرقام في الرقم هو قيمة متغيرة ، و RecyclerView
هي أفضل طريقة في Android لعرض عدد متغير من العناصر (الأرقام) على التوالي. بالإضافة إلى ذلك ، يتيح لك RecyclerView
التحكم في الرسوم المتحركة للعنصر من خلال ItemAnimator
.
DigitAdapter و DigitViewHolder
تحتاج إلى البدء عن طريق إنشاء DigitViewHolder
يحتوي على رقم واحد. ستتألف View
DigitViewHolder
من ImageView
واحد مع android:src="@drawable/viv_asl_pathmorph_digits"
. لعرض الرقم المطلوب في ImageView
، يتم استخدام طريقة mImageView.setImageState(state, true);
. يتم تكوين مصفوفة حالة state
استنادًا إلى الرقم المعروض باستخدام سمات حالة viv_DigitState
المحددة أعلاه.
اعرض الرقم المطلوب في `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); }
يعتبر المحول DigitAdapter
مسؤولاً عن إنشاء DigitViewHolder
وعرض الرقم المطلوب في DigitViewHolder
المطلوب.
للحصول على الرسوم المتحركة الصحيحة لتحويل رقم واحد إلى آخر ، DiffUtil
استخدام DiffUtil
. مع ذلك ، يتم تحريك رتبة العشرات إلى مرتبة العشرات ، من مئات إلى مئات ، وعشرات الملايين إلى عشرات الملايين ، وهلم جرا. يظل رمز الطرح دائمًا بمفرده ولا يمكن أن يظهر أو يختفي إلا ويتحول إلى صورة فارغة ( viv_vd_pathmorph_digits_nth.xml
).
للقيام بذلك ، DiffUtil.Callback
في الأسلوب DiffUtil.Callback
true
فقط إذا تمت مقارنة أرقام متطابقة من الأرقام. الطرح هو فئة خاصة ، والناقص من الرقم السابق يساوي الطرح من الرقم الجديد.
يقارن أسلوب areContentsTheSame
الأحرف في مواضع معينة في الأرقام السابقة والجديدة. يمكن رؤية التنفيذ نفسه في DigitAdapter
.
DigitItemAnimator
سيتم التحكم في الرسوم المتحركة للتغيير في الرقم ، وهي تحويل الأرقام ومظهرها واختفاءها ، بواسطة متحرك خاص لـ RecyclerView
- DigitItemAnimator
. لتحديد مدة الرسوم المتحركة ، يتم استخدام نفس المصدر integer
كما هو الحال في <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; }
الجزء الأكبر من DigitItemAnimator
طرق DigitItemAnimator
. يتم تنفيذ الرسوم المتحركة لمظهر الرقم (طريقة animateAdd
) على شكل انتقال من صورة فارغة إلى الرقم أو علامة الطرح المطلوبة. يتم تنفيذ الرسوم المتحركة للاختفاء (طريقة animateRemove
) animateRemove
من الرقم المعروض أو علامة الطرح إلى صورة فارغة.
لأداء حركة تغيير الرقم ، يتم تخزين المعلومات عن الرقم المعروض السابق أولاً عن طريق تجاوز طريقة recordPreLayoutInformation
. بعد ذلك ، في طريقة animateChange
، يتم إجراء الانتقال من الرقم المعروض السابق إلى الرقم الجديد.
RecyclerView.ItemAnimator
يتطلب أن تستبدل أساليب الرسوم المتحركة الطرق التي ترمز إلى نهاية الرسوم المتحركة. لذلك ، في كل من animateAdd
و animateRemove
و animateChange
، هناك استدعاء للطريقة المقابلة مع تأخير يساوي مدة الرسوم المتحركة. على سبيل المثال ، animateAdd
أسلوب animateAdd
الأسلوب dispatchAddFinished
بتأخير يساوي @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
قبل إنشاء CustomView ، تحتاج إلى تحديد سمات xml الخاصة به. للقيام بذلك ، أضف <declare-styleable>
إلى ملف 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
تم إنشاؤه VectorIntegerView
2 xml للتخصيص:
viv_vector_integer
الرقم المعروض عند إنشاء العرض (0 بشكل افتراضي).- لون أرقام
viv_digit_color
(أسود افتراضيًا).
يمكن تغيير معلمات VectorIntegerView
الأخرى عن طريق تجاوز الموارد في التطبيق (كما هو الحال في التطبيق التجريبي ):
@integer/viv_animation_duration
يحدد مدة الرسوم المتحركة ( @integer/viv_animation_duration
بشكل افتراضي).@dimen/viv_digit_size
يحدد حجم رقم واحد ( 24dp
بشكل افتراضي).- ينطبق
@dimen/viv_digit_translateX
على جميع الصور المتجهة للأرقام @dimen/viv_digit_translateX
أفقيًا. - يتم تطبيق
@dimen/viv_digit_translateY
على جميع الصور المتجهة للأرقام @dimen/viv_digit_translateY
رأسيًا. - ينطبق
@dimen/viv_digit_strokewidth
على جميع الصور المتجهة للأرقام. - ينطبق
@dimen/viv_digit_margin_horizontal
على جميع أرقام العرض ( DigitViewHolder
) (افتراضيًا -3dp
). هذا ضروري لجعل المسافات بين الأرقام أصغر ، لأن الصور المتجهة للأرقام مربعة.
سيتم تطبيق موارد VectorIntegerView
كل VectorIntegerView
في التطبيق.
يتم تعيين جميع هذه المعلمات من خلال الموارد ، نظرًا لأن تغيير حجم VectorDrawable
أو مدة AnimatedVectorDrawable
الرسوم المتحركة من خلال التعليمات البرمجية مستحيل.
VectorIntegerView
إضافة VectorIntegerView
إلى ترميز XML كما يلي:
<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>
بعد ذلك ، يمكنك تغيير الرقم المعروض في الرمز بتمرير BigInteger
:
final VectorIntegerView vectorIntegerView = findViewById(R.id.vectorIntegerView); vectorIntegerView.setInteger( vectorIntegerView.getInteger().add(BigInteger.ONE), true );
من أجل الراحة ، هناك طريقة لإرسال عدد من النوع long
:
vectorIntegerView.setInteger(1918L, false);
إذا false
تمرير false
notifyDataSetChanged
animated
، فسيتم notifyDataSetChanged
الأسلوب notifyDataSetChanged
، وسيتم عرض الرقم الجديد بدون رسوم متحركة.
عند إعادة إنشاء VectorIntegerView
يتم حفظ الرقم المعروض باستخدام onRestoreInstanceState
و onRestoreInstanceState
.
كود المصدر
كود المصدر متاح على جيثب (دليل المكتبة). هناك أيضًا تطبيق تجريبي يستخدم VectorIntegerView
(دليل التطبيق).
يوجد أيضًا apk تجريبي ( minSdkVersion 21
).