如何设置view 点击事件不回调,如何实现?有什么区别?
setEnabled(false)
这个方案用于设置view是否可以响应用户的其他交互事件如触摸,轨迹球等。
setClickable(false)
这个方法用于设置view是否可以响应用户的点击事件。
setOnTouchListener{ return true}
设置监听,并且表示消费事件。
直接重写onTouchEvent 不要super相关逻辑
override fun onTouchEvent(event: MotionEvent?): Boolean {return true
}
直接重写 dispatchTouchEvent 不要super 相关逻辑
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {return true
}
dispatchTouchEvent 相关
事件的责任链模式中,在view层只有两个:
- onTouchEvent,返回true 表示消费事件
- dispatchTouchEvent false 表示不分发,自己消费
但是view 层对这两个函数有默认实现。所以我们自定义view的时候,很少全部都放弃super 相关逻辑,这很毒瘤。而且dispatchTouchEvent 作为事件的分发,这个一般不会重写。最多是处理onTouchEvent。
但是setOnTouchListener 的分发则是在dispatchTouchEvent 函数中。在dispatchTouchEvent这里:
if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;
}
if (!result && onTouchEvent(event)) {result = true;
}
当我们onTouch 返回了true,则导致下面if 中前面的条件 !result=false,那么onTouchEvent函数就没有调用了,这也是setOnTouchListener 优先级高于 的原因,所以我们这里解决了为什么 setOnTouchListener{ return true} 和直接重写 dispatchTouchEvent 不要super 相关逻辑点击事件不回调的问题。
在来看一个问题 setEnabled(false) 是可以管控到触摸事件的,我们再来dispatchTouchEvent的代码:
if (onFilterTouchEventForSecurity(event)) {// 上面分发代码在这个里面。}
onFilterTouchEventForSecurity:
public boolean onFilterTouchEventForSecurity(MotionEvent event) {//noinspection RedundantIfStatementif ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {// Window is obscured, drop this touch.return false;}return true;
}
结合 setEnabled() 源码中的部分代码:
setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);
可以看到,对于mViewFlags 赋值成了DISABLED,就变成了:
static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
static final int DISABLED = 0x00000020;
boolean result= (DISABLED&FILTER_TOUCHES_WHEN_OBSCURED)!=0;
导致onFilterTouchEventForSecurity 直接返回false,所以后续的 mOnTouchListener.onTouch 和 onTouchEvent(event) 都没有被执行了。
onTouchEvent 相关
通过上面的知识点,我们就只剩下setClickable 没有开始找为什么了,如果其他的都正确的话,那么我们事件就会传递到onTouchEvent 中。
我们先来看serClickable 源码:
public void setClickable(boolean clickable) {setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
很单纯,设置了一个Flag =CLICKABLE。在OnTouchEvent 中:
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
clickable=false ,就导致if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) 这个循环根本就没有进去。所以说setClickable(false) 最终影响到了 判断的执行。
我们知道点击事件回调是当action=MotionEvent.ACTION_UP的时候触发:
- performClickInternal();
- performClick():
public boolean performClick() {notifyAutofillManagerOnClick();
final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
可以看到。performClick实现内部调用了li.mOnClickListener.onClick(this);而mOnClickListener就是我们设置的点击事件。通过这个逻辑,那么 直接重写onTouchEvent 不要super相关逻辑 也可以实现点击事件不回调了。
setClickable(false) 无效
可以看到下面的代码:
isClickable=false
setOnClickListener {LogUtils.e("setOnClickListener")
}
我们先设置了clickable,又设置了点击事件。但是点击事件可以响应,为什么呢?我们来看下设置点击事件的源码就知道了:
public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener = l;
}
非常单纯的代码,如果是不可点击,那就设置为可以点击。所以 setClickable(false)得写到设置点击事件之后。
总结
其实,这个逻辑还是蛮简单的,主要是要点一下代码。最终汇总下:
- view 的dispatchTouchEvent 有默认实现,当重写后,放弃super,那么直接影响了点击事件和触摸事件等事件的分发,滚动也被影响了。所以点击事件回调就无法触发,因为没有代码调用到点击事件。
- setEnabled 将flag 修改成了DISABLED,导致onFilterTouchEventForSecurity返回了false,所以触摸事件回调和onTouchEvent 事件都没有调用到。而点击事件回调在onTouchEvent 里面。
- setOnTouchListener{ return true} 会导致onTouchEvent 不会被调用,是这么屏蔽的点击事件回调。
- setClickable(false) 也是更改的flag=CLICKABLE,会导致onTouchEvent 中的clickable 等于false,所以事件还没有分发就结束了。
- 重写onTouchEvent,不要super,这种思路还是直接放弃了源码的实现,所以函数也没有地方调用。
事件分发和绘制原理,还是得懂一下,毕竟现在各个系统打架,懂了,跨平台方案可能学习得快一点吧。
Android 学习笔录
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo