ps:由于现在这个公众号没有留言功能,如有其他错误、建议、疑问,可发至到邮箱[email protected],我会将错误的内容更正
ps:源码是基于 android api 26 来分析的,demo 是用 kotlin 语言写的
前面写了一篇Android中View事件的分发第一篇对事件分发的一些源码进行分析以及写 demo 进行演示验证结论;这一篇文章还对事件的分发进一步的学习与探索。
一个事件序列是指从手指触摸屏幕的那时候起,到手指抬起的那时候结束,这一过程中会产生一系列的事件,事件序列最终以 down 事件开始,最终到 up 事件结束,中途会有 N 个 move 事件;在Android中View事件的分发第一篇这一篇文章中曾演示过,所以这里就不再进行演示。
一般情况下,一个事件序列只能被-个 View 拦截并且处理,一个事件序列中的事件是由一个 View 处理的,一个 View 元素如果拦截了 down 事件,那么同一个事件序列内的 move 等所有事件都会直接交给这个 View 处理,而且这个 View 的 onInterceptTouchEvent 方法只会被调用一次。下面我们举个例子(案例A);
案例A
(1)新建一个 kt 语言的类 MyView 并继承与 View:
class MyView : View {
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.d(A_Activity.TAG, "-----MyView--onTouchEvent")
return false
}
}
(2)新建一个 kt 语言的类 MyLinearLayout 并继承于 LinearLayout:
class MyLinearLayout: LinearLayout {
constructor(context: Context): super(context) {
}
constructor(context: Context,attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context,attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(A_Activity.TAG,"-----MyLinearLayout--onTouchEvent--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(A_Activity.TAG,"-----MyLinearLayout--onTouchEvent--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(A_Activity.TAG,"-----MyLinearLayout--onTouchEvent--ACTION_UP")
}
}
return true
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_DOWN) {
Log.d(A_Activity.TAG,"-----MyLinearLayout----onInterceptTouchEvent")
return true
} else if (ev?.action == MotionEvent.ACTION_MOVE) {
Log.d(A_Activity.TAG,"-----MyLinearLayout----onInterceptTouchEvent")
}
return false
}
}
(3)新建一个 kt 语言类型的 Activity,名字叫 A_Activity:
class A_Activity : AppCompatActivity() {
companion object {
var TAG: String = "A_Activity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a_)
}
}
(4)A_Activity 对应的布局文件为 activity_a_.xml:
程序一开始运行时,界面如下所示:
当我们触摸绿色的区域时,便打印如下日志:
07-21 09:28:53.559 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout----onInterceptTouchEvent
07-21 09:28:53.560 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_DOWN
07-21 09:28:53.788 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:53.804 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:53.821 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:54.021 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:54.048 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_UP
这时候发现 MyView 的 onTouchEvent方法没有被调用,在这个案例中,MyView 作为子元素,MyLinearLayout 作为父元素,MyLinearLayout 重写 onInterceptTouchEvent 方法并在 down 事件中返回了 true 就相当于对子元素 MyView 的事件进行拦截并该方法只调用了一次。
如果 View 不消费除 down 以外的其他事件,父元素也不消费除 down 以外的其他事件,那么这个点击事件就会消失,这些消失的点击事件最终会传递给 Activity 处理。好,下面我们来写一个 demo 来验证上面的这句话(案例B);
案例B
(1)新建一个 kt 语言的类 MyView2 并继承与 View:
class MyView2 : View {
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(B_Activity.TAG,"-----MyView2--onTouchEvent--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(B_Activity.TAG,"-----MyView2--onTouchEvent--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(B_Activity.TAG,"-----MyView2--onTouchEvent--ACTION_UP")
}
}
return super.onTouchEvent(event)
}
}
(2)新建一个 kt 语言的类 MyLinearLayout2 并继承于 LinearLayout:
class MyLinearLayout2: LinearLayout {
constructor(context: Context): super(context) {
}
constructor(context: Context, attrs: AttributeSet): super(context,attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(B_Activity.TAG,"-----MyLinearLayout2--onTouchEvent--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(B_Activity.TAG,"-----MyLinearLayout2--onTouchEvent--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(B_Activity.TAG,"-----MyLinearLayout2--onTouchEvent--ACTION_UP")
}
}
return super.onTouchEvent(event)
}
}
(3)新建一个 kt 语言类型的 Activity,名字叫 B_Activity:
class B_Activity : AppCompatActivity() {
companion object {
var TAG: String = "B_Activity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b_)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(TAG,"-----B_Activity--onTouchEvent--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(TAG,"-----B_Activity--onTouchEvent--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(TAG,"-----B_Activity--onTouchEvent--ACTION_UP")
}
}
return super.onTouchEvent(event)
}
}
(4)B_Activity 对应的布局文件为 activity_b_.xml:
程序一开始运行的界面如下所示:
然后我们用手指触摸绿色的区域,打印如下日志:
07-22 13:23:52.375 26535-26535/com.xe.eventdemo2 D/B_Activity: -----MyView2--onTouchEvent--ACTION_DOWN
07-22 13:23:52.375 26535-26535/com.xe.eventdemo2 D/B_Activity: -----MyLinearLayout2--onTouchEvent--ACTION_DOWN
07-22 13:23:52.376 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_DOWN
07-22 13:23:52.503 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.686 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.703 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.820 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.853 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_UP
真的像上面结论总结的一样,我们的案例中的 MyView2 和 父元素 MyLinearLayout2 的触摸事件只消费 down 事件时,其他事件会给 Activity 来处理。
ViewGroup 默认是不拦截事件的,也就是 ViewGroup 的onInterceptTouch-Event 方法返回值是 false。我们来看看 ViewGroup 的 onInterceptTouchEvent 方法;
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
发现该方法返回的默认值是 false,只有在事件源类型是鼠标并且是 down 事件是鼠标点击按钮和是滚动条的手势时才返回 true。所以默认一般 ViewGroup 的 onInterceptTouchEvent 方法返回都为 false,也就是说默认不拦截事件。
View 没有 onInterceptTouchEvent 方法,如果有事件传递给它,也就是 dispatchTouchEvent 方法被调用,那么它的 onTouchEvent 方法就会被调用(如果没有 onTouch 方法,或者 onTouch 方法返回值为 false 的条件下)。我们来看看 View 的 dispatchTouchEvent 方法;
public boolean dispatchTouchEvent(MotionEvent event) {
......
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
......
return result;
}
发现 View 的 dispatchTouchEvent 方法有调用 onTouchEvent 方法。
View 的 onClick 方法会执行的前提是当前 View 是可点击的(也就是 clickable 和 enabled 属性只要有一个为 true),并且它收到了 down 和 up 的事件;如果重写了 View 的 onTouchEvent 方法,那么 down 和 up 事件的返回值必须为 super.onTouchEvent(event),才会调用 View 的 onClick 方法,具体什么原因可以看Android中View事件的分发第一篇,下面我们写一个 demo 来验证这点结论(案例C);
案例C
(1)新建一个 kt 语言的类 MyView3 并继承与 View:
class MyView3: View {
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_MOVE")
return true
}
MotionEvent.ACTION_UP -> {
Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_UP")
}
}
return super.onTouchEvent(event)
}
}
(2)新建一个 kt 语言类型的 Activity,名字叫 C_Activity:
class C_Activity : AppCompatActivity() {
companion object {
var TAG: String = "C_Activity";
}
var mMyView3: MyView3? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_c_)
mMyView3 = findViewById(R.id.my_view3);
mMyView3!!.isEnabled = true
mMyView3!!.isClickable = false
mMyView3!!.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.d(TAG,"--C_Activity *** onClick--")
}
})
}
}
(3)C_Activity 对应的布局文件为 activity_c_.xml:
程序一开始运行的界面如下所示:
当我们用手指触摸时,日志打印如下所示,可以看到 onClick 方法被调用了:
07-26 08:58:33.206 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_DOWN
07-26 08:58:33.303 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.620 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.637 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.670 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.754 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_UP
07-26 08:58:33.758 5056-5056/com.xe.eventdemo2 D/C_Activity: --C_Activity *** onClick--
当我们把 mMyView3 都改一下,也就是 mMyView3!!.isEnabled = false,mMyView3!!.isClickable = false,然后在运行程序用手触摸屏幕,日志打印如下所示,发现 onClick 方法没有被调用:
07-26 09:03:16.440 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_DOWN
07-26 09:03:16.525 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:03:16.776 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:03:16.851 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_UP
当我们把 mMyView3 都改一下,也就是 mMyView3!!.isEnabled = true,mMyView3!!.isClickable = false,然后再改一下 MyView3 的 down 事件的返回值为 true,代码如下修改:
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_DOWN")
return true
}
MotionEvent.ACTION_MOVE -> {
Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_MOVE")
return true
}
MotionEvent.ACTION_UP -> {
Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_UP")
}
}
return super.onTouchEvent(event)
}
程序运行后,用手触摸屏幕,日志打印如下,发现也是没有调用 onClick 方法:
07-26 09:07:43.443 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_DOWN
07-26 09:07:43.574 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:07:43.894 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:07:43.896 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_UP
本篇文章写到这里就结束了,由于技术水平有限,文章中难免会有错误,欢迎大家批评指正,另外附上demo地址:https://github.com/Yubao1/EventDemo2.git
关注微信公众号,阅读更多文章