זיהוי תנועות נפוצות

אפשר לנסות את הדרך של כתיבת הודעה
‫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;
    }
});

שימו לב לא ליצור פונקציית listener שמחזירה false לאירוע ACTION_DOWN. אם עושים את זה, לא מתבצעת קריאה ל-listener עבור הרצף הבא של האירועים 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;
        }
    }
}

מקורות מידע נוספים