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

تجربة طريقة الإنشاء
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;
        }
    }
}

مراجع إضافية