文章目录
1、事件的定义
- 定义:当用户触摸屏幕时,将产生的触摸行为(Touch事件);
- 事件的类型,有四种:
MotionEvent.ACTION_DOWN 手指初次接触到屏幕时触发;
MotionEvent.ACTION_UP 手指离开屏幕时触发;
MotionEvent.ACTION_MOVE 手指在屏幕上滑动时触发,会多次触发;
MotionEvent.ACTION_CANCEL 事件被上层拦截时触发;
- 事件序列
正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件
1、点击屏幕后立即松开,事件序列为DOWN–>UP;
2、点击屏幕滑动一会再松开,事件序列为DOWN–>MOVE–>…–>MOVE–>UP;
1.1 DOWN事件的特殊性
1、所有touch事件都是从DOWN事件开始的;
2、DOWN事件的处理结果会直接影响后续MOVE、UP事件的逻辑;
3、后续的MOVE、UP等事件分发交给谁,取决于它们的起始事件DOWN是由谁捕获的。
1.2 事件分发
- 事件分发对象
Activity:控制生命周期 & 处理事件;
ViewGroup:一组View的集合(含多个子View);
View:所有UI组件的基类;
- 事件分发主要方法
dispatchTouchEvent(MotionEvent ev):用来进行事件分发;
onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中);
onTouchEvent(MotionEvent ev):处理点击事件
2、事件分发详解
2.1 事件分发–Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//空方法,子类可重写
onUserInteraction();
}
//getWindow是PhoneWindow对象
//最终调用ViewGroup.dispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
2.2事件分发— ViewGroup
- ViewGroup的事件分发,可以用以下伪代码表示
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean counsume = false;
//调用onInterceptTouchEvent判断是否拦截
if(onInterceptTouchEvent(ev)){
//拦截则调用自身的onTouchEvent
consume = onTouchEvent(ev);
}else{
//不拦截,将事件分发给子View
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
2.3 事件分发— View
- 事件分发流程
2.4 总结:
1、一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束;
2、正常情况下,一个事件序列只能被一个View拦截并消耗;
3、某个View一旦决定拦截,那么这个时间序列都将由它的onTouchEvent处理,并且它的onIntenrceptTouchEvent不会被调用;
4、某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
5、事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外;
6、ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用;
7、View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false;
8、View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
9、onClick会影响的前提是当前View是可点击的,并且收到了ACTION_DOWN和ACTION_UP的事件,并且受长按事件影响,当长按事件返回true时,onClick不会响应;
10、onLongClick在ACTION_DOWN里判断是否响应,要想执行长按事件该View必须是longClickable并且设置了OnLongClickListener。
3、事件传递测试
3.1 正常事件传递过程
- MyViewGroup.class
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "parent:dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, "parent:onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.e(TAG, "parent:onTouchEvent" + ev.getAction());
return super.onTouchEvent(ev);
}
- MyView.class
@Override
public void setOnTouchListener(OnTouchListener l) {
Log.e(TAG, "child: setOnTouchListener");
super.setOnTouchListener(l);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(TAG, "child: dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "child: onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
- 测试结果
2021-04-05 22:38:56.594 15581-15581/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===0
2021-04-05 22:38:56.594 15581-15581/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===0
2021-04-05 22:38:56.595 15581-15581/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===0
2021-04-05 22:38:56.595 15581-15581/com.laoda.androdstudy E/Constraints: child: onTouchEvent===0
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===2
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===2
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===2
2021-04-05 22:38:56.608 15581-15581/com.laoda.androdstudy E/Constraints: child: onTouchEvent===2
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===1
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===1
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===1
2021-04-05 22:38:56.609 15581-15581/com.laoda.androdstudy E/Constraints: child: onTouchEvent===1
- 总结:正常事件分发流程中,父View首先将DOWN事件分发给子View,子View捕获到DOWN事件并消费该事件;后续父View继续向子View分发MOVE、UP事件。
- 其中:action_DOWN=0; ACTION_UP = 1; ACTION_MOVE=2; ACTION_CANCEL=3;
3.2 ViewGroup中onInterceptTouchEvent()为true时
- MyViewGroup.class
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, "parent:onInterceptTouchEvent" +"==="+ ev.getAction());
return true;
}
- 测试结果
2021-04-05 22:41:27.654 21961-21961/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===0
2021-04-05 22:41:27.654 21961-21961/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===0
2021-04-05 22:41:27.655 21961-21961/com.laoda.androdstudy E/Constraints: parent:onTouchEvent===0
- 总结:这里为什么没有MOVE UP事件的日志呢? 如果ViewGroup的onTouchEvent返回的是true呢?
1、事件被父View拦截,子View捕获不到DOWN事件,且父View又不能自行消费,此时父View会向上传递自己不能处理事件的信号,后续上层也不会再向父View分发事件,故日志中无MOVE、UP的日志;
2、如果ViewGroup中onTouchEvent返回的是true,代表父View能自行消费事件,后续MOVE、UP事件都由父View消费。
3.3 当ViewGroup中onInterceptTouchEvent拦截MOVE事件时
- MyViewGroup.class
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
return true;
}
Log.e(TAG, "parent:onInterceptTouchEvent" + "===" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
- 测试结果
2021-04-06 10:43:46.439 3596-3596/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===0
2021-04-06 10:43:46.440 3596-3596/com.laoda.androdstudy E/Constraints: parent:onInterceptTouchEvent===0
2021-04-06 10:43:46.440 3596-3596/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===0
2021-04-06 10:43:46.441 3596-3596/com.laoda.androdstudy E/Constraints: child: onTouchEvent===0
2021-04-06 10:43:46.493 3596-3596/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===2
2021-04-06 10:43:46.494 3596-3596/com.laoda.androdstudy E/Constraints: child: dispatchTouchEvent===3
2021-04-06 10:43:46.494 3596-3596/com.laoda.androdstudy E/Constraints: child: onTouchEvent===3
2021-04-06 10:43:46.498 3596-3596/com.laoda.androdstudy E/Constraints: parent:dispatchTouchEvent===1
2021-04-06 10:43:46.499 3596-3596/com.laoda.androdstudy E/Constraints: parent:onTouchEvent===1
- 总结:此种情况,MOVE事件被父View拦截,子View只能捕获到DOWN事件不能继续向后捕获,则调用CANCEL事件;后续的MOVE、UP事件由父View消费。