Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách xử lý cử chỉ vuốt trái/phải (Swipe Gesture) trên Android

Giới thiệu

Bài viết hướng dẫn phát hiện cử chỉ vuốt (swipe) sang trái và sang phải trên Android bằng cách tạo class OnSwipeTouchListener triển khai View.OnTouchListener và sử dụng GestureDetector.

Các bước thực hiện

Bước 1: Tạo dự án mới

Android Studio → File → New Project → Empty Activity.

Bước 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:id="@+id/relativeLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Vuốt Trái/Phải để phát hiện Swipe Gesture"
        android:textSize="16sp"
        android:textStyle="bold"
        android:textAlignment="center" />
</RelativeLayout>

Bước 3: Class OnSwipeTouchListener (OnSwipeTouchListener.java)

Class tái sử dụng được, tách biệt logic gesture.

import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class OnSwipeTouchListener implements View.OnTouchListener {
    private final GestureDetector gestureDetector;

    public OnSwipeTouchListener(Context context) {
        gestureDetector = new GestureDetector(context, new GestureListener());
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        return gestureDetector.onTouchEvent(motionEvent);
    }

    private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
        private static final int SWIPE_THRESHOLD = 100;
        private static final int SWIPE_VELOCITY_THRESHOLD = 100;

        @Override
        public boolean onDown(MotionEvent e) {
            return true; // Quan trọng: phải true để nhận fling
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            float diffY = e2.getY() - e1.getY();
            float diffX = e2.getX() - e1.getX();

            try {
                // Xác định hướng chính
                if (Math.abs(diffX) > Math.abs(diffY)) {
                    // Vuốt ngang
                    if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                        if (diffX > 0) {
                            onSwipeRight();
                        } else {
                            onSwipeLeft();
                        }
                        return true;
                    }
                } else {
                    // Vuốt dọc (có thể mở rộng)
                    if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
                        if (diffY > 0) {
                            onSwipeDown();
                        } else {
                            onSwipeUp();
                        }
                        return true;
                    }
                }
            } catch (Exception exception) {
                exception.printStackTrace();
            }
            return false;
        }
    }

    // Các method callback - override khi sử dụng
    public void onSwipeRight() {}
    public void onSwipeLeft() {}
    public void onSwipeUp() {}
    public void onSwipeDown() {}

    // Bonus: Tap, Double tap, Long press
    public void onClick() {}
    public void onDoubleClick() {}
    public void onLongClick() {}
}

Bước 4: Sử dụng trong MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.RelativeLayout;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    RelativeLayout layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        layout = findViewById(R.id.relativeLayout);

        layout.setOnTouchListener(new OnSwipeTouchListener(this) {
            @Override
            public void onSwipeLeft() {
                Toast.makeText(MainActivity.this, "Phát hiện vuốt TRÁI", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onSwipeRight() {
                Toast.makeText(MainActivity.this, "Phát hiện vuốt PHẢI", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onSwipeUp() {
                Toast.makeText(MainActivity.this, "Vuốt LÊN", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onSwipeDown() {
                Toast.makeText(MainActivity.this, "Vuốt XUỐNG", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Bước 5: Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Vuốt trái/phải/lên/xuống trên màn hình → Toast hiện hướng vuốt.

Cách xử lý cử chỉ vuốt trái/phải (Swipe Gesture) trên Android

Giải thích kỹ thuật

GestureDetector.SimpleOnGestureListener

Cung cấp các callback gesture:

  • onDown(): Ngón tay chạm xuống - bắt buộc trả true
  • onFling(): Vuốt nhanh (fling) - e1 (điểm đầu), e2 (điểm cuối), velocityX/Y
  • onScroll(): Kéo (drag) - khác với fling
  • onSingleTapUp(), onDoubleTap(), onLongPress()

Thuật toán phát hiện Swipe

  1. Tính vector vị trí: diffX = e2.x - e1.x, diffY = e2.y - e1.y
  2. So sánh |diffX| vs |diffY| → xác định ngang hay dọc
  3. Kiểm tra ngưỡng: |diff| > SWIPE_THRESHOLD (khoảng cách) VÀ |velocity| > VELOCITY_THRESHOLD (tốc độ)
  4. Dấu của diff → hướng: diffX > 0 = phải, diffX < 0 = trái

Cải tiến & Best Practices

1. Tách Interface (Cleaner)

public interface OnSwipeListener {
    void onSwipeLeft();
    void onSwipeRight();
    void onSwipeUp();
    void onSwipeDown();
}

// Trong OnSwipeTouchListener:
private OnSwipeListener listener;
public void setOnSwipeListener(OnSwipeListener listener) { this.listener = listener; }

// Gọi:
if (listener != null) listener.onSwipeRight();

2. Xử lý xung đột với ScrollView/RecyclerView

Nếu gán listener cho ViewGroup có scroll (RecyclerView, ScrollView), swipe sẽ xung đột với scroll.

Giải pháp: Chỉ gán cho View không scroll (header, footer, container cha) hoặc dùng ItemTouchHelper cho RecyclerView.

3. Điều chỉnh nhạy độ

// Tăng ngưỡng cho ít nhạy hơn (cần vuốt xa hơn)
private static final int SWIPE_THRESHOLD = 150; // dp → convert to px
private static final int SWIPE_VELOCITY_THRESHOLD = 150;

4. Dùng Kotlin Extension (Cleaner)

// Extension function cho View
fun View.setOnSwipeListener(listener: OnSwipeListener) {
    setOnTouchListener(OnSwipeTouchListener(context).apply {
        setOnSwipeListener(listener)
    })
}

// Sử dụng:
relativeLayout.setOnSwipeListener(object : OnSwipeListener {
    override fun onSwipeLeft() { toast("Trái") }
    override fun onSwipeRight() { toast("Phải") }
    override fun onSwipeUp() { toast("Lên") }
    override fun onSwipeDown() { toast("Xuống") }
})

Thư viện thay thế

  • androidx.swiperefreshlayout:swiperefreshlayout - Pull to refresh
  • com.github.chthai64:Android-SwipeLayout - Swipe menu cho item RecyclerView
  • ItemTouchHelper (RecyclerView) - Swipe to dismiss, drag & drop