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

Xử lý cử chỉ vuốt (Swipe Gestures) trên Android

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

  1. onDown(): Bắt đầu gesture, return true để tiếp tục theo dõi
  2. onFling(): Được gọi khi user nhấc ngón tay sau khi vuốt nhanh
  3. Tính diffX = e2.x - e1.x, diffY = e2.y - e1.y
  4. So sánh |diffX| vs |diffY| để xác định hướng chính
  5. Kiểm tra ngưỡng: |diff| > SWIPE_THRESHOLD VÀ |velocity| > SWIPE_VELOCITY_THRESHOLD
  6. 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);