前言
原標題「二進制中 1 的個數」與內容完全不符,實際為 Android GestureDetector 手勢識別代碼。本文整理完整手勢監聽器實作、常見手勢類型、觸發時機與最佳實踐。
核心手勢類型與觸發時機
| 手勢 | 方法 | 觸發條件 | 典型用途 |
| 單擊 | onSingleTapUp | 按下→快速抬起,非雙擊 | 項目選中、按鈕點擊 |
| 雙擊 | onDoubleTap | 兩次快速單擊 | 縮放、進入詳情 |
| 長按 | onLongPress | 按下不動超過閾值 (~500ms) | 顯示選單、編輯模式 |
| 滑動 | onScroll | 按下移動 | 拖拽、畫布平移 |
| 快滑 | onFling | 快速滑動並抬起 | 翻頁、刪除列表項 |
| 按下確認 | onShowPress | 按下短暫停留,未移動/抬起 | 視覺反饋 (ripple) |
| 雙擊事件 | onDoubleTapEvent | 雙擊期間的 DOWN/MOVE/UP | 雙擊縮放過程處理 |
完整手勢監聽器實作
public class GestureHandler extends GestureDetector.SimpleOnGestureListener {
private final Context context;
private static final String TAG = "GestureHandler";
public GestureHandler(Context context) {
this.context = context;
}
@Override
public boolean onDown(MotionEvent e) {
// **必須返回 true**,否則後續手勢 (scroll/fling/longpress) 不會觸發
Log.d(TAG, "onDown: " + e.getAction());
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.d(TAG, "onShowPress: 短暫按下");
// 視覺反饋,如高亮項目
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d(TAG, "onSingleTapUp: 單擊確認");
// 處理單擊邏輯
return true; // 消費事件
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.d(TAG, "onDoubleTap: 雙擊");
// 處理雙擊邏輯 (如縮放)
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
// 雙擊手勢內部的每個事件 (DOWN, MOVE, UP)
return super.onDoubleTapEvent(e);
}
@Override
public void onLongPress(MotionEvent e) {
Log.d(TAG, "onLongPress: 長按");
Toast.makeText(context, "長按觸發", Toast.LENGTH_SHORT).show();
// 顯示上下文選單、進入編輯模式
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.d(TAG, "onScroll: 滑動 dx=" + distanceX + " dy=" + distanceY);
// e1: 首次 DOWN, e2: 當前 MOVE
// distanceX/Y: 自上次 scroll 移動距離 (正值=向左/向上)
// 處理拖拽、平移
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
Log.d(TAG, "onFling: 快滑 vx=" + velocityX + " vy=" + velocityY);
// velocityX/Y: 每秒像素速度
// 判斷方向:Math.abs(velocityX) > Math.abs(velocityY) ? 水平 : 垂直
// 處理翻頁、刷新、刪除
return true;
}
}
Activity/View 整合
public class MainActivity extends AppCompatActivity {
private GestureDetector gestureDetector;
private GestureHandler gestureHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gestureHandler = new GestureHandler(this);
gestureDetector = new GestureDetector(this, gestureHandler);
// 可選:設定雙擊縮放等參數
// gestureDetector.setIsLongpressEnabled(true); // 預設 true
}
/** 核心:將觸摸事件委派給 GestureDetector */
@Override
public boolean onTouchEvent(MotionEvent event) {
// 先讓 GestureDetector 處理,返回 true 表示已消費
boolean consumed = gestureDetector.onTouchEvent(event);
if (consumed) return true;
// 未消費則交給父類 (如滑動衝突處理)
return super.onTouchEvent(event);
}
// 若在特定 View 上處理
public void setupViewTouch(View targetView) {
targetView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
}
}
進階:ScaleGestureDetector 雙指縮放
private ScaleGestureDetector scaleDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
scaleDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor(); // >1 放大, <1 縮小
// 更新縮放矩陣、重繪
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
scaleDetector.onTouchEvent(event); // 先處理雙指
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
}
常見坑點與解決
| 問題 | 原因 | 解決 |
| onScroll/onFling 不觸發 | onDown 返回 false | onDown **必須返回 true** |
| 長按與滑動衝突 | 長按閾值觸發前移動會取消長按 | 調整邏輯或用 onShowPress 給視覺提示 |
| ListView/RecyclerView 滑動衝突 | 父容器攔截觸摸事件 | 請求父容器 requestDisallowInterceptTouchEvent(true) |
| 雙擊與單擊衝突 | 單擊需等待雙擊超時才觸發 | 單擊邏輯放 onSingleTapConfirmed (API 16+) |
| 座標系統混淆 | getX()/getRawX()、distanceX 正負 | 統一用相對座標,distanceX 正=向左移動 |
總結
- 繼承
SimpleOnGestureListener,僅覆寫需要的手勢
- onDown 必須返回 true 是所有手勢生效的前提
- 在 Activity/View 的
onTouchEvent 中委派 gestureDetector.onTouchEvent(event)
- 複雜場景組合
ScaleGestureDetector (雙指縮放)、ViewDragHelper (拖拽)
- 注意觸摸事件分發鏈:
dispatchTouchEvent → onInterceptTouchEvent → onTouchEvent