ACTION_CANCEL事件和事件回传

本文深入解析了Android事件传递机制,特别是ONINTERCEPTTOUCHEVENT()和ONTOUCHEVENT()方法的应用。详细解释了ACTION_CANCEL事件的触发条件及其处理方式,以及事件如何在控件间传递。通过实例代码演示了如何在不同控件中实现事件拦截与响应。

ANDROID事件传递机制以及ONINTERCEPTTOUCHEVENT()和ONTOUCHEVENT()详解二之小秘与领导的故事

ANDROID事件传递机制以及ONINTERCEPTTOUCHEVENT()和ONTOUCHEVENT()总结

前两篇博文讲了onInterceptTouchEvent和OnTouchEvent的处理流程(没有看的赶紧去补下)

来自 http://www.cnblogs.com/xiaoQLu/archive/2013/04/02/2994030.html

ACTION_CANCEL事件,官方文档讲的是当前手势被释放,你将不会接收到其他的事件,应该向ACTION_UP一样对待它。

那到底什么情况会触发这个事件呢?当 当前控件(子控件,儿子)收到前驱事件(ACTION_MOVE或者ACTION_MOVE)后,它的父控件(老爸)突然插手,截断事件的传递,这时,当前控件就会收到ACTION_CANCEL,收到此事件后,不管子控件此时返回true或者false,都认为这一个动作已完成,不会再回传到父控件的OnTouchEvent中处理,同时后续事件,会通过dispatchEvent方法直接传送到父控件这里来处理。这和之前的结论有点相悖,之前说过如果子控件的OnTouchEvent返回false,表明事件未被处理,是回传到父控件去处理的,这里纠正一下,只有ACTION_DOWN事件才可以被回传,ACTION_MOVE和ACTION_UP事件会跟随ACTION_DOWN事件,即ACTION_DOWN是哪个控件处理的,后续事件都传递到这里,不会上抛到父控件,ACTION_CANCEL也不能回传。还有一点,触摸区域的范围问题,如果触摸区域在子控件内,同时父控件没有截断事件传递,刚不管子控件是否拦截此事件,都会传递到子控件的OnTouchEvent中处理,可以看成一种责任吧,因为我点的就是你,你父亲没有拦截,说明他不想处理,那到你这里了,不管你拦不拦截,都得你来处理。

结论:ACTION_CANCEL事件是收到前驱事件后,后续事件被父控件拦截的情况下产生,onTouchEvent的事件回传到父控件只会发生在ACTION_DOWN事件中

实例说明:在下面的实例中,LinearView1是父控件,LayoutView2是子控件,点击区域是LayoutView2,可以看到父控件的onInterceptTouchEvent方法在ACTION_DOWN的时候没有拦截事件,但是在ACTION_MOVE的时候突然插手,拦截掉事件,这时候,子控件就会收到ACTION_CANCEL。

 
 
复制代码
public class LayoutView1 extends LinearLayout {

    private final String TAG = "LayoutView1"; public LayoutView1(Context context, AttributeSet attrs) { super(context, attrs); Log.d(TAG, TAG); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "1:onInterceptTouchEvent action:ACTION_DOWN"); //return true; break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "1:onInterceptTouchEvent action:ACTION_MOVE"); return true; //break; case MotionEvent.ACTION_UP: Log.d(TAG, "1:onInterceptTouchEvent action:ACTION_UP"); //return true; break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "1:onInterceptTouchEvent action:ACTION_CANCEL"); break; } return false; } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "1:onTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "1:onTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "1:onTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "1:onTouchEvent action:ACTION_CANCEL"); break; } return true; } }
复制代码
复制代码
public class LayoutView2 extends LinearLayout {
    private final String TAG = "LayoutView2"; public LayoutView2(Context context, AttributeSet attrs) { super(context, attrs); Log.d(TAG,TAG); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: Log.d(TAG,"2:onInterceptTouchEvent action:ACTION_DOWN"); break; //return true; case MotionEvent.ACTION_MOVE: Log.d(TAG,"2:onInterceptTouchEvent action:ACTION_MOVE"); break; //return true; case MotionEvent.ACTION_UP: Log.d(TAG,"2:onInterceptTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG,"2:onInterceptTouchEvent action:ACTION_CANCEL"); break; } return false; } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: Log.d(TAG,"2:onTouchEvent action:ACTION_DOWN"); //return false; break; case MotionEvent.ACTION_MOVE: Log.d(TAG,"2:onTouchEvent action:ACTION_MOVE"); return false; //break; case MotionEvent.ACTION_UP: Log.d(TAG,"2:onTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG,"2:onTouchEvent action:ACTION_CANCEL"); break; } return true; } }
复制代码

有不明白的下载源码运行看看结果

源码下载:http://files.cnblogs.com/xiaoQLu/MotionEventTest.rar

转载于:https://www.cnblogs.com/LiesSu/p/3899484.html

<think>嗯,用户现在想了解安卓开发中的ACTION_UPACTION_CANCEL事件的使用场景及区别。我之前已经回答过他们关于int变量System.out.println的问题,现在他们转向触摸事件处理了。我需要先仔细分析用户的问题,确保理解正确。 首先,用户的问题明确,直接询问这两个事件的区别使用场景。根据提供的引用资料,特别是引用[2]、[3]、[4],里面提到了ACTION_CANCEL的产生机制,比如父视图拦截事件时子视图会收到ACTION_CANCEL,而ACTION_UP则是正常结束触摸。我需要结合这些引用内容来组织回答。 接下来,我需要确定这两个事件的核心区别。根据引用[4],当父视图拦截了ACTION_MOVE或ACTION_UP,子视图会收到ACTION_CANCEL,而ACTION_UP是在正常抬起时触发。这说明ACTION_CANCEL事件序列被中断的情况,而ACTION_UP是正常结束。 然后,使用场景方面,引用[3]中的例子说明当父布局拦截MOVE事件时,子控件会收到ACTION_CANCEL。而ACTION_UP则是在用户正常抬起手指时触发。比如按钮点击后滑动到外部抬起会触发ACTION_CANCEL,而滑动回去抬起可能触发ACTION_UP,但实际可能不会,需要具体分析。 需要结合引用中的具体例子,比如ScrollView嵌套ListView的情况,虽然可能没有滑动冲突,但ACTION_CANCEL可能在父视图拦截时触发。同时,引用[5]提到事件分发的机制,尤其是ACTION_DOWNACTION_POINTER_DOWN的处理,这可能影响后续事件的传递,但用户的问题集中在UPCANCEL,所以这部分可能作为补充。 我需要确保回答结构清晰,先讲区别,再讲使用场景,最后结合代码示例。同时,注意引用提供的资料,在适当的地方标注引用标识,如[^2][^4]。 还要考虑用户可能的深层需求,比如如何处理这些事件,避免常见错误。例如,在自定义View时忘记处理ACTION_CANCEL可能导致状态不一致,或者如何处理事件拦截后的状态恢复。 另外,用户之前的提问涉及多线程Handler,但这次是触摸事件,需要确保回答专注于当前问题,不引入无关内容。但如果有类似的最佳实践,比如使用标志位或状态管理,可以提及。 最后,生成的相关问题需要与当前主题紧密相关,比如事件分发机制、处理多手指触摸,或者解决滑动冲突的方法,这些都能帮助用户进一步学习。</think>在Android触摸事件处理中,`ACTION_UP``ACTION_CANCEL`都是**事件序列终止信号**,但触发条件应用场景有本质区别。以下是基于Android事件分发机制的深度解析: --- ### 一、核心区别对比 | 特征 | ACTION_UP | ACTION_CANCEL | |--------------------|--------------------------------------|--------------------------------------| | **触发条件** | 用户主动抬起手指 | 系统强制终止事件流(如父视图拦截) | | **事件序列完整性** | 属于完整事件序列(DOWN→MOVE→UP) | 中断事件序列(DOWN→MOVE→CANCEL) | | **业务逻辑影响** | 通常触发点击/长按等操作 | 需要重置View状态(如按钮按下状态的恢复) | | **典型场景** | 按钮点击、滑动停止 | 手势冲突、ViewGroup拦截事件、窗口失去焦点 | --- ### 二、使用场景详解 #### 1. ACTION_UP 的典型场景 **案例1:按钮点击验证** ```java button.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_UP) { performClick(); // 仅当手指在控件区域内抬起才触发点击 } return true; }); ``` - **关键逻辑**:需要配合`ACTION_DOWN`的坐标范围检查,防止手指滑出控件区域后抬起仍触发点击[^4] **案例2:滑动动画停止** ```java @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: mScroller.fling(...); // 抬起时触发惯性滚动 break; } return true; } ``` #### 2. ACTION_CANCEL 的特殊场景 **场景1:父视图拦截事件** 当父`ViewGroup`在`onInterceptTouchEvent()`中返回`true`时: ```mermaid sequenceDiagram 子View->>父ViewGroup: 接收ACTION_DOWN 父ViewGroup->>子View: 发送ACTION_CANCEL 父ViewGroup->>父ViewGroup: 自行处理后续事件 ``` 此时子View必须立即停止所有触摸反馈(如取消按钮按下状态)[^2] **场景2:窗口失去焦点** 当来电提醒/弹窗覆盖当前界面时: $$ \text{Activity窗口失去焦点} \Rightarrow \text{自动发送ACTION_CANCEL给所有持有DOWN事件的View} $$ **场景3:多点触摸冲突** 当第二个手指按下(`ACTION_POINTER_DOWN`)导致事件流被重置时[^5] --- ### 三、开发注意事项 1. **状态恢复强制要求** 收到`ACTION_CANCEL`时必须: ```java public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { setPressed(false); // 取消按下状态 invalidate(); // 重绘界面 } } ``` 2. **坐标验证必要性** ```java // 错误示例:未验证坐标直接处理ACTION_UP if (event.getAction() == MotionEvent.ACTION_UP) { handleClick(); // 可能误触 } // 正确做法:结合DOWN事件坐标 private float mDownX, mDownY; if (event.getAction() == MotionEvent.ACTION_DOWN) { mDownX = event.getX(); mDownY = event.getY(); } else if (event.getAction() == MotionEvent.ACTION_UP) { if (Math.abs(event.getX() - mDownX) < touchSlop && Math.abs(event.getY() - mDownY) < touchSlop) { handleClick(); } } ``` 3. **嵌套滚动冲突解决** 在`ScrollView`嵌套可滑动控件时: ```java @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { // 当子View无法继续滑动时,父容器发送ACTION_CANCEL sendCancelEvent(target); } ``` --- ### 四、实验验证代码 ```java public class TouchDebugView extends View { private static final String TAG = "TouchDebug"; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "DOWN at ("+event.getX()+","+event.getY()+")"); return true; case MotionEvent.ACTION_UP: Log.d(TAG, "UP at ("+event.getX()+","+event.getY()+")"); break; case MotionEvent.ACTION_CANCEL: Log.w(TAG, "CANCEL received!"); break; } return super.onTouchEvent(event); } } ``` **测试结果**: - 正常点击:`DOWN → UP` - 滑动到其他控件区域抬起:`DOWN → MOVE... → CANCEL` - 被父视图拦截:`DOWN → CANCEL` --- ### 五、扩展:手势冲突解决方案 当需要同时处理多种手势时: 1. 使用`GestureDetector`区分单击/长按/滑动 2. 通过`requestDisallowInterceptTouchEvent()`阻止父容器拦截 3. 在`onInterceptTouchEvent()`中动态判断是否需要拦截[^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值