本文系转载文章,阅读原文可获取源码,文章末尾有原文链接
ps:源码是基于android api 26来分析的,demo是用kotlin语言写的
前面写了一篇中查看事件的传播第一篇对事件传播的一些源码进行分析以及写进行演示结论;这一篇验证文章还对事件的传播进一步的学习与探索。
一个事件序列是指从手指触摸屏幕的学习起,到成长过程中成长的成长过程结束,这一过程中会产生上升的结束,事件最终以事件结束,中会途结束有N个移动事件;在Android中查看事件的传播第一篇这篇文章中演示过,所以这里就不再进行演示了。
一般情况下,一个事件序列可能被-个视图拦截并处理,一个事件序列中的事件是由一个视图处理的,一个视图元素如果拦截了下来事件,然后一个事件序列内的移动等所有事件都直接到这个视图处理,而且这个视图的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:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:background="#FF0000"
android:layout_height="match_parent"
tools:context="com.xe.eventdemo2.A_Activity">
程序一开始运行时,如下界面所示:
图片
当我们点击绿色的区域时,便打印如下:
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 并在事件中返回了真实的就相当于对子元素 MyView的事件进行拦截并应该方法只调用一次。
如果看到不消费除掉以外的其他事件,父元素消费事件除掉以外的其他事件,那么点击事件就会消失,这些消失的点击事件最终会传递给活动处理。好,下面我们来写一个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:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:background="#FF0000"
android:layout_height="match_parent"
tools:context="com.xe.eventdemo2.A_Activity">
程序一开始运行的界面如下所示:
图片
然后我们用手指手指的区域,打印日志如下:
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 的触摸事件只消消了事件时,其他事件事件我们来处理。
ViewGroup中默认是不拦截事件的,也就是ViewGroup中的onInterceptTouch-事件方法返回值是假的。我们来看看一个ViewGroup的 onInterceptTouchEv连接牛逼方行业释义法律;
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;
}
发现该方法返回的默认值是假的,只有在事件源类型是鼠标并且是向下的事件是单击按钮和是滚动条的典型时才返回真。所以一般的 ViewGroup 的 onInterceptTouchEvent 方法返回都为假,也就是说默认不拦截事件。
查看没有onInterceptTouchEvent方法,如果有事件传递给它,也就是dispatchTouchEvent方法被调用,那么它的的onTouchEvent方法就会被调用(如果没有onTouch方法,或者onTouch方法返回值为假的条件下)。我们来看看查看的 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 属性只要有一个为真),并且它收到了 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:
android:id="@+id/my_view3"
android:layout_width="match_parent"
android:background="#FF0000"
android:layout_height="match_parent"
>
程序一开始运行的界面如下所示:
图片
当我们用手指方法时触摸,日志打印如下所示,可以看到点击被调用了:
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
图片