أرقام متحركة على Android

واجهة مستخدم جميلة وجذابة مهمة. لذلك ، بالنسبة إلى 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), /* animated = */ true ); 

من أجل الراحة ، هناك طريقة لإرسال عدد من النوع long :


 vectorIntegerView.setInteger(1918L, false); 

إذا false تمرير false notifyDataSetChanged animated ، فسيتم notifyDataSetChanged الأسلوب notifyDataSetChanged ، وسيتم عرض الرقم الجديد بدون رسوم متحركة.


عند إعادة إنشاء VectorIntegerView يتم حفظ الرقم المعروض باستخدام onRestoreInstanceState و onRestoreInstanceState .


كود المصدر


كود المصدر متاح على جيثب (دليل المكتبة). هناك أيضًا تطبيق تجريبي يستخدم VectorIntegerView (دليل التطبيق).


يوجد أيضًا apk تجريبي ( minSdkVersion 21 ).

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


All Articles