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

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

يشرح هذا الدرس كيفية تتبُّع الحركة في أحداث اللمس.

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

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

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

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

راجع الموارد ذات الصلة التالية:

تتبع السرعة

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

في ما يلي مثال يوضِّح الغرض من الطرق في واجهة برمجة التطبيقات VelocityTracker:

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 مع إحداثيات المؤشر التي تحدد الحركات النسبية مثل دلتا س أو ص، على غرار الإحداثيات التي يتم إرسالها بواسطة جهاز كرة تعقب. يمكنك استرداد الإحداثيات باستخدام getX() وgetY().

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

يمكن للعرض في تطبيقك رفع إصبعك عن المؤشر عن طريق طلب الرمز releasePointerCapture()، كما هو موضّح في مثال الرمز التالي:

Kotlin

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

Java

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

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