DiffClickOrSwipLayout区分点击和滑动,滑动时拦截子 View 的事件 DiffClickOrSwipLayout区分点击和滑动滑动时拦截子 View 的事件package com.bg.widget; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; /** * 可拦截 Touch 事件的展开区域布局 * 用于区分点击和滑动滑动时拦截子 View 的事件 */ public class DiffClickOrSwipLayout extends LinearLayout { private float mStartX; private float mStartY; private boolean mIsDragging; private static final int DRAG_THRESHOLD 10; // dp private View mClickTarget; // 触摸点下的子 View private OnExpandGestureListener mGestureListener; public interface OnExpandGestureListener { /** * 按下事件取消倒计时 */ void onDown(); /** * 点击事件 */ void onClick(View view); /** * 滑动事件往左滑 */ void onSlideLeft(); /** * 滑动事件往右滑 */ void onSlideRight(); /** * 取消/释放恢复倒计时 */ void onRelease(); } public DiffClickOrSwipLayout(Context context) { super(context); } public DiffClickOrSwipLayout(Context context, AttributeSet attrs) { super(context, attrs); } public void setOnExpandGestureListener(OnExpandGestureListener listener) { mGestureListener listener; } Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mStartX ev.getRawX(); mStartY ev.getRawY(); mIsDragging false; mClickTarget findChildUnder(ev.getX(), ev.getY()); if (mGestureListener ! null) mGestureListener.onDown(); // 不拦截 DOWN让子 View 有机会响应点击 return false; case MotionEvent.ACTION_MOVE: if (!mIsDragging isDragBeyondThreshold(ev)) { mIsDragging true; // 拦截后后续事件全部进 onTouchEvent return true; } // 已处于拖拽中持续拦截否则不拦截 return mIsDragging; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // 未被拦截 → 点击 if (!mIsDragging mGestureListener ! null) { mGestureListener.onClick(mClickTarget); } mIsDragging false; mClickTarget null; return false; } return super.onInterceptTouchEvent(ev); } Override public boolean onTouchEvent(MotionEvent event) { final int action event.getAction(); // ─── 子 View 未消费 DOWN 的兜底路径 ─── // 当没有 touch target 时MOVE/UP 会跳过 onInterceptTouchEvent 直接进入这里 if (action MotionEvent.ACTION_DOWN) { mStartX event.getRawX(); mStartY event.getRawY(); mIsDragging false; mClickTarget findChildUnder(event.getX(), event.getY()); if (mGestureListener ! null) mGestureListener.onDown(); return true; // 必须返回 true否则系统不会发后续事件 } // 未处于拖拽状态时在此分支做兜底检测 if (!mIsDragging) { if (action MotionEvent.ACTION_MOVE) { if (isDragBeyondThreshold(event)) { mIsDragging true; } return true; } if (action MotionEvent.ACTION_UP) { if (mGestureListener ! null) mGestureListener.onClick(mClickTarget); mIsDragging false; mClickTarget null; return true; } return true; } // ─── 拖拽中拦截后的路径 或 兜底路径 ─── switch (action) { case MotionEvent.ACTION_MOVE: return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: handleDragEnd(event); mIsDragging false; return true; } return super.onTouchEvent(event); } /** * 处理拖拽结束判断是左滑还是普通释放 */ private void handleDragEnd(MotionEvent ev) { if (mGestureListener null) return; float slideDx mStartX - ev.getRawX(); // 往左滑为正往右滑为负 final float threshold dpToPx(50); if (slideDx threshold) { mGestureListener.onSlideLeft(); } else if (slideDx -threshold) { mGestureListener.onSlideRight(); } else { mGestureListener.onRelease(); } } /** * 判断手指移动是否超过拖拽阈值 */ private boolean isDragBeyondThreshold(MotionEvent ev) { float dx Math.abs(ev.getRawX() - mStartX); float dy Math.abs(ev.getRawY() - mStartY); float threshold dpToPx(DRAG_THRESHOLD); return dx threshold || dy threshold; } /** * 根据坐标找到触摸点下的子 View从顶层开始查找 * * return 触摸点下的子 View没有则返回 null */ private View findChildUnder(float x, float y) { final int count getChildCount(); for (int i count - 1; i 0; i--) { View child getChildAt(i); if (child.getVisibility() ! GONE x child.getLeft() x child.getRight() y child.getTop() y child.getBottom()) { return child; } } return null; } private float dpToPx(int dp) { return dp * getResources().getDisplayMetrics().density; } }