请选择 进入手机版 | 继续访问电脑版

[Android] Android中ACTION_CANCEL的触发机制与滑出子view的环境

[复制链接]
查看150 | 回复28 | 2021-9-14 05:18:31 | 显示全部楼层 |阅读模式
目次

看完本文你将相识 :

  • ACTION_CANCEL的触发机遇
  • 滑出子View地区 会发生什么?为什么不相应 onClick()变乱

起首 看一下官方的表明 :

  1. /**
  2. * Constant for {@link #getActionMasked}: The current gesture has been aborted.
  3. * You will not receive any more points in it. You should treat this as
  4. * an up event, but not perform any action that you normally would.
  5. */
  6. public static final int ACTION_CANCEL = 3;
复制代码

说人话就是:当前的手势被制止 了,你不会再收到任何变乱 了,你可以把它当做一个ACTION_UP变乱 ,但是不要实行 正常环境 下的逻辑。

ACTION_CANCEL的触发机遇

有四种环境 会触发

  1. ACTION_CANCEL
复制代码
:

  • 在子View处理变乱 的过程中,父View对变乱 拦截
  • ACTION_DOWN初始化操作
  • 在子View处理变乱 的过程中被从父View中移除时
  • 子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时

1,父view拦截变乱

起首 要相识 ViewGroup什么环境 下会拦截变乱 ,Look the Fuck Resource Code:

  1. /**
  2. * {@inheritDoc}
  3. */
  4. @Override
  5. public boolean dispatchTouchEvent(MotionEvent ev) {
  6. ...
  7. boolean handled = false;
  8. if (onFilterTouchEventForSecurity(ev)) {
  9. final int action = ev.getAction();
  10. final int actionMasked = action & MotionEvent.ACTION_MASK;
  11. ...
  12. // Check for interception.
  13. final boolean intercepted;
  14. // 判断条件一
  15. if (actionMasked == MotionEvent.ACTION_DOWN
  16. || mFirstTouchTarget != null) {
  17. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  18. // 判断条件二
  19. if (!disallowIntercept) {
  20. intercepted = onInterceptTouchEvent(ev);
  21. ev.setAction(action); // restore action in case it was changed
  22. } else {
  23. intercepted = false;
  24. }
  25. } else {
  26. // There are no touch targets and this action is not an initial down
  27. // so this view group continues to intercept touches.
  28. intercepted = true;
  29. }
  30. ...
  31. }
  32. ...
  33. }
复制代码

有两个条件

  • MotionEvent.ACTION_DOWN变乱 或者mFirstTouchTarget非空也就是有子view在处理变乱
  • 子view没有做拦截,也就是没有调用
    1. ViewParent#requestDisallowInterceptTouchEvent(true)
    复制代码

假如 满足 上面的两个条件才会实行

  1. onInterceptTouchEvent(ev)
复制代码

假如 ViewGroup拦截了变乱 ,则
  1. intercepted
复制代码
变量为true,接着往下看:

  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent ev) {
  3. boolean handled = false;
  4. if (onFilterTouchEventForSecurity(ev)) {
  5. ...
  6. // Check for interception.
  7. final boolean intercepted;
  8. if (actionMasked == MotionEvent.ACTION_DOWN
  9. || mFirstTouchTarget != null) {
  10. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  11. if (!disallowIntercept) {
  12. // 当mFirstTouchTarget != null,也就是子view处理了事件
  13. // 此时如果父ViewGroup拦截了事件,intercepted==true
  14. intercepted = onInterceptTouchEvent(ev);
  15. ev.setAction(action); // restore action in case it was changed
  16. } else {
  17. intercepted = false;
  18. }
  19. } else {
  20. // There are no touch targets and this action is not an initial down
  21. // so this view group continues to intercept touches.
  22. intercepted = true;
  23. }
  24. ...
  25. // Dispatch to touch targets.
  26. if (mFirstTouchTarget == null) {
  27. ...
  28. } else {
  29. // Dispatch to touch targets, excluding the new touch target if we already
  30. // dispatched to it. Cancel touch targets if necessary.
  31. TouchTarget predecessor = null;
  32. TouchTarget target = mFirstTouchTarget;
  33. while (target != null) {
  34. final TouchTarget next = target.next;
  35. if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
  36. ...
  37. } else {
  38. // 判断一:此时cancelChild == true
  39. final boolean cancelChild = resetCancelNextUpFlag(target.child)
  40. || intercepted;
  41. // 判断二:给child发送cancel事件
  42. if (dispatchTransformedTouchEvent(ev, cancelChild,
  43. target.child, target.pointerIdBits)) {
  44. handled = true;
  45. }
  46. ...
  47. }
  48. ...
  49. }
  50. }
  51. ...
  52. }
  53. ...
  54. return handled;
  55. }
复制代码

以上判断 一处

  1. cancelChild
