إنشاء عرض مخصّص تفاعلي

تجربة طريقة الإنشاء
Jetpack Compose هي مجموعة أدوات واجهة المستخدم المقترَحة لنظام التشغيل Android. تعرَّف على كيفية استخدام التنسيقات في Compose.

يُعد رسم واجهة مستخدم جزءًا واحدًا فقط من إنشاء طريقة عرض مخصّصة. تحتاج أيضًا إلى جعل رؤيتك تستجيب لإدخال المستخدم بطريقة تشبه إلى حد كبير الإجراء الواقعي الذي تحاكيه.

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

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

توضِّح هذه الصفحة كيفية استخدام ميزات إطار عمل 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 من خلال قراءة الوقت الحالي واستخدام النموذج الفيزيائي لحساب الموضعين س وص في ذلك الوقت. يمكنك استدعاء 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 الأساسية، يصبح إنشاء الصورة المتحركة أسهل من ذي قبل، لأنّ الملفات الشخصية تحتوي على ViewPropertyAnimator مضمّنة تم تحسينها من أجل إنشاء صور متحركة متزامنة لعدّة خصائص، كما في المثال التالي:

Kotlin

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

Java

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();