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

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

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

  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;
        }
    }
}

مراجع إضافية