复制代码
为true,然后进入判断 二中一看毕竟 :

  1. private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
  2. View child, int desiredPointerIdBits) {
  3. final boolean handled;
  4. // Canceling motions is a special case. We don't need to perform any transformations
  5. // or filtering. The important part is the action, not the contents.
  6. final int oldAction = event.getAction();
  7. if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
  8. // 将event设置成ACTION_CANCEL
  9. event.setAction(MotionEvent.ACTION_CANCEL);
  10. if (child == null) {
  11. ...
  12. } else {
  13. // 分发给child
  14. handled = child.dispatchTouchEvent(event);
  15. }
  16. event.setAction(oldAction);
  17. return handled;
  18. }
  19. ...
  20. }
复制代码

当参数cancel为ture时会将event设置为MotionEvent.ACTION_CANCEL,然后分发给child。

2,ACTION_DOWN初始化操作

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. boolean handled = false;
  3. if (onFilterTouchEventForSecurity(ev)) {
  4. final int action = ev.getAction();
  5. final int actionMasked = action & MotionEvent.ACTION_MASK;
  6. // Handle an initial down.
  7. if (actionMasked == MotionEvent.ACTION_DOWN) {
  8. // Throw away all previous state when starting a new touch gesture.
  9. // The framework may have dropped the up or cancel event for the previous gesture
  10. // due to an app switch, ANR, or some other state change.
  11. // 取消并清除所有的Touch目标
  12. cancelAndClearTouchTargets(ev);
  13. resetTouchState();
  14. }
  15. ...
  16. }
  17. ...
  18. }
复制代码

体系 大概 会由于App切换、ANR等缘故原由 丢失了up,cancel变乱 。

因此必要 在ACTION_DOWN时丢弃掉全部 前面的状态,详细 代码如下:

  1. private void cancelAndClearTouchTargets(MotionEvent event) {
  2. if (mFirstTouchTarget != null) {
  3. boolean syntheticEvent = false;
  4. if (event == null) {
  5. final long now = SystemClock.uptimeMillis();
  6. event = MotionEvent.obtain(now, now,
  7. MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
  8. event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
  9. syntheticEvent = true;
  10. }
  11. for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
  12. resetCancelNextUpFlag(target.child);
  13. // 分发事件同情况一
  14. dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
  15. }
  16. ...
  17. }
  18. }
复制代码

PS:在

  1. dispatchDetachedFromWindow()
复制代码
中也会调用
  1. cancelAndClearTouchTargets()
复制代码

3,在子View处理变乱 的过程中被从父View中移除时

  1. public void removeView(View view) {
  2. if (removeViewInternal(view)) {
  3. requestLayout();
  4. invalidate(true);
  5. }
  6. }
  7. private boolean removeViewInternal(View view) {
  8. final int index = indexOfChild(view);
  9. if (index >= 0) {
  10. removeViewInternal(index, view);
  11. return true;
  12. }
  13. return false;
  14. }
  15. private void removeViewInternal(int index, View view) {
  16. ...
  17. cancelTouchTarget(view);
  18. ...
  19. }
  20. private void cancelTouchTarget(View view) {
  21. TouchTarget predecessor = null;
  22. TouchTarget target = mFirstTouchTarget;
  23. while (target != null) {
  24. final TouchTarget next = target.next;
  25. if (target.child == view) {
  26. ...
  27. // 创建ACTION_CANCEL事件
  28. MotionEvent event = MotionEvent.obtain(now, now,
  29. MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
  30. event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
  31. 分发给目标view
  32. view.dispatchTouchEvent(event);
  33. event.recycle();
  34. return;
  35. }
  36. predecessor = target;
  37. target = next;
  38. }
  39. }
复制代码

4,子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时

在环境 一种的两个判断 处:

  1. // 判断一:此时cancelChild == true
  2. final boolean cancelChild = resetCancelNextUpFlag(target.child)
  3. || intercepted;
  4. // 判断二:给child发送cancel事件
  5. if (dispatchTransformedTouchEvent(ev, cancelChild,
  6. target.child, target.pointerIdBits)) {
  7. handled = true;
  8. }
复制代码

  1. resetCancelNextUpFlag(target.child)
复制代码
为true时同样也会导致cancel,查看代码:

  1. /**
  2. * Indicates whether the view is temporarily detached.
  3. *
  4. * @hide
  5. */
  6. static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000;
  7. private static boolean resetCancelNextUpFlag(View view) {
  8. if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
  9. view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
  10. return true;
  11. }
  12. return false;
  13. }
复制代码

根据解释 大概意思是,该view暂时 detached,detached是什么意思?就是和attached相反的谁人 ,详细 什么时间 打了这个标记,我以为 没必要穷究 。

以上四种环境 最告急 的就是第一种,后面的只需相识 即可。

滑出子View地区 会发生什么?

相识 了什么环境 下会触发

  1. ACTION_CANCEL
复制代码
,那么针对标题 :滑出子View地区 会触发
  1. ACTION_CANCEL
复制代码
吗?这个标题 就很明白 了:不会。

