إنّ رسم واجهة المستخدم هو جزء واحد فقط من إنشاء طريقة عرض مخصّصة. عليك أيضًا أن تجعل طريقة عرضك تستجيب لبيانات أدخلها المستخدم بطريقة تشبه إلى حد كبير الإجراء الذي تحاكيه في العالم الحقيقي.
اجعل العناصر في تطبيقك تتصرف مثل العناصر الحقيقية. على سبيل المثال، لا تسمح باختفاء الصور في تطبيقك وظهورها في مكان آخر، لأنّ العناصر في العالم الحقيقي لا تتصرف بهذه الطريقة. بدلاً من ذلك، يمكنك نقل صورك من مكان إلى آخر.
يدرك المستخدمون حتى السلوكيات أو الأحاسيس الدقيقة في الواجهة ويتفاعلون بشكل أفضل مع التفاصيل الدقيقة التي تحاكي العالم الحقيقي. على سبيل المثال، عندما يمرر المستخدمون الإصبع ثم يرفعونه بسرعة على أحد عناصر واجهة المستخدم، امنحهم إحساسًا بالقصور الذاتي في البداية يؤخّر الحركة. في نهاية الحركة، امنحهم إحساسًا بالزخم الذي يدفع العنصر إلى ما بعد عملية التحريك السريع.
توضّح هذه الصفحة كيفية استخدام ميزات إطار عمل Android لإضافة هذه السلوكيات الواقعية إلى طريقة العرض المخصّصة.
يمكنك العثور على معلومات إضافية ذات صلة في نظرة عامة على أحداث الإدخال ونظرة عامة على الصور المتحركة الخاصة بالسمات.
التعامل مع إيماءات الإدخال
مثل العديد من أُطر واجهة المستخدم الأخرى، يتيح نظام التشغيل Android نموذج أحداث الإدخال. تتحوّل إجراءات المستخدم إلى أحداث تؤدي إلى تشغيل عمليات ردّ الاتصال، ويمكنك تجاهل عمليات ردّ الاتصال لتخصيص طريقة استجابة تطبيقك للمستخدم. إنّ حدث الإدخال الأكثر شيوعًا في نظام Android هو اللمس، الذي يؤدي إلى تشغيل onTouchEvent(android.view.MotionEvent).
يمكنك إلغاء هذه الطريقة للتعامل مع الحدث، كما يلي:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
أحداث اللمس وحدها ليست مفيدة بشكل خاص. تحدّد واجهات المستخدم الحديثة التي تعمل باللمس التفاعلات من خلال الإيماءات، مثل النقر والسحب والدفع والتحريك السريع والتكبير والتصغير. لتحويل أحداث اللمس الأولية إلى إيماءات، يوفّر نظام التشغيل Android
GestureDetector.
أنشئ GestureDetector من خلال تمرير مثيل لفئة تنفّذ GestureDetector.OnGestureListener.
إذا كنت تريد معالجة بضع إيماءات فقط، يمكنك توسيع GestureDetector.SimpleOnGestureListener بدلاً من تنفيذ واجهة GestureDetector.OnGestureListener. على سبيل المثال، ينشئ هذا الرمز فئة توسّع GestureDetector.SimpleOnGestureListener وتتجاوز onDown(MotionEvent).
Kotlin
private val myListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { return true } } private val detector: GestureDetector = GestureDetector(context, myListener)
Java
class MyListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } } detector = new GestureDetector(getContext(), new MyListener());
سواء كنت تستخدم GestureDetector.SimpleOnGestureListener أم لا، عليك دائمًا تنفيذ طريقة onDown() تعرض القيمة true. هذا الإجراء ضروري لأنّ جميع الإيماءات تبدأ برسالة onDown(). إذا رجعت إلى false
من onDown()، كما يفعل GestureDetector.SimpleOnGestureListener، يفترض النظام أنّك تريد تجاهل بقية الإيماءة، ولا يتم استدعاء الطرق الأخرى في GestureDetector.OnGestureListener. لا تعرض القيمة
false من onDown() إلا إذا أردت تجاهل إيماءة كاملة.
بعد تنفيذ GestureDetector.OnGestureListener وإنشاء مثيل GestureDetector، يمكنك استخدام GestureDetector لتفسير أحداث اللمس التي تتلقّاها في onTouchEvent().
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return detector.onTouchEvent(event).let { result -> if (!result) { if (event.action == MotionEvent.ACTION_UP) { stopScrolling() true } else false } else true } }
Java
@Override public boolean onTouchEvent(MotionEvent event) { boolean result = detector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result; }
عند تمرير onTouchEvent() حدث لمس لا يمكن التعرّف عليه كجزء من إيماءة، يتم عرض false. يمكنك بعد ذلك تشغيل رمز مخصّص لرصد الإيماءات.
إنشاء حركة معقولة من الناحية الفيزيائية
الإيماءات هي طريقة فعّالة للتحكّم في الأجهزة التي تعمل باللمس، ولكن قد يكون من الصعب تذكّرها وفهمها ما لم تؤدِّ إلى نتائج منطقية.
على سبيل المثال، لنفترض أنّك تريد تنفيذ إيماءة التمرير السريع الأفقي التي تضبط العنصر المرسوم في طريقة العرض على الدوران حول محوره الرأسي. يكون هذا الإيماء منطقيًا إذا استجابت واجهة المستخدم بالتحرّك بسرعة في اتجاه السحب السريع، ثم تباطأت، كما لو أنّ المستخدم يدفع عجلة موازنة ويجعلها تدور.
تقدّم المستندات حول كيفية
تحريك إيماءة التمرير السريع شرحًا تفصيليًا حول كيفية تنفيذ سلوك التمرير السريع الخاص بك. لكن محاكاة شعور دولاب الموازنة ليس أمرًا بسيطًا. يتطلّب إعداد نموذج دولاب الموازنة بشكلٍ سليم معرفة الكثير من الفيزياء والرياضيات. لحسن الحظ، يوفّر نظام التشغيل Android فئات مساعدة لمحاكاة هذا السلوك والسلوكيات الأخرى. تشكّل الفئة
Scroller
الأساس للتعامل مع الإيماءات السريعة بنمط العجلة الدوّارة.
لبدء عملية التحريك السريع، استدعِ الدالة
fling()
مع سرعة البدء والحد الأدنى والأقصى لقيمتَي x وy
لعملية التحريك السريع. بالنسبة إلى قيمة السرعة، يمكنك استخدام القيمة التي تم احتسابها بواسطة GestureDetector.
Kotlin
fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { scroller.fling( currentX, currentY, (velocityX / SCALE).toInt(), (velocityY / SCALE).toInt(), minX, minY, maxX, maxY ) postInvalidate() return true }
Java
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); return true; }
يؤدي طلب fling() إلى إعداد نموذج الفيزياء لإيماءة النقر السريع. بعد ذلك، عدِّل Scroller من خلال استدعاء
Scroller.computeScrollOffset()
على فترات منتظمة. تعدّل computeScrollOffset() الحالة الداخلية للعنصر Scroller من خلال قراءة الوقت الحالي واستخدام نموذج الفيزياء لحساب موضعَي x وy في ذلك الوقت. استخدِم الدالتَين
getCurrX()
و
getCurrY()
لاسترداد هذه القيم.
تمرِّر معظم طرق العرض موضعَي x وy للعنصر Scroller مباشرةً إلى scrollTo().
يختلف هذا المثال قليلاً، إذ يستخدم موضع التمرير الحالي x لضبط زاوية دوران العرض.
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
تحسب الفئة Scroller مواضع التمرير نيابةً عنك، ولكنّها لا تطبّق هذه المواضع تلقائيًا على طريقة العرض. طبِّق الإحداثيات الجديدة
بشكل متكرّر بما يكفي لجعل الرسوم المتحركة للتمرير تبدو سلسة. هناك طريقتان لإجراء ذلك:
- فرض إعادة الرسم من خلال استدعاء
postInvalidate()بعد استدعاءfling()تتطلّب هذه الطريقة احتساب إزاحات التمرير فيonDraw()واستدعاءpostInvalidate()في كل مرة تتغيّر فيها إزاحة التمرير. - اضبط
ValueAnimatorلإنشاء حركة خلال مدة التحريك السريع وإضافة أداة معالجة لتنفيذ تعديلات الحركة من خلال استدعاءaddUpdateListener(). تتيح لك هذه الطريقة تحريك سماتView.
جعل عمليات الانتقال سلسة
يتوقّع المستخدمون أن تنتقل واجهة المستخدم الحديثة بسلاسة بين الحالات المختلفة، مثل ظهور عناصر واجهة المستخدم واختفائها تدريجيًا بدلاً من الظهور والاختفاء بشكل مفاجئ، وبدء الحركات وانتهائها بسلاسة بدلاً من البدء والتوقّف بشكل مفاجئ. يسهّل إطار عمل الرسوم المتحركة الخاصة بالخصائص في Android إجراء انتقالات سلسة.
لاستخدام نظام الصور المتحركة، عندما تغيّر إحدى الخصائص مظهر العرض، لا تغيّر الخاصية مباشرةً. استخدِم ValueAnimator بدلاً من ذلك لإجراء التغيير. في المثال التالي، يؤدي تعديل المكوّن الثانوي المحدّد في طريقة العرض إلى تدوير طريقة العرض المعروضة بالكامل ليتم توسيط مؤشر التحديد.
تغيّر الدالة ValueAnimator عملية التدوير على مدار فترة زمنية تبلغ عدة مئات من الملّي ثانية، بدلاً من ضبط قيمة التدوير الجديدة على الفور.
Kotlin
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply { setIntValues(targetAngle) duration = AUTOCENTER_ANIM_DURATION start() }
Java
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0); autoCenterAnimator.setIntValues(targetAngle); autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); autoCenterAnimator.start();
إذا كانت القيمة التي تريد تغييرها إحدى ViewالسماتView الأساسية، ستكون عملية إنشاء الحركة أسهل، لأنّ طرق العرض تتضمّن ViewPropertyAnimator
مدمجةViewPropertyAnimator محسّنة لإنشاء حركة متزامنة لسمات متعدّدة، كما في المثال التالي:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();