如何设置view 点击事件不回调,如何实现?有什么区别?
这个方案用于设置view是否可以响应用户的其他交互事件如触摸,轨迹球等。
这个方法用于设置view是否可以响应用户的点击事件。
设置监听,并且表示消费事件。
override fun onTouchEvent(event: MotionEvent?): Boolean {
return true
}
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
return true
}
事件的责任链模式中,在view层只有两个:
但是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 RedundantIfStatement
if ((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) 都没有被执行了。
通过上面的知识点,我们就只剩下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的时候触发:
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相关逻辑 也可以实现点击事件不回调了。
可以看到下面的代码:
isClickable=false
setOnClickListener {
LogUtils.e("setOnClickListener")
}
我们先设置了clickable,又设置了点击事件。但是点击事件可以响应,为什么呢?我们来看下设置点击事件的源码就知道了:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
非常单纯的代码,如果是不可点击,那就设置为可以点击。所以 setClickable(false)得写到设置点击事件之后。
其实,这个逻辑还是蛮简单的,主要是要点一下代码。最终汇总下:
事件分发和绘制原理,还是得懂一下,毕竟现在各个系统打架,懂了,跨平台方案可能学习得快一点吧。
Android 往年面试题锦:
2023年最新Android 面试题集:
Android 车载开发岗位面试习题:
音视频面试题锦:
Android 性能优化篇:
Android Framework底层原理篇:
Android 车载篇:
Android 逆向安全学习笔记:
Android 音视频篇:
Jetpack全家桶篇(内含Compose):
OkHttp 源码解析笔记:
Kotlin 篇:
Gradle 篇:
Flutter 篇:
Android 八大知识体:
Android 核心笔记: