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.

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ả trueonFling(): Vuốt nhanh (fling) - e1 (điểm đầu), e2 (điểm cuối), velocityX/YonScroll(): Kéo (drag) - khác với flingonSingleTapUp(),onDoubleTap(),onLongPress()
Thuật toán phát hiện Swipe
- Tính vector vị trí:
diffX = e2.x - e1.x,diffY = e2.y - e1.y - So sánh
|diffX|vs|diffY|→ xác định ngang hay dọc - Kiểm tra ngưỡng:
|diff| > SWIPE_THRESHOLD(khoảng cách) VÀ|velocity| > VELOCITY_THRESHOLD(tốc độ) - 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 refreshcom.github.chthai64:Android-SwipeLayout- Swipe menu cho item RecyclerViewItemTouchHelper(RecyclerView) - Swipe to dismiss, drag & drop