Giới thiệu
GestureDetector là class Android phát hiện các gesture phổ biến: tap, double tap, long press, scroll, fling (vuốt nhanh), v.v. Bài viết hướng dẫn implement fling detection để xử lý swipe left/right/up/down.
Các bước thực hiện
Bước 1: Tạo dự án mới
Android Studio → File → New Project → Empty View Activity.
Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvInstruction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Vuốt màn hình (swipe) để test Fling"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:textSize="20sp"
android:textColor="#2196F3"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/tvInstruction"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Bước 3: Custom GestureListener (FlingDetector.kt)
package com.example.flinggesture
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.Toast
class FlingDetector(private val context: android.content.Context) : GestureDetector.SimpleOnGestureListener() {
companion object {
// Ngưỡng tối thiểu để coi là fling (pixel/giây)
private const val SWIPE_THRESHOLD_VELOCITY = 100
private const val SWIPE_MIN_DISTANCE = 100
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
val distanceX = e2.x - e1.x
val distanceY = e2.y - e1.y
// Kiểm tra ngưỡng khoảng cách và tốc độ
if (Math.abs(distanceX) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
if (distanceX > 0) {
onSwipeRight()
} else {
onSwipeLeft()
}
return true
} else if (Math.abs(distanceY) > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
if (distanceY > 0) {
onSwipeDown()
} else {
onSwipeUp()
}
return true
}
return false
}
private fun onSwipeLeft() {
showToast("Vuốt TRÁI (Swipe Left)")
}
private fun onSwipeRight() {
showToast("Vuốt PHẢI (Swipe Right)")
}
private fun onSwipeUp() {
showToast("Vuốt LÊN (Swipe Up)")
}
private fun onSwipeDown() {
showToast("Vuốt XUỐNG (Swipe Down)")
}
private fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
Bước 4: MainActivity.kt - Kết nối GestureDetector
package com.example.flinggesture
import android.os.Bundle
import android.view.GestureDetector
import android.view.MotionEvent
import androidx.appcompat.app.AppCompatActivity
import com.example.flinggesture.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var gestureDetector: GestureDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Khởi tạo GestureDetector với custom listener
gestureDetector = GestureDetector(this, FlingDetector(this))
// Set touch listener cho root view (hoặc view muốn detect)
binding.root.setOnTouchListener { _, event ->
gestureDetector.onTouchEvent(event)
true // Consume event
}
}
// Override onTouchEvent của Activity cũng được (nếu không setOnTouchListener)
// override fun onTouchEvent(event: MotionEvent): Boolean {
// return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
// }
}
Cách hiện đại: ViewCompat.setOnTouchListener + Kotlin Extension
Tạo extension function tái sử dụng:
// Extensions.kt
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.content.Context
fun View.onFling(
minDistance: Int = 100,
minVelocity: Int = 100,
onSwipeLeft: () -> Unit = {},
onSwipeRight: () -> Unit = {},
onSwipeUp: () -> Unit = {},
onSwipeDown: () -> Unit = {}
) {
val detector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
val dx = e2.x - e1.x
val dy = e2.y - e1.y
when {
Math.abs(dx) > minDistance && Math.abs(velocityX) > minVelocity -> {
if (dx > 0) onSwipeRight() else onSwipeLeft()
return true
}
Math.abs(dy) > minDistance && Math.abs(velocityY) > minVelocity -> {
if (dy > 0) onSwipeDown() else onSwipeUp()
return true
}
}
return false
}
})
setOnTouchListener { _, event -> detector.onTouchEvent(event) }
}
Sử dụng:
binding.root.onFling(
onSwipeLeft = { binding.tvResult.text = "← Left" },
onSwipeRight = { binding.tvResult.text = "Right →" },
onSwipeUp = { binding.tvResult.text = "↑ Up" },
onSwipeDown = { binding.tvResult.text = "Down ↓" }
)
RecyclerView Swipe (ItemTouchHelper)
Nếu cần swipe trên item RecyclerView (xóa, archive...), dùng ItemTouchHelper:
val callback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder): Boolean = false
override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
when (direction) {
ItemTouchHelper.LEFT -> { /* Archive */ }
ItemTouchHelper.RIGHT -> { /* Delete */ }
}
adapter.notifyItemRemoved(position)
}
}
ItemTouchHelper(callback).attachToRecyclerView(recyclerView)
ViewPager2 / ViewPager - Swipe ngang giữa trang
Để swipe chuyển trang (onboarding, tabs), dùng ViewPager2:
// build.gradle.kts
implementation("androidx.viewpager2:viewpager2:1.1.0")
// Layout
// Adapter
class ScreenSlideAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun getItemCount() = 3
override fun createFragment(position: Int) = ScreenSlidePageFragment()
}
// Activity
binding.viewPager.adapter = ScreenSlideAdapter(this)
Lưu ý quan trọng
GestureDetectorchỉ detect **single-pointer** gestures (1 ngón). Multi-touch dùngScaleGestureDetector.- Fling khác với Scroll: Fling = vuốt nhanh rồi thả (velocity cao), Scroll = kéo chậm.
- Ngưỡng
SWIPE_MIN_DISTANCEvàSWIPE_THRESHOLD_VELOCITYcần tune cho phù hợp UX. - Trên View có click listener: Fling có thể conflict với click. Cân nhắc dùng
onSingleTapConfirmedcho click. - Jetpack Compose: Dùng
Modifier.pointerInput { detectTapGestures { } }hoặcdetectDragGestures { }.