Swipe Gesture Detection - Phát hiện vuốt trái/phải/lên/xuống
Android cung cấp GestureDetector để phát hiện các cử chỉ (gesture) như tap, double tap, scroll, fling (vuốt nhanh). Ví dụ này tạo listener tái sử dụng cho swipe 4 hướng.
Bước 1: Tạo dự án mới
Khởi tạo project Android Studio.
Bước 2: Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/swipeArea"
android:layout_width="200dp"
android:layout_height="200dp"
android:text="Vuốt ở đây\n(Trái/Phải/Lên/Xuống)"
android:gravity="center"
android:textSize="16sp"
android:background="@color/colorPrimary"
android:textColor="#FFF" />
<TextView
android:id="@+id/tvResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Kết quả: "
android:textSize="20sp" />
</LinearLayout>
Bước 3: OnSwipeTouchListener.java (Reusable Class)
package com.example.myapplication;
import android.content.Context;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class OnSwipeTouchListener implements OnTouchListener {
private final GestureDetector gestureDetector;
public OnSwipeTouchListener(Context ctx) {
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
private final class GestureListener extends SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 100; // Khoảng cách tối thiểu (px)
private static final int SWIPE_VELOCITY_THRESHOLD = 100; // Tốc độ tối thiểu (px/s)
@Override
public boolean onDown(MotionEvent e) {
return true; // Bắt buộc return true để nhận các event tiếp theo
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
// Xác định hướng vuốt chủ yếu (ngang hay dọc)
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(); // Vuốt phải (e2.x > e1.x)
} else {
onSwipeLeft(); // Vuốt trái
}
result = true;
}
} else {
// Vuốt dọc
if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom(); // Vuốt xuống
} else {
onSwipeTop(); // Vuốt lên
}
result = true;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
}
// Các method override trong subclass
public void onSwipeRight() {}
public void onSwipeLeft() {}
public void onSwipeTop() {}
public void onSwipeBottom() {}
}
Bước 4: MainActivity.java - Sử dụng Listener
package com.example.myapplication;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private TextView tvResult;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvResult = findViewById(R.id.tvResult);
Button swipeArea = findViewById(R.id.swipeArea);
// Gán OnSwipeTouchListener cho view
swipeArea.setOnTouchListener(new OnSwipeTouchListener(this) {
@Override
public void onSwipeTop() {
showResult("Vuốt LÊN");
}
@Override
public void onSwipeRight() {
showResult("Vuốt PHẢI");
}
@Override
public void onSwipeLeft() {
showResult("Vuốt TRÁI");
}
@Override
public void onSwipeBottom() {
showResult("Vuốt XUỐNG");
}
});
}
private void showResult(String direction) {
tvResult.setText("Kết quả: " + direction);
Toast.makeText(this, direction, Toast.LENGTH_SHORT).show();
}
}
Giải thích thuật toán phát hiện Swipe
- onDown(): Bắt đầu gesture, return true để tiếp tục theo dõi
- onFling(): Được gọi khi user nhấc ngón tay sau khi vuốt nhanh
- Tính
diffX = e2.x - e1.x,diffY = e2.y - e1.y - So sánh
|diffX|vs|diffY|để xác định hướng chính - Kiểm tra ngưỡng:
|diff| > SWIPE_THRESHOLDVÀ|velocity| > SWIPE_VELOCITY_THRESHOLD - Gọi callback tương ứng
Tùy chỉnh ngưỡng (Threshold)
public class OnSwipeTouchListener implements OnTouchListener {
// Có thể truyền threshold qua constructor
private final int swipeThreshold;
private final int swipeVelocityThreshold;
public OnSwipeTouchListener(Context ctx, int swipeThreshold, int swipeVelocityThreshold) {
this.swipeThreshold = swipeThreshold;
this.swipeVelocityThreshold = swipeVelocityThreshold;
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
// ... trong GestureListener dùng this.swipeThreshold
}
Ứng dụng thực tế
| Use Case | Gesture | Action |
|---|---|---|
| Xóa item trong list | Vuốt trái/phải | Show delete button, confirm delete |
| Chuyển tab/ViewPager | Vuốt trái/phải | setCurrentItem() |
| Pull to refresh | Vuốt xuống | Trigger refresh (SwipeRefreshLayout) |
| Đóng activity/dialog | Vuốt xuống | finish() / dismiss() |
| Navigation drawer | Vuốt phải từ mép trái | Open drawer (DrawerLayout tự xử lý) |
| Image gallery | Vuốt trái/phải | Prev/Next image |
Thư viện hỗ trợ (Khuyên dùng)
- ItemTouchHelper (RecyclerView built-in): Swipe to dismiss, drag & drop
- SwipeRefreshLayout: Pull to refresh chuẩn Material
- ViewPager2: Swipe ngang giữa fragment/page
- GestureDetectorCompat: Support library version
ItemTouchHelper - Swipe to Delete (RecyclerView)
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(RecyclerView rv, ViewHolder vh, ViewHolder target) {
return false; // Không hỗ trợ drag
}
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT) {
// Vuốt trái -> Archive
archiveItem(position);
} else {
// Vuốt phải -> Delete
deleteItem(position);
}
}
@Override
public void onChildDraw(Canvas c, RecyclerView rv, ViewHolder vh, float dX, float dY, int actionState, boolean isCurrentlyActive) {
// Vẽ background màu đỏ/xanh khi vuốt
super.onChildDraw(c, rv, vh, dX, dY, actionState, isCurrentlyActive);
}
};
new ItemTouchHelper(callback).attachToRecyclerView(recyclerView);