本文系转载文章,阅读原文可获取源码,文章末尾有原文链接
ps:demo 是基于 kotlin 语言来写的,代码是基于 Android Api 26 分析的
前面写了2篇的Android中查看事件分发的一些源码分析,演示演示和总结一些结论,这一篇接着写 的Android中V IEW 事件配给物的结论。
查看的onTouchEvent默认不会超时事件,即它的返回值为false,我们查看一下查看中的onTouchEvent方法源码:
public boolean onTouchEvent(MotionEvent event) {
......
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;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
......
return true;
}
return false;
}
看这个方法的结果,发现返回是假的,可以看到的onTouchEvent 方法默认返回值是假的,写一个演示验证一下我们(案例D):
案例D
(1)新建一个 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 b: Boolean = super.onTouchEvent(event)
Log.d(D_Activity.TAG,"MyView----" + b)
return b
}
}
(2)新建一个 kotlin 类型的 Activity,名为 D_Activity 并继承于 AppCompatActivity:
class D_Activity : AppCompatActivity() {
companion object {
var TAG: String = "D_Activity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_d_)
}
}
(3)D_Activity对应的图文文件activity_d_如下所示:
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#FF0000"
android:layout_height="match_parent"
>
程序一开始运行的界面如下所示:
图片
用手触摸一下屏幕,打印如下:
07-27 13:23:07.878 19660-19660/com.xe.eventdemo3 D/D_Activity: MyView----false
View 的 onTouchEvent 方法默认返回验证了这值为 false。
如果有2个视图有重叠且父元素不拦截事件,都要做向下处理的事件,也就是消费移动、向上等事件,那么最上面的视图有可能接收到事件处理,被覆盖的视图就接收不到到事件;如果在上面的视图不消费所有事件,那么就在下面的视图消费包含的事件,那么在该视图下面的视图就会接收到事件处理。这有点不太好理解,我们举个例子(案例E):
案例E
(1)新建一个 kotlin 语言的类 MyView2 并继承于 View:
class MyView2: 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 {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(E_Activity.TAG,"--MyView2--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(E_Activity.TAG,"--MyView2--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(E_Activity.TAG,"--MyView2--ACTION_UP")
}
}
return true
}
}
(2)新建一个 kotlin 语言的类 MyView3 并继承于 View:
class MyView3: 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 {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(E_Activity.TAG,"--MyView3--ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d(E_Activity.TAG,"--MyView3--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(E_Activity.TAG,"--MyView3--ACTION_UP")
}
}
return true
}
}
(3)新建一个kotlin类型的Activity,名叫E_Activity:
class E_Activity : AppCompatActivity() {
companion object {
var TAG: String = "E_Activity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_e_)
}
}
(4)E_Activity对应的布局文件activity_e_:
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#00FF00"
android:layout_width="match_parent"
android:layout_height="match_parent">
程序一开始运行的界面如下所示:
图片
当我们用手指触摸蓝色区域时,打印日志如下:
07-28 13:20:26.400 4554-4554/com.xe.eventdemo3 D/E_Activity: --MyView3--ACTION_DOWN
07-28 13:20:26.485 4554-4554/com.xe.eventdemo3 D/E_Activity: --MyView3--ACTION_MOVE
07-28 13:20:26.837 4554-4554/com.xe.eventdemo3 D/E_Activity: --MyView3--ACTION_MOVE
07-28 13:20:26.935 4554-4554/com.xe.eventdemo3 D/E_Activity: --MyView3--ACTION_UP
首先activity_e_.xml 这个文件的布局采用的是FrameLayout,它的布局样式是,子视图如果不添加控制位置的属性,一般默认设置在FrameLayout的左上角,再添加的子视图前面添加的子视图给覆盖掉,也就是添加的子视图会看到前面添加子视图的上面;MyView2是一开始就添加的,背景颜色是红色,而MyView3是添加的,颜色是蓝色,MyView3会MyView2 的上面;从日志打印出来,MyView2 理所当然的没有接收到任何事件,而 MyView3 接收到了向下、移动、向上事件。
我们想改一下,把MyView3的onTouchEvent方法的返回值改为假,再运行程序日志,再用手指触摸蓝色的区域,打印如下所示:
07-28 13:48:19.467 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView3--ACTION_DOWN
07-28 13:48:19.468 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_DOWN
07-28 13:48:19.644 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_MOVE
07-28 13:48:19.828 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_MOVE
07-28 13:48:19.845 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_MOVE
07-28 13:48:19.878 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_MOVE
07-28 13:48:19.895 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_MOVE
07-28 13:48:19.962 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_MOVE
07-28 13:48:20.122 9254-9254/com.xe.eventdemo3 D/E_Activity: --MyView2--ACTION_UP
从日志,MyView2 的TouchEvent 方法被调用了并进行了事件处理,是因为位于MyView2 之上的MyView3 没有处理事件,所以MyView2 的父元素引发事件给了MyView2。
在事件传播的过程中,如果子视图处理了事件(已经处理了事件,就是 onTouchEvent 的返回值为 true),那么父元素 ViewGroup 的 onTouchEvent 方法一次都不会调用;如果子视图和父元素 ViewGroup 都没有处理事件,那么他们都会执行下来事件,而且还是子视图的返回如果优先执行,但它们的值仍然是假的;子视图接收到了下来事件,没有消费,而父元素视图组进行了消费,那么子查看的羽绒还是比父元素的羽绒事件优先执行。
我们先来看看ViewGroup的dispatchTouchEvent方法的一段源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
......
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
......
}
从ViewGroup的dispatchTouchEvent方法的方法的触发事件可以触发事件,触发事件是通过disallowIntercept变量和onInterceptTouchEvent方法控制的,触发事件无法拦截;传递过程是由外向内的即事件总是先传递给父元素ViewGroup,然后再由父元素ViewGroup传播给子View,子View通过getParent().requestDisallowInterceptTouchEvent(值参数) 属性可以在子元素中引入父元素的事件传播过程,下来事件除外。为了更好的理解,我们来举个例子(案例F):
案例F
(1)新建一个 kotlin 的类 MyView4 并继承于 View(这里的 parent.requestDisallowInterceptTouchEvent(true) 语句等同于 Java 的 getParent().requestDisallowInterceptTouchEvent(true) 语句:
class MyView4: 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 {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(F_Activity.TAG,"--MyView4--onTouchEvent--ACTION_DOWN")
super.onTouchEvent(event)
parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
Log.d(F_Activity.TAG,"--MyView4--onTouchEvent--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(F_Activity.TAG,"--MyView4--onTouchEvent--ACTION_UP")
}
}
return true
}
}
(2)新建一个 kotlin 语言的类 MyFrameLayout 并继承于 FrameLayout:
class MyFrameLayout: FrameLayout {
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 onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(F_Activity.TAG,"--MyFrameLayout--onInterceptTouchEvent--ACTION_DOWN")
super.onInterceptTouchEvent(ev)
return false
}
MotionEvent.ACTION_MOVE -> {
Log.d(F_Activity.TAG,"--MyFrameLayout--onInterceptTouchEvent--ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d(F_Activity.TAG,"--MyFrameLayout--onInterceptTouchEvent--ACTION_UP")
}
}
return true
}
}
(3)新建一个kotlin语言的Activity,名为F_Activity并继承于AppCompatActivity:
class F_Activity : AppCompatActivity() {
companion object {
var TAG: String = "F_Activity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_f_)
}
}
(4)F_Activity对应的布局文件activity_f_.xml如下所示:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
tools:context="com.xe.eventdemo3.F_Activity">
程序一开始运行的界面如下所示:
图片
当我们用手触摸蓝色区域的时候,打印日志如下所示:
07-30 13:33:33.619 27770-27770/? D/F_Activity: --MyFrameLayout--onInterceptTouchEvent--ACTION_DOWN
07-30 13:33:33.620 27770-27770/? D/F_Activity: --MyView4--onTouchEvent--ACTION_DOWN
07-30 13:33:33.696 27770-27770/? D/F_Activity: --MyView4--onTouchEvent--ACTION_MOVE
07-30 13:33:33.764 27770-27770/? D/F_Activity: --MyView4--onTouchEvent--ACTION_MOVE
07-30 13:33:33.780 27770-27770/? D/F_Activity: --MyView4--onTouchEvent--ACTION_MOVE
07-30 13:33:33.974 27770-27770/? D/F_Activity: --MyView4--onTouchEvent--ACTION_MOVE
07-30 13:33:33.975 27770-27770/? D/F_Activity: --MyView4--onTouchEvent--ACTION_UP
从录音,MyView4请求MyFrameLayout不拦截事件成功,我们结合上面ViewGroup的dispatchTouchEvent方法源码进行分析;
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
当 MyFrameLayout 的 onInterceptTouchEvent 不对向下事件进行拦截时方法,MyView4 接收到向下事件并调用 parent.requestDisallowInterceptTouchEvent(true) 语句让第二次调用 ViewGroup 的 dispatchTouchEvent 方法中的 disallowIntercept 为 true,从而让 MyFrameLayout 的 onInterceptTouchEvent 无法再次调用,所以也让截取的值为假。
我们把parent.requestDisInterceptTouchEvent(true)语句的参数改为false 再运行程序,用手指再触摸蓝色的区域,打印日志:
07-30 13:55:01.733 31063-31063/? D/F_Activity: --MyFrameLayout--onInterceptTouchEvent--ACTION_DOWN
07-30 13:55:01.733 31063-31063/? D/F_Activity: --MyView4--onTouchEvent--ACTION_DOWN
07-30 13:55:01.786 31063-31063/? D/F_Activity: --MyFrameLayout--onInterceptTouchEvent--ACTION_MOVE
这里的parent.DisallowInterceptTouchEvent(false)语句的参数为false和不写该语句的效果是的,都是请求方法了ViewGroup的dispatchTouchEvent中的disallowIntercept 是false,手指次移动的过程中,加上第二调用MyFrameLayout 的 onInterceptTouchEvent 方法并且它的返回值为 true,也就是 move 事件的返回值为 true,所以 MyView4 的 move 事件被拦截了,所以 MyView4 的 move 事件没有打印日志。