所谓事件分发,其实就是对 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 事件除外。