Android中View事件的分发第三篇

小二玩编程

本文系转载文章,阅读原文可获取源码,文章末尾有原文链接

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_如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.xe.views.MyView

xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#FF0000"
android:layout_height="match_parent"
>

</com.xe.views.MyView>

程序一开始运行的界面如下所示:

图片

用手触摸一下屏幕,打印如下:

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_:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout

xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#00FF00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xe.views.MyView2
    android:layout_width="400px"
    android:layout_height="400px"
    android:background="#FF0000">
</com.xe.views.MyView2>
<com.xe.views.MyView3
    android:layout_width="200px"
    android:layout_height="200px"
    android:background="#0000FF">
</com.xe.views.MyView3>

</FrameLayout>

程序一开始运行的界面如下所示:

图片

当我们用手指触摸蓝色区域时,打印日志如下:

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如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.xe.views.MyFrameLayout

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">
<com.xe.views.MyView4
    android:layout_width="match_parent"
    android:layout_height="300px"
    android:background="#0000FF"/>

</com.xe.views.MyFrameLayout>

程序一开始运行的界面如下所示:

图片

当我们用手触摸蓝色区域的时候,打印日志如下所示:

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 事件没有打印日志。

阅读 226

活到老,学到老。

1 声望
1 粉丝
0 条评论
你知道吗?

活到老,学到老。

1 声望
1 粉丝
宣传栏