ภาพรวม
ตัวอย่างนี้สาธิตการจับเหตุการณ์ MotionEvent.ACTION_UP ซึ่งเกิดขึ้นเมื่อผู้ใช้ปล่อยนิ้วจากหน้าจอ (Pointer สุดท้าย) หมายถึงการจบลงของ Gesture ใช้คู่กับ ACTION_DOWN เพื่อทำ Click/Tap detection แบบกำหนดเอง
ขั้นตอนการสร้างโปรเจกต์
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
Android Studio → File → New Project → Empty Activity
ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<TextView
android:id="@+id/actionEvent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="30dp"
android:textSize="40sp"
android:gravity="center"
android:text="กดแล้วปล่อยที่นี่"
android:background="#FFF3E0"
android:padding="20dp" />
</LinearLayout>
ขั้นตอนที่ 3: โค้ดหลัก (src/MainActivity.java)
package com.example.myapplication;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
TextView textView;
@SuppressLint("ClickableViewAccessibility")
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView actionEvent = findViewById(R.id.actionEvent);
actionEvent.setText("Action UP Test");
actionEvent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_UP) {
// เกิดขึ้นเมื่อปล่อยนิ้ว (Pointer สุดท้าย)
Toast.makeText(MainActivity.this, "Action UP - ปล่อยนิ้วแล้ว", Toast.LENGTH_SHORT).show();
actionEvent.setText("สถานะ: ปล่อยนิ้ว (UP)");
actionEvent.setBackgroundColor(0xFF2196F3); // กลับสีปกติ
return true;
}
if (action == MotionEvent.ACTION_DOWN) {
actionEvent.setText("สถานะ: กดลง (DOWN)");
actionEvent.setBackgroundColor(0xFF4CAF50);
return true;
}
return false;
}
});
}
}
ลำดับ Event ใน Gesture ปกติ (Single Tap)
ACTION_DOWN (1 pointer) → ACTION_MOVE* (0 หรือหลายครั้ง) → ACTION_UP
Multi-touch Sequence
Pointer 1: ACTION_DOWN Pointer 2: ACTION_POINTER_DOWN Pointer 1: ACTION_MOVE... Pointer 2: ACTION_MOVE... Pointer 2: ACTION_POINTER_UP Pointer 1: ACTION_MOVE... Pointer 1: ACTION_UP ← จบ Gesture
Action Up vs OnClickListener
| คุณสมบัติ | OnTouchListener (ACTION_UP) | OnClickListener |
|---|---|---|
| Trigger | ปล่อยนิ้วทุกครั้ง (แม้จะลากนอก View) | ปล่อยนิ้วภายใน View เท่านั้น |
| Drag Cancel | ยังได้รับ UP | ไม่ได้รับ Click |
| Timing | ทันทีเมื่อปล่อย | หลัง UP + Validation |
| Custom Logic | ยืดหยุ่นสูงสุด | จำกัด |
กรณีการใช้งาน ACTION_UP
- Custom Click Handling: ต้องการตรวจสอบเงื่อนไขก่อน Click
- Drag & Drop End: จบการลาก วาง Item (Drop zone detection)
- Drawing Complete: จบ Stroke, บันทึก Path
- Swipe/Fling Detection: วัดความเร็ว/ทิศทางจาก DOWN ถึง UP
- Long Press Detection: วัดเวลาระหว่าง DOWN ถึง UP
ตัวอย่าง: คำนวณ Swipe Velocity
private long downTime;
private float downX, downY;
view.setOnTouchListener((v, event) -> {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
downTime = System.currentTimeMillis();
downX = event.getX();
downY = event.getY();
return true;
case MotionEvent.ACTION_UP:
long upTime = System.currentTimeMillis();
float upX = event.getX();
float upY = event.getY();
long duration = upTime - downTime;
float distance = (float) Math.hypot(upX - downX, upY - downY);
float velocity = distance / duration; // px/ms
if (distance > 100 && velocity > 0.5f) {
// Swipe detected
float angle = (float) Math.toDegrees(Math.atan2(upY - downY, upX - downX));
handleSwipe(angle, velocity);
}
return true;
}
return false;
});
ข้อควรระวัง
- ต้อง return true ใน ACTION_DOWN ถึงจะได้รับ ACTION_UP
- ACTION_UP อาจไม่เกิด ถ้ามี
ACTION_CANCEL(System interrupt: Dialog, Phone call, ANR) - Multi-touch: ACTION_UP = Pointer สุดท้ายปล่อย | ใช้ ACTION_POINTER_UP สำหรับ Pointer อื่นๆ
- พิกัดใน UP อาจต่างจาก DOWN หากลาก (ใช้สำหรับ Swipe detection)
- สำหรับ Click ปกติ ใช้ OnClickListener ดีกว่า (มี Ripple, Accessibility, Haptic feedback อัตโนมัติ)
Best Practice: ใช้ GestureDetector
GestureDetector detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
// Tap ปกติ
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// Double tap
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// Long press
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float vX, float vY) {
// Swipe/Fling
return true;
}
});
view.setOnTouchListener((v, event) -> detector.onTouchEvent(event));