تتبُّع حركات اللمس والمؤشر

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

يصف هذا الدرس كيفية تتبع الحركة في أحداث الاتصال.

يتم تشغيل onTouchEvent() جديد مع حدث ACTION_MOVE كلما تغيّر موضع لمس اللمس أو الضغط أو الحجم الحالي. كما هو موضّح في رصد الإيماءات الشائعة، يتم تسجيل كل هذه الأحداث في معلَمة MotionEvent الخاصة بـ onTouchEvent().

بما أنّ اللمسات التي تعتمد على الأصابع ليست دائمًا أكثر أشكال التفاعل دقة، غالبًا ما يعتمد رصد أحداث اللمس على الحركة وليس على التلامس البسيط. لمساعدة التطبيقات في التفريق بين الإيماءات المستندة إلى الحركة (مثل التمرير السريع) والإيماءات غير الحركية (مثل النقر مرة واحدة)، يتضمّن Android مفهوم شريط اللمس. تشير انزلاق اللمس إلى المسافة بالبكسل التي يمكن أن يتنقل بها المستخدم قبل أن يتم تفسير الإيماءة على أنها إيماءة قائمة على الحركة. للحصول على مزيد من المعلومات حول هذا الموضوع، اطّلِع على مقالة إدارة أحداث اللمس في ViewGroup.

هناك عدة طرق لتتبع الحركة في إيماءة، اعتمادًا على احتياجات تطبيقك. في ما يلي أمثلة على ذلك:

  • موضع البداية والنهاية للمؤشر، مثل نقل كائن على الشاشة من النقطة أ إلى النقطة ب.
  • الاتجاه الذي يتحرك فيه المؤشر، كما هو محدد في الإحداثيين X وY.
  • السجلّ. يمكنك معرفة حجم سجلّ الإيماءة من خلال استدعاء طريقة MotionEvent getHistorySize(). ويمكنك بعد ذلك الحصول على المواضع والأحجام والوقت والضغوط لكل حدث من الأحداث التاريخية باستخدام طرق getHistorical<Value> حدث الحركة. يُعد السجل مفيدًا عند عرض مسار بإصبع المستخدم، مثل الرسم باللمس. يُرجى الاطّلاع على مرجع "MotionEvent" للحصول على التفاصيل.
  • سرعة المؤشر أثناء تحركه على الشاشة التي تعمل باللمس

يمكنك الاطّلاع على المراجع التالية ذات الصلة:

تتبُّع السرعة

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

إليك مثال يوضّح الغرض من الطرق في VelocityTracker API:

Kotlin

private const val DEBUG_TAG = "Velocity"

class MainActivity : Activity() {
    private var mVelocityTracker: VelocityTracker? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                // Reset the velocity tracker back to its initial state.
                mVelocityTracker?.clear()
                // If necessary, retrieve a new VelocityTracker object to watch
                // the velocity of a motion.
                mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                // Add a user's movement to the tracker.
                mVelocityTracker?.addMovement(event)
            }
            MotionEvent.ACTION_MOVE -> {
                mVelocityTracker?.apply {
                    val pointerId: Int = event.getPointerId(event.actionIndex)
                    addMovement(event)
                    // When you want to determine the velocity, call
                    // computeCurrentVelocity(). Then, call getXVelocity() and
                    // getYVelocity() to retrieve the velocity for each pointer
                    // ID.
                    computeCurrentVelocity(1000)
                    // Log velocity of pixels per second. It's best practice to
                    // use VelocityTrackerCompat where possible.
                    Log.d("", "X velocity: ${getXVelocity(pointerId)}")
                    Log.d("", "Y velocity: ${getYVelocity(pointerId)}")
                }
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker?.recycle()
                mVelocityTracker = null
            }
        }
        return true
    }
}

Java

public class MainActivity extends Activity {
    private static final String DEBUG_TAG = "Velocity";
        ...
    private VelocityTracker mVelocityTracker = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();
        int action = event.getActionMasked();
        int pointerId = event.getPointerId(index);

        switch(action) {
            case MotionEvent.ACTION_DOWN:
                if(mVelocityTracker == null) {
                    // Retrieve a new VelocityTracker object to watch the
                    // velocity of a motion.
                    mVelocityTracker = VelocityTracker.obtain();
                }
                else {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker.clear();
                }
                // Add a user's movement to the tracker.
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                // When you want to determine the velocity, call
                // computeCurrentVelocity(). Then call getXVelocity() and
                // getYVelocity() to retrieve the velocity for each pointer ID.
                mVelocityTracker.computeCurrentVelocity(1000);
                // Log velocity of pixels per second. It's best practice to use
                // VelocityTrackerCompat where possible.
                Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId));
                Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId));
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
}

استخدام ميزة التقاط المؤشر

تستفيد بعض التطبيقات، مثل الألعاب وبرامج سطح المكتب البعيد وبرامج المحاكاة الافتراضية، من التحكم في مؤشر الماوس. ميزة "التقاط المؤشر" هي ميزة متوفرة في Android 8.0 (المستوى 26 لواجهة برمجة التطبيقات) والإصدارات الأحدث منه، وتوفّر إمكانية التحكّم هذه من خلال عرض كل أحداث الماوس في عرض مركَّز في تطبيقك.

طلب التقاط مؤشر الماوس

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

لطلب التقاط المؤشر، يمكنك استدعاء طريقة requestPointerCapture() على العرض. يوضّح مثال الرمز التالي كيفية طلب التقاط المؤشر عندما ينقر المستخدم على إحدى المشاهدات:

Kotlin

fun onClick(view: View) {
    view.requestPointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.requestPointerCapture();
}

بعد نجاح طلب التقاط المؤشر، يستدعي Android الرمز onPointerCaptureChange(true). يعرض النظام أحداث الماوس إلى العرض محل التركيز في تطبيقك طالما أنه في نفس تسلسل طرق العرض مثل العرض الذي طلب الالتقاط. تتوقف التطبيقات الأخرى عن تلقّي أحداث الماوس إلى أن يتم تفعيل عملية الالتقاط، بما في ذلك أحداث ACTION_OUTSIDE. يعرض Android أحداث المؤشر من مصادر أخرى غير الماوس على النحو المعتاد، ولكن مؤشر الماوس لم يعد مرئيًا.

التعامل مع أحداث المؤشر التي تم التقاطها

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

يوضّح مثال الرمز التالي كيفية تنفيذ onCapturedPointerEvent(MotionEvent):

Kotlin

override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
    // Get the coordinates required by your app.
    val verticalOffset: Float = motionEvent.y
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true
}

Java

@Override
public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
  // Get the coordinates required by your app.
  float verticalOffset = motionEvent.getY();
  // Use the coordinates to update your view and return true if the event is
  // successfully processed.
  return true;
}

يوضّح مثال الرمز التالي كيفية تسجيل OnCapturedPointerListener:

Kotlin

myView.setOnCapturedPointerListener { view, motionEvent ->
    // Get the coordinates required by your app.
    val horizontalOffset: Float = motionEvent.x
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    true
}

Java

myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
  @Override
  public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
    // Get the coordinates required by your app.
    float horizontalOffset = motionEvent.getX();
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true;
  }
});

سواء كنت تستخدم طريقة عرض مخصّصة أو تسجّل مستمعًا، ستتلقّى طريقة العرض MotionEvent مع إحداثيات مؤشر تحدّد الحركات النسبية، مثل X أو Y دلتا، على غرار الإحداثيات التي يعرضها جهاز كرة تعقّب. يمكنك استرداد الإحداثيات باستخدام getX() وgetY().

تحرير لقطة المؤشر

يمكن أن يرفع العرض في تطبيقك التقاط المؤشر من خلال استدعاء releasePointerCapture()، كما هو موضح في مثال الرمز التالي:

Kotlin

override fun onClick(view: View) {
    view.releasePointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.releasePointerCapture();
}

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