共计 8204 个字符,预计需要花费 21 分钟才能阅读完成。
ps:浏览原文可获取 demo 源码,文章开端有原文链接
ps:源码是基于 android api 27 来剖析的,demo 是用 kotlin 语言写的
在 Android View 事件的散发中,如果 View 的 dispatchTouchEvent 办法被调用,那么它就不会再往下散发,也不会进行拦挡,因为 View 是最底层的元素;在事件散发中,View 是充当子元素的,而不能充当父元素,它的 2 个办法施展着很好的合作能力,那就是 dispatchTouchEvent 办法和 onTouchEvent 办法;首先咱们先看 dispatchTouchEvent 办法:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
//1、// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {return false;}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//2、// Defensive cleanup for new gesture
stopNestedScroll();}
//3、if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//4、if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {result = true;}
//5、if (!result && onTouchEvent(event)) {result = true;}
}
if (!result && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//6、// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {stopNestedScroll();
}
return result;
}
正文 1 示意如果不能取得焦点或者不存在一个 View,那么就不处理事件;正文 2 示意如果咱们点击 View 时,其余的依赖滑动都要先停下;正文 3 示意过滤掉一些不合理的事件,比如说弹出一个 Dialog 把 View 给挡住了;正文 4 示意(1)首先判断监听 ListenerInfo 对象不为 null 且咱们通过 setOnTouchListener 设置了监听,即是实现 OnTouchListener 接口的 onTouch 办法,(2)如果有实现就判断以后的 View 状态是不是 ENABLED(enabled 属性是否为 true),(3)如果实现的 OnTouchListener 的 onTouch 中返回 true,这(1)(2)(3)同时成立示意处理事件并调用;正文 5 示意如果正文 4 的条件不成立,那么执行正文 5 的代码,即触摸事件,阐明 OnTouchListener 的 onTouch 办法优先级比 onTouchEvent 办法的优先级高,在 Android 中 View 事件的散发第一篇这篇文章已验证;正文 6 示意如果这是手势的结尾,则在嵌套滚动后清理。
回过头来看,正文 5 的代码,本篇文章中的另外一个角色 onTouchEvent 办法;
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//7、final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//8、if ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}
}
//9、if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {
case MotionEvent.ACTION_UP:
......
//10、performClick();
......
break;
}
}
return true;
}
return false;
}
正文 8 示意如果是 DISABLED,也就是 View.setEnabled(false), 那么返回正文 7 的布尔值,正文 7 前面再剖析;正文 9 示意如果能够点击或者此视图能够在悬停又或长按时显示工具提醒,那么就返回 true,也就是生产事件;正文 10 示意当手指抬起来的时候会调用 OnClickListener.onClick 办法(能够点击 performClick 办法看看,如果以后的 View 重写了 onTouchEvent 办法,那么它的 up 事件返回值必须是 super.onTouchEvent(event)),如果以后 View 设置有 OnClickListener 事件的话。
正文 7 示意是否能够点击,由以后 View 的 CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE 决定,也就是 clickable、longClickable 和 contextClickable 属性是否为 true;View 的 longClickable 属性默认都为 false,clickable 属性要分状况, 如果是 Button 的 clickable 属性默认为 true, 而 TextView 的 clickable 属性默认为 false。
View(设置有 OnClickListener 事件)的 enable(ENABLED 值)属性不影响 super.onTouchEvent 的默认返回值(返回为 true),即便 View 是 disable(DISABLED 值)状态的,只有它的 clickable 或者 longClickable 或者 contextClickable 属性有一个为 true,那么它的 super.onTouchEvent 就返回 true,然而如果 View 真是 disable(DISABLED 值)状态的并且设置有 OnClickListener 事件,那么 OnClickListener.onClick 办法不会被回调;又如果 View 的 clickable、longClickable 和 contextClickable 属性都为 false,enable 属性为 true 并且设置有 OnClickListener 事件,那么 OnClickListener.onClick 办法会被回调,super.onTouchEvent(event) 的返回值也会为 true。
咱们来验证蓝色文字这一段话,上面咱们来写一个 demo;
(1)新建一个 kotlin 语言类型的 Activity,名叫 MainActivity:
class MainActivity: AppCompatActivity() {
companion object {var TAG: String = "MainActivity"}
var mView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mView = findViewById(R.id.my_view)
mView?.isLongClickable = false
mView?.isContextClickable = false
mView?.isClickable = true
mView?.isEnabled = true
mView?.setOnClickListener(object : View.OnClickListener {override fun onClick(v: View?) {Log.d(TAG,"----onClick----")
}
})
}
}
(2)新建一个 kotlin 语言类型的类 MyView 并继承于 View:
class MyView: View {
constructor(context: Context): super(context) {
}
constructor(context: Context,@Nullable attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context, @Nullable attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}
override fun onTouchEvent(event: MotionEvent?): Boolean {var consume: Boolean = super.onTouchEvent(event)
when(event?.action) {
MotionEvent.ACTION_DOWN -> {Log.d(MainActivity.TAG,"--MotionEvent.ACTION_DOWN--isConsume =" + consume)
}
MotionEvent.ACTION_MOVE -> {Log.d(MainActivity.TAG,"--MotionEvent.ACTION_MOVE--isConsume =" + consume)
}
MotionEvent.ACTION_UP -> {Log.d(MainActivity.TAG,"--MotionEvent.ACTION_UP--isConsume =" + consume)
}
}
return consume
}
}
(3)新建 MainActivity 对应的布局文件 activity_main:
<?xml version=”1.0″ encoding=”utf-8″?>
<com.xe.eventdemo4.MyView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FF00"
tools:context="com.xe.eventdemo4.MainActivity">
</com.xe.eventdemo4.MyView>
程序一开始运行的界面如下所示:
图片
咱们触摸一下绿色的屏幕,日志打印如下所示:
08-24 13:19:23.085 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_DOWN–isConsume = true
08-24 13:19:23.143 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.210 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.227 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.293 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.310 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.460 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:19:23.506 15226-15226/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_UP–isConsume = true
08-24 13:19:23.517 15226-15226/com.xe.eventdemo4 D/MainActivity: —-onClick—-
从日志看出 contextClickable、longClickable 和 clickable 属性只有有一个为 true 时,super.onTouchEvent(event) 就返回为 true。
咱们只把 enable 属性改为 false,即 mView?.isEnabled = true 改为 mView?.isEnabled = false,其余代码不变,而后运行程序,用手指触摸绿色屏幕,日志打印如下所示:
08-24 13:27:31.960 18435-18435/com.xe.eventdemo4 W/Activity: Slow Operation: Activity com.xe.eventdemo4/.MainActivity onCreate took 586ms
08-24 13:27:37.732 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_DOWN–isConsume = true
08-24 13:27:37.807 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:37.858 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:37.875 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:37.958 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:27:38.019 18435-18435/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_UP–isConsume = true
从日志看出 contextClickable、longClickable 和 clickable 属性只有有一个为 true 时,即便 enable 属性为 false,super.onTouchEvent(event) 返回值还是 true,同时 OnClickListener.onClick(该办法被调用的前提是 enable 属性为 true)办法不会被调用。
咱们把 clickable 属性改为 false,enable 属性改为 true,运行程序,用手指触摸绿色屏幕,日志打印如下所示:
08-24 13:56:03.837 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_DOWN–isConsume = true
08-24 13:56:03.949 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:56:04.189 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_MOVE–isConsume = true
08-24 13:56:04.284 20118-20118/com.xe.eventdemo4 D/MainActivity: –MotionEvent.ACTION_UP–isConsume = true
08-24 13:56:04.292 20118-20118/com.xe.eventdemo4 D/MainActivity: —-onClick—-
从日志能够看出 super.onTouchEvent(event) 返回的是 true,OnClickListener.onClick 办法也会被调用。