View 的事件分发机制
作者:互联网
所谓事件分发,其实就是对 MotionEvent 事件的分发过程。即一个 MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 。而这个传递的过程就是事件分发。
这个过程有三个很重要的方法来完成,如下
//用来进行事件分发,如果事件能过传递给当前View ,那么此方法一定会被调用。
//返回结果受当前View 的 onTouchEvent 和上下级 View 的dispatchTouchEvent 的影响,表示是否消耗此事件
public boolean dispatchTouchEvent(MotionEvent event)
//用来判断是否拦截某个事件,如果 当前View 拦截了某个事件,那么在同一个事件序列中,此方法不会被调用,
//返回结果表示是否拦截当前事件
public boolean onInterceptTouchEvent(MotionEvent event)
// onTouchEvent 在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件。
//如果不消耗,则在同一个事件序列中,当前View 无法再次接收到当前事件
public boolean onTouchEvent(MotionEvent event)
对于一个 ViewGroup 来说,点击事件产生后,首先会传给ViewGroup。这时他的 dispatchTouchEvent 就会被调用。如果这个ViewGroup 的 onInterceptTouchEvent 返回true ,则表示他要拦截此事件,接着事件就会交给ViewGroup 处理,即他的 onTouchEvent 就会被调用;如果ViewGroup 的 onInterceptTouchEvent 方法返回false,则表示不拦截此事件,这个时候事件就会继续向下传递,接着子View 的 dispatchTouchEvent 就会被调用。如此反复直到事件被处理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
重写此方法 返回 true 后,则表示 事件不在向下传递。接下来会调用 onTouchEvent 方法。onTouchEvent 返回true,则事件被消费。如果返回false ,则当前ViewGroup 无法接收到当前事件。
实验:
布局如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.admin.view_core.viewGroup.MyLinearLayout
android:id="@+id/myLinearlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#9999">
<android.support.v7.widget.AppCompatButton
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp" />
</com.admin.view_core.viewGroup.MyLinearLayout>
</RelativeLayout>
public class MyLinearLayout extends LinearLayout {
private Paint paint;
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context,AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
{
paint = new Paint();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Toast.makeText(getContext(), "onTouchEvent", Toast.LENGTH_SHORT).show();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.parseColor("#ED6F99"));
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(100,100,30,paint);
canvas.drawCircle(300,300,50,paint);
canvas.drawCircle(300,500,80,paint);
canvas.drawCircle(700,300,20,paint);
}
}
1,拦截事件,会发生什么?
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;//拦截事件
}
可以看到onTouchEvent 方法被调用了,说明拦截事件后 交给了 onTouchEvent 去处理了。如果没有拦截,则会向下传递
2, onTouchEvent 返回 true 和 false 有什么区别吗?
我们先给这个 ViewGroup 加一个 点击事件
findViewById(R.id.myLinearlayout).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "onClick", Toast.LENGTH_SHORT).show();
}
});
下面在看 返回 true 和 返回 false 的区别
- 返回true,(ViewGroup不拦截事件)。
@Override
public boolean onTouchEvent(MotionEvent event) {
Toast.makeText(getContext(), "onTouchEvent", Toast.LENGTH_SHORT).show();
return true;
}
如果我们点击的 是 ViewGroup ,则onTouchEvent 消费掉事件,并且 onClick 不会执行,如果点击的是 ViewGroup 中的Button ,则 onTouchEvent 不会执行。
由此可得,当设置拦截器后 onTouchEvent 一定会执行,如果没有设置拦截器 且 点击的刚好是 ViewGroup ,则 onTouchEvent 也会执行,如果点击的是 ViewGroup 的子View。则onTouchEvent 不会执行。且 当 onTouchEvent 返回 true 时,ViewGroup 的onClick事件也没有执行。
-
返回false,(不拦截事件)。
同上,如果点击的是 子View 则继续向下传递,否则 ViewGroup 无法接收当前事件(没有消费此事件)。则该事件就会向上传递()。且 ViewGroup 的点onClick件 也没有执行。
-
返回 super。
点击事件执行,且onTouchEvent 消费了此事件,这个事件到这里结束。
当一个View 需要事件处理时,如果他设置了 OnTouchListener 。则 OnTouchListener 中的 onTouch 方法会被回调。这个事件如何处理则需要看 onTouch 的返回值。如果返回 false。则当前 View 的 onTouchEvent 会被调用。如果返回true,则不会被调用。由此可见 OnTouchListener 的优先级 比 OnTouchEvent 的优先级高。
注意:View 没有 拦截事件的方法!
实验:
布局如下
<com.admin.view_core.viewGroup.MyRelative xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.admin.view_core.view.CustomVolumeControlBar
android:id="@+id/controlBar"
android:layout_width="150dp"
android:layout_height="150dp"
app:secondColor="#000000"
app:circleWidth="10dp"
app:splitSize="20"
app:dotCount="10"
app:firstColor="#999999"
app:bg="@drawable/ic_launcher"/>
</com.admin.view_core.viewGroup.MyRelative>
/**
* @author Lv
* Created at 2019/6/5
*/
public class CustomVolumeControlBar extends View
{
......
public CustomVolumeControlBar(Context context) {
this(context, null);
}
public CustomVolumeControlBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomVolumeControlBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
......
mPaint = new Paint();
mRect = new Rect();
rect = new Rect();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
canvas.drawBitmap(mImage, null, mRect, mPaint);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("---------------", "onTouchEvent: " );
return true;
}
}
由于代码较长,所以我将 一些 代码省略了。
1,首先给 这个 View 设置 onClick 和 onTouch
CustomVolumeControlBar bar = findViewById(R.id.controlBar);
bar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClick: " );
}
});
bar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
继续分析,如果 onTouch 返回 true 会如何。
结果就是 View 的 onClick 和 onTouchEvent 都没有执行
如果 onTouch 返回 false 呢(onTouchEvent 返回 true )?
结果 onClick 没有执行,但是 onTouchEvent 执行了。
onTouchEvent 返回 true,
结果:onClick 没有执行,onTouchEvent 消费了此事件。
onTouchEvent 返回 false
结果:onClick 没有执行,onTouchEvent 没有消费此事件,则此事件向上传递,父视图 的 onTouchEvent 会被调用。
onTouchEvent 返回 super
结果:onclick 执行,onTouchEvent 消费此事件。
由此可见,onTouch 的优先级 > onTouchEvent > onClick
当一个事件产生后,他的传递遵循如下规律:Activity -> Window -> View ,即事件总是先给 Activity,Activity 在传给 Window。最后 Window 在传给 顶级的 View 。
View 接收到事件后 ,就会按照事件分发机制去分发事件。如果一个 View 的 onTouchEvent 返回 false,那么他的父容器的 onTouchEvent 将会被调用,依次类推,如果所有的 onTouchEvent 都不处理这个事件,那么这个事件最终会被 Activity 所处理,即 Activity 的 onTouchEvent 会被调用。
下面看一张流程图:
在图片中,清晰的记录了从开始到结束 所调用的方法。这个图片来自这位大佛 https://blog.csdn.net/petterp/article/details/94463191
对于 dispatchTouchEvent (分发)方法:如果他返回 false ,不管当前是 ViewGroup 还是 View ,都会从 当前View 的上一级onTouchEvent 事件向上传递()。如果返回 true,则表示这个事件被消费,这个事件就直接结束。返回 super,事件继续向下分发,直到事件被消费为止。
关于事件的传递,这里给出一些结论,根据这些结论可以更好地理解整个传递机制,如下所示:
1,同一个序列 是指当 手指按下的那一刻起,到这个手指离开的那一刻结束,在这个过程中所产生的一系列事件,这个事件以 down 开始,中间 有不定的 move 事件,最后以 up 事件结束。
2,正常情况下,一个事件序列只能被一个 View 所拦截且消耗(3)。因为一旦一个元素拦截了 某个事件,那么同一个事件 序列内 的所有事件都会交给他处理,因此同一个时间序列中的事件不能分别由两个View 同时处理。但是通过特殊手段可以做到,比如一个 View 将本该自己处理的事件 通过 onTouchEvent 强行传递给其他 View处理。
3,某个 View 一旦决定拦截,那么这个序列只能由他 来处理(如果事件序列能高传递给他的话),并且它的 onInterceptTouchEvent 不会再被调用。这个也很好理解,就是说当某个View决定拦截一个事件后,那么系统会把同一个事件序列的其他方法都交给他处理,因此就不会调用这个 View 的 onInterceptTouchEvent 去询问他是否要拦截了。
4,某个View 一旦开始处理事件,如果他不消耗 ACTION_DOWN 事件 (onTouchEvent 反回了 false),那么同一事件序列的其他事件都不会交给他来处理,并且交给他的父元素去处理。即父元素的 onTouchEvent 会被调用,意思就是 事件一旦交给了 View去处理,那么他就必须消耗掉,否则 同一序列中剩下的事件 就不在交给他来处理了。
5,如果 View 不消耗 ACTION_DOWN 以外的其他事件,那么这个点击事件会消失,此时父元素的 onTouchEvent 并不会被调用,并且 View 可以持续收到后续的事件,最终这些消失的事件会传递给 Activity 处理。
6,ViewGroup 默认不会拦截任何事件。Android 源码中的 ViewGroup 的onInterceptTouchEvent 方法默认返回 false。
7,View没有 onInterceptTouchEvent 方法,一旦有事件传递给他,那么他的 onTouchEvent 就会被调用。
8,View 的 onTouchEvent 默认都会消耗事件(返回true),除非他是不可点击的(clickable 和 longClickable 同时 为 false),View 的longClickable 属性默认都为 false,clickable 属性要分情况,比如 Button 的 clickable 属性默认为 true,而 TextView 的 clickable 属性默认为 false。所以 如果给Button 的 clickable 设置为 false ,则这个Button 是不可点击的。
9.,View 额 enable 属性不影响 onTouchEvent 的返回值,哪怕一个 View 是 disable 状态的,只要他的 clickable 或者 longClickable 有一个 为 true,那么太尬的 onTouchEvent 就返回 true。
10,onClick 会发生的前提是 当前 View 是可点击的。并且他收到了 down 和 up 的事件。
11,事件 传递的过程是 由外向内的,即事件总是先传递给父元素,然后由父元素分发给 子View,通过 requestDisallowInterceptTouchEvent 方法可以 在子元素中敢于父元素的分发过程,但是 ACTION_DOWN 事件除外。
文章参考自 艺术探索
标签:分发,ViewGroup,onTouchEvent,事件,机制,true,public,View 来源: https://blog.csdn.net/baidu_40389775/article/details/94628184