رصد الإيماءات الشائعة

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

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

  1. جارٍ جمع بيانات أحداث اللمس.
  2. تفسير البيانات لتحديد ما إذا كانت تستوفي معايير الإيماءات التي يتيحها تطبيقك

صفوف AndroidX

تستخدم الأمثلة في هذا المستند الفئتَين GestureDetectorCompat وMotionEventCompat. تتوفر هذه الفئات في مكتبة AndroidX. استخدِم فئات AndroidX متى أمكن ذلك لضمان التوافق مع الأجهزة السابقة. MotionEventCompat هو ليس بديل للفئة MotionEvent. بل توفّر بدلاً من ذلك طرق الأداة المساعدة الثابتة التي يمكنك من خلالها تمرير كائن MotionEvent لتلقّي الإجراء المرتبط بذلك الحدث.

جمع البيانات

عندما يضع المستخدم إصبعًا واحدًا أو أكثر على الشاشة، يؤدي ذلك إلى إعادة الاتصال onTouchEvent() في طريقة العرض التي تتلقّى أحداث اللمس. يتم تنشيط onTouchEvent() عدة مرات لكل تسلسل من أحداث اللمس، مثل الموضع والضغط والحجم وإضافة إصبع آخر.

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

تسجيل أحداث اللمس لنشاط أو عرض

لاعتراض أحداث اللمس في Activity أو View، يمكنك إلغاء معاودة الاتصال onTouchEvent().

يستخدم مقتطف الرمز التالي getAction() لاستخراج الإجراء الذي ينفّذه المستخدم من المعلَمة event. يمنحك هذا البيانات الأولية التي تحتاجها لتحديد ما إذا كانت الإيماءة التي تهتم بها ستحدث أم لا.

Kotlin

class MainActivity : Activity() {
    ...
    // This example shows an Activity. You can use the same approach if you are 
    // subclassing a View.
    override fun onTouchEvent(event: MotionEvent): Boolean {
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(DEBUG_TAG, "Action was DOWN")
                true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(DEBUG_TAG, "Action was MOVE")
                true
            }
            MotionEvent.ACTION_UP -> {
                Log.d(DEBUG_TAG, "Action was UP")
                true
            }
            MotionEvent.ACTION_CANCEL -> {
                Log.d(DEBUG_TAG, "Action was CANCEL")
                true
            }
            MotionEvent.ACTION_OUTSIDE -> {
                Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
                true
            }
            else -> super.onTouchEvent(event)
        }
    }
}

Java

public class MainActivity extends Activity {
...
// This example shows an Activity. You can use the same approach if you are
// subclassing a View.
@Override
public boolean onTouchEvent(MotionEvent event){
    switch(event.getAction()) {
        case (MotionEvent.ACTION_DOWN) :
            Log.d(DEBUG_TAG,"Action was DOWN");
            return true;
        case (MotionEvent.ACTION_MOVE) :
            Log.d(DEBUG_TAG,"Action was MOVE");
            return true;
        case (MotionEvent.ACTION_UP) :
            Log.d(DEBUG_TAG,"Action was UP");
            return true;
        case (MotionEvent.ACTION_CANCEL) :
            Log.d(DEBUG_TAG,"Action was CANCEL");
            return true;
        case (MotionEvent.ACTION_OUTSIDE) :
            Log.d(DEBUG_TAG,"Movement occurred outside bounds of current screen element");
            return true;
        default :
            return super.onTouchEvent(event);
    }
}

ينتج عن هذا الرمز رسائل مثل ما يلي في Logcat أثناء نقر المستخدم ولمسه مع الاستمرار وسحبه:

GESTURES D   Action was DOWN
GESTURES D   Action was UP
GESTURES D   Action was MOVE

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

تسجيل أحداث اللمس لعرض واحد

كبديل لـ onTouchEvent()، يمكنك إرفاق كائن View.OnTouchListener بأي كائن View باستخدام الطريقة setOnTouchListener(). ويتيح ذلك الاستماع إلى أحداث اللمس بدون تصنيف View حالي، كما هو موضّح في المثال التالي:

Kotlin

findViewById<View>(R.id.my_view).setOnTouchListener { v, event ->
    // Respond to touch events.
    true
}

Java

View myView = findViewById(R.id.my_view);
myView.setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        // Respond to touch events.
        return true;
    }
});

يجب توخّي الحذر من إنشاء أداة معالجة تعليقات تعرض رمز الاستجابة false لحدث ACTION_DOWN. إذا فعلت ذلك، لن يتم استدعاء المستمع للتسلسل اللاحق لأحداث ACTION_MOVE وACTION_UP. ويرجع ذلك إلى أنّ ACTION_DOWN هي نقطة البداية لجميع أحداث اللمس.

وإذا كنت تنشئ طريقة عرض مخصّصة، يمكنك إلغاء onTouchEvent() كما هو موضّح سابقًا.

رصد الإيماءات

يوفّر Android الفئة GestureDetector لرصد الإيماءات الشائعة. وتتضمّن بعض الإيماءات المتوافقة onDown() وonLongPress() وonFling(). يمكنك استخدام GestureDetector مع طريقة onTouchEvent() الموضّحة سابقًا.

رصد كل الإيماءات المتوافقة

عند إنشاء مثيل كائن GestureDetectorCompat، تتمثل إحدى المعلمات التي يتم استخدامها في فئة تنفذ واجهة GestureDetector.OnGestureListener. يرسل GestureDetector.OnGestureListener إشعارًا إلى المستخدمين عند حدوث حدث لمس معيّن. لتمكين كائن GestureDetector من تلقّي الأحداث، عليك إلغاء طريقة onTouchEvent() الخاصة بالملف الشخصي أو النشاط، وتمرير جميع الأحداث المرصودة إلى مثيل أداة الرصد.

في المقتطف التالي، تشير القيمة المعروضة true من طرق on<TouchEvent> الفردية إلى أنّه تم التعامل مع حدث اللمس. وتمرر القيمة التي تعرضها false الأحداث لأسفل عبر حزمة طرق العرض إلى أن يتم التعامل مع اللمسة بنجاح.

إذا شغّلت المقتطف التالي في تطبيق اختباري، يمكنك التعرّف على كيفية تنفيذ الإجراءات عند التفاعل مع الشاشة التي تعمل باللمس ومحتوى MotionEvent لكل حدث لمس. سترى بعد ذلك مقدار البيانات التي يتم إنشاؤها للتفاعلات البسيطة.

Kotlin

private const val DEBUG_TAG = "Gestures"

class MainActivity :
        Activity(),
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {

    private lateinit var mDetector: GestureDetectorCompat

    // Called when the activity is first created.
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = GestureDetectorCompat(this, this)
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (mDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    override fun onDown(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDown: $event")
        return true
    }

    override fun onFling(
            event1: MotionEvent,
            event2: MotionEvent,
            velocityX: Float,
            velocityY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onFling: $event1 $event2")
        return true
    }

    override fun onLongPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onLongPress: $event")
    }

    override fun onScroll(
            event1: MotionEvent,
            event2: MotionEvent,
            distanceX: Float,
            distanceY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
        return true
    }

    override fun onShowPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onShowPress: $event")
    }

    override fun onSingleTapUp(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapUp: $event")
        return true
    }

    override fun onDoubleTap(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTap: $event")
        return true
    }

    override fun onDoubleTapEvent(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
        return true
    }

    override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
        return true
    }

}

Java

public class MainActivity extends Activity implements
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener{

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetectorCompat mDetector;

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = new GestureDetectorCompat(this,this);
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
            float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

رصد مجموعة فرعية من الإيماءات المتوافقة

إذا كنت تريد معالجة بعض الإيماءات فقط، يمكنك توسيع نطاق GestureDetector.SimpleOnGestureListener بدلاً من تنفيذ واجهة GestureDetector.OnGestureListener.

توفّر GestureDetector.SimpleOnGestureListener عملية تنفيذ لجميع طُرق on<TouchEvent> من خلال عرض false لها جميعًا. يتيح لك هذا إلغاء الأساليب التي تهمك فقط. على سبيل المثال، ينشئ مقتطف الرمز التالي فئة تمتد إلى GestureDetector.SimpleOnGestureListener وتلغي الترميزَين onFling() وonDown().

سواء كنت تستخدم GestureDetector.OnGestureListener أو GestureDetector.SimpleOnGestureListener، من أفضل الممارسات تنفيذ طريقة onDown() التي تعرض true. ويعود السبب في ذلك إلى أنّ كل الإيماءات تبدأ برسالة onDown(). إذا أعدت false من onDown()، كما تفعل GestureDetector.SimpleOnGestureListener تلقائيًا، يفترض النظام أنك تريد تجاهل بقية الإيماءة، وأن طرق GestureDetector.OnGestureListener الأخرى لا يتم استدعاءها. قد يتسبب ذلك في حدوث مشاكل غير متوقَّعة في تطبيقك. يُرجى عدم عرض الرمز false من onDown() إلا إذا كنت تريد تجاهل إيماءة كاملة.

Kotlin

private const val DEBUG_TAG = "Gestures"

class MainActivity : Activity() {

    private lateinit var mDetector: GestureDetectorCompat

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mDetector = GestureDetectorCompat(this, MyGestureListener())
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        mDetector.onTouchEvent(event)
        return super.onTouchEvent(event)
    }

    private class MyGestureListener : GestureDetector.SimpleOnGestureListener() {

        override fun onDown(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDown: $event")
            return true
        }

        override fun onFling(
                event1: MotionEvent,
                event2: MotionEvent,
                velocityX: Float,
                velocityY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onFling: $event1 $event2")
            return true
        }
    }
}

Java

public class MainActivity extends Activity {

    private GestureDetectorCompat mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDetector = new GestureDetectorCompat(this, new MyGestureListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
              return true;
        }
        return super.onTouchEvent(event);
    }

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        private static final String DEBUG_TAG = "Gestures";

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d(DEBUG_TAG,"onDown: " + event.toString());
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
                float velocityX, float velocityY) {
            Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
            return true;
        }
    }
}

مصادر إضافية