其他分享
首页 > 其他分享> > View 的事件分发机制

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 的区别

@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事件也没有执行。


​ 当一个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