實現一個跟隨手指移動的懸浮圖標。首次移動正常,第二次按下移動時,圖標先瞬間跳回初始位置 (0,0),再跟隨移動。
// 錯誤邏輯
case MotionEvent.ACTION_MOVE:
mx = event.getRawX();
my = event.getRawY();
rx = mx - x; // x,y 是 ACTION_DOWN 時的原始觸摸座標
ry = my - y;
mParams.x = (int) rx; // 直接用相對偏移量作為窗口絕對座標!
mParams.y = (int) ry;
mWM.updateViewLayout(ivCursor, mParams);
break;
根因: mParams.x/y 是窗口在屏幕上的**絕對座標**。代碼中直接將**手指相對按下點的偏移量**賦給絕對座標。首次按下時 x,y ≈ 初始窗口座標,偏移量恰好近似絕對座標,看似正常。二次按下時 x,y 為新按下點座標,偏移量變小,導致窗口跳回原點附近。
private int mWindowStartX, mWindowStartY; // 記錄按下時窗口座標
private float mTouchStartX, mTouchStartY; // 記錄按下時手指屏幕座標
relativeLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 1. 記錄按下時 手指屏幕座標
mTouchStartX = event.getRawX();
mTouchStartY = event.getRawY();
// 2. 記錄按下時 窗口當前絕對座標
mWindowStartX = mParams.x;
mWindowStartY = mParams.y;
break;
case MotionEvent.ACTION_MOVE:
// 3. 計算手指移動偏移量
float dx = event.getRawX() - mTouchStartX;
float dy = event.getRawY() - mTouchStartY;
// 4. 新窗口座標 = 按下時窗口座標 + 手指偏移量
mParams.x = mWindowStartX + (int) dx;
mParams.y = mWindowStartY + (int) dy;
mWM.updateViewLayout(ivCursor, mParams);
break;
case MotionEvent.ACTION_UP:
// 可選:邊界檢查、吸附邊緣等
break;
}
return true; // 消費事件
}
});
| 變量 | 含義 | 賦值時機 |
|---|---|---|
mTouchStartX/Y | 手指按下時的屏幕絕對座標 (getRawX/Y) | ACTION_DOWN |
mWindowStartX/Y | 窗口按下時的屏幕絕對座標 (mParams.x/y) | ACTION_DOWN |
dx/dy | 手指相對按下點的移動偏移量 | ACTION_MOVE 計算 |
mParams.x/y | 窗口新的絕對座標 = mWindowStartX/Y + dx/dy | ACTION_MOVE 更新 |
mParams 前檢查是否超出屏幕可見區 (DisplayMetrics),防止移出屏幕。ACTION_UP 時判斷距離左右邊緣閾值,動畫平滑移至邊緣 (ValueAnimator 修改 mParams.x)。SYSTEM_ALERT_WINDOW 權限,引導用戶開啟。onDestroy 移除 mWM.removeView(ivCursor)。Bug 核心:將「手指相對偏移量」誤作為「窗口絕對座標」。 修復關鍵:**在 ACTION_DOWN 記錄窗口當前絕對座標,移動時新座標 = 記錄座標 + 手指偏移量。** 此邏輯適用於所有拖拽移動 View/Window 的場景。