实践是检验真理的唯一标准,代码撸起来:

  1. public class MyButton extends androidx.appcompat.widget.AppCompatButton {
  2. @Override
  3. public boolean onTouchEvent(MotionEvent event) {
  4. switch (event.getAction()) {
  5. case MotionEvent.ACTION_DOWN:
  6. LogUtil.d("ACTION_DOWN");
  7. break;
  8. case MotionEvent.ACTION_MOVE:
  9. LogUtil.d("ACTION_MOVE");
  10. break;
  11. case MotionEvent.ACTION_UP:
  12. LogUtil.d("ACTION_UP");
  13. break;
  14. case MotionEvent.ACTION_CANCEL:
  15. LogUtil.d("ACTION_CANCEL");
  16. break;
  17. }
  18. return super.onTouchEvent(event);
  19. }
  20. }
复制代码

一波操作以后日志 如下:

  1. (MyButton.java:32) -->ACTION_DOWN
  2. (MyButton.java:36) -->ACTION_MOVE
  3. (MyButton.java:36) -->ACTION_MOVE
  4. (MyButton.java:36) -->ACTION_MOVE
  5. (MyButton.java:36) -->ACTION_MOVE
  6. (MyButton.java:36) -->ACTION_MOVE
  7. (MyButton.java:39) -->ACTION_UP
复制代码

滑出view后依然可以收到

  1. ACTION_MOVE
复制代码
  1. ACTION_UP
复制代码
变乱 。

为什么有人会以为 滑出view后会收到

  1. ACTION_CANCEL
复制代码
呢?

我想是由于 滑出view后,view的

  1. onClick()
复制代码
不会触发了,以是 有人就以为是触发了
  1. ACTION_CANCEL
复制代码

那么为什么滑出view后不会触发

  1. onClick
复制代码
呢?再来看看View的源码:

在view的

  1. onTouchEvent()
复制代码
中:

  1. case MotionEvent.ACTION_MOVE:
  2. // Be lenient about moving outside of buttons
  3. // 判断是否超出view的边界
  4. if (!pointInView(x, y, mTouchSlop)) {
  5. // Outside button
  6. if ((mPrivateFlags & PRESSED) != 0) {
  7. // 这里改变状态为 not PRESSED
  8. // Need to switch from pressed to not pressed
  9. mPrivateFlags &= ~PRESSED;
  10. }
  11. }
  12. break;
  13. case MotionEvent.ACTION_UP:
  14. boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
  15. // 可以看到当move出view范围后,这里走不进去了
  16. if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
  17. ...
  18. performClick();
  19. ...
  20. }
  21. mIgnoreNextUpEvent = false;
  22. break;
复制代码

1,在

  1. ACTION_MOVE
复制代码
中会判断 变乱 的位置是否超出view的边界,假如 超出边界则将
  1. mPrivateFlags
复制代码
置为
  1. not PRESSED
复制代码
状态。
2,在
  1. ACTION_UP
复制代码
中判断 只有当
  1. mPrivateFlags
复制代码
包含
  1. PRESSED
复制代码
状态时才会实行
  1. performClick()
复制代码
等。
因此滑出view后不会实行
  1. onClick()
复制代码

结论:

  • 滑出view范围后,假如 父view没有拦截变乱 ,则会继续受到
    1. ACTION_MOVE
    复制代码
    1. ACTION_UP
    复制代码
    等变乱 。
  • 一旦滑出view范围,view会被移除
    1. PRESSED
    复制代码
    标记,这个是不可逆的,然后在
    1. ACTION_UP
    复制代码
    中不会实行
    1. performClick()
    复制代码
    等逻辑。

到此这篇关于Android中ACTION_CANCEL的触发机制与滑出子view的环境 的文章就先容 到这了,更多干系 Android ACTION_CANCEL内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的干系 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 123457018 | 2021-9-19 22:51:50 | 显示全部楼层
看了这么多帖子,第一次看到这么高质量内容!
回复

使用道具 举报

avatar 123457682 | 2021-9-20 06:51:12 | 显示全部楼层
帖子好乱!
回复

使用道具 举报

avatar yslzaity | 2021-9-20 19:49:19 | 显示全部楼层
听admin楼主一席话,省我十本书!
回复

使用道具 举报

avatar 无热天龙中 | 2021-9-21 13:01:07 | 显示全部楼层
怎么我回帖都没人理我呢?
回复

使用道具 举报

avatar lj1282502016 | 2021-10-4 10:05:13 | 显示全部楼层
看帖不回帖的人就是耍流氓,我回复了!
回复

使用道具 举报

avatar 123457049 | 2021-10-4 20:24:23 | 显示全部楼层
你觉得该怎么做呢?
回复

使用道具 举报

avatar 山东美家环保 | 2021-10-6 10:30:35 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,运动刷步数还是免费刷的,QQ和微信都可以刷,特别好用。访问地址:http://yd.mxswl.com 猫先森网络
回复

使用道具 举报

avatar 张柏芝一号胸 | 2021-10-6 22:52:08 | 显示全部楼层
很多天不上线,一上线就看到这么给力的帖子!
回复

使用道具 举报

avatar 消敢市音 | 2021-10-8 03:41:40 | 显示全部楼层
看了这么多帖子,第一次看看到这么有内涵的!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则