事件分发机制
一、Touch事件分发中重要方法介绍
1.事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
return false:不继续向下分发该事件,不会执行当前view的onInterceptTouchEvent()和onTouchEvent()方法,会返回至上一级,由上一级继续处理该事件。
return true:不继续向下分发事件,不会执行当前view的onInterceptTouchEvent()和onTouchEvent()方法,事件在当前view的dispatchTouchEvent被处理。
return super.dispatchTouchEvent(ev):向下分发事件,执行当前view的onInterceptTouchEven()
2.事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
return false:对事件不进行拦截,事件继续向下传递,并执行子view的dispatchTouchEvent()。
return true:事件拦截,事件不继续向下传递,事件由当前view的onTouchEvent()执行响应。
return super.onInterceptTouchEvent(ev):与false情况一致。
3.事件响应:public boolean onTouchEvent(MotionEvent event)
return false:不响应事件,返回上级,由上级处理。
return true:响应事件,事件不再继续传递。
return super.onTouchEvent(ev):如果当前view有能力处理,则与true情况一致,例如button控件,默认有能力处理事件,如果当前view没有能力处理,则与false情况一致,交由上级处理。
在View控件中是没有onInterceptTouchEvent,因为View控件已经没有下层的控件了,所以不需要拦截。主要方法的存在具体可看下表:
控件\方法 | dispatchTouchEvent | onInterceptTouchEvent | onTouchEvent |
---|---|---|---|
Activity | 存在 | 不存在 | 存在 |
ViewGroup | 存在 | 存在 | 存在 |
View | 存在 | 不存在 | 存在 |
二、方法返回值的影响实例
从onTouchEvent的返回值可以知道当返回super.onTouchEvent(ev)时,其结果与控件是否有能力处理有关系。因此以下案例可分成两种情况。
1.view有响应事件的能力,如Button:
工具类:
public class TouchEventUtil {
/**
* 获取事件的action
* @param motionEvent
* @return
*/
public static String getTouchEventName(MotionEvent motionEvent){
String touchEventName = "";
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
touchEventName = "ACTION_DOWN";
break;
case MotionEvent.ACTION_UP:
touchEventName = "ACTION_UP";
break;
case MotionEvent.ACTION_MOVE:
touchEventName = "ACTION_MOVE";
break;
}
return touchEventName;
}
}
Activity代码:
public class TouchEventActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch_event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("TouchEventActivity", "dispatchTouchEvent: TouchEventActivity====="+TouchEventUtil.getTouchEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("TouchEventActivity", "onTouchEvent: TouchEventActivity====="+TouchEventUtil.getTouchEventName(event));
return super.onTouchEvent(event);
}
}
ViewGroup代码:
public class CustomViewGroup extends LinearLayout {
public CustomViewGroup(Context context) {
super(context);
}
public CustomViewGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("TouchEventActivity", "dispatchTouchEvent: CustomViewGroup====="+TouchEventUtil.getTouchEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("TouchEventActivity", "onInterceptTouchEvent: CustomViewGroup====="+TouchEventUtil.getTouchEventName(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("TouchEventActivity", "onTouchEvent: CustomViewGroup====="+TouchEventUtil.getTouchEventName(event));
return super.onTouchEvent(event);
}
}
Button的代码:
public class CustomButton extends androidx.appcompat.widget.AppCompatButton {
public CustomButton(@NonNull Context context) {
super(context);
}
public CustomButton(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("TouchEventActivity", "dispatchTouchEvent: CustomButton====="+TouchEventUtil.getTouchEventName(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("TouchEventActivity", "onTouchEvent: CustomButton====="+TouchEventUtil.getTouchEventName(event));
return super.onTouchEvent(event);
}
}
activity_touch_event布局代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TouchEventActivity"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/teal_700">
<com.homework.stuproject.CustomViewGroup
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/purple_200">
<com.homework.stuproject.CustomButton
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@color/purple_700">
</com.homework.stuproject.CustomButton>
</com.homework.stuproject.CustomViewGroup>
</LinearLayout>
具体样式如下:
a.假设控件返回均为默认值:
方法 | dispatchTouchEvent | onInterceptTounchEvent | onTouchEvent |
---|---|---|---|
Activity | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
CustomViewGroup | super.dispatchTouchEvent(ev) | super.onInterceptTouchEvent(ev) | super.onTouchEvent(ev) |
CustomButton | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
点击中心区域,即点击CustomButton控件,打印结果如下:
从打印可以看出,因为CustomButton有响应事件的能力,此时super.onTouchEvent(ev)=true。
b.假设ViewGroup的onInterceptTouchEvent(ev)返回true,其余均为默认值:
方法 | dispatchTouchEvent | onInterceptTounchEvent | onTouchEvent |
---|---|---|---|
Activity | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
CustomViewGroup | super.dispatchTouchEvent(ev) | true | super.onTouchEvent(ev) |
CustomButton | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
打印结果如下:
因为在CustomViewGroup中拦截了事件,因此事件不会再向下传递,只会调用CustomViewGroup的onTouchEvent(),但由于CustomViewGroup没有处理事件的能力,super.onTouchEvent(ev)结果为false,事件交由上层处理,即Activity中的onTouchEvent()。又因为Activity已经知道了CustomViewGroup没有处理事件的能力,因此ACTION_UP不会再向下传递,直接就被Activity调用onTouchEvent()处理。
c.假设ViewGroup的dispatchTouchEvent(ev)返回true,其余均为默认值:
方法 | dispatchTouchEvent | onInterceptTounchEvent | onTouchEvent |
---|---|---|---|
Activity | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
CustomViewGroup | true | super.onInterceptTouchEvent(ev) | super.onTouchEvent(ev) |
CustomButton | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
结果如下:
d.假设ViewGroup的dispatchTouchEvent(ev)返回false,其余均为默认值:
方法 | dispatchTouchEvent | onInterceptTounchEvent | onTouchEvent |
---|---|---|---|
Activity | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
CustomViewGroup | false | super.onInterceptTouchEvent(ev) | super.onTouchEvent(ev) |
CustomButton | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
结果如下:
返回false,即事件不会向下传递,交由上层处理。
2.view没有响应事件的能力,如CustomView,代码如下:
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("TouchEventActivity", "dispatchTouchEvent: CustomView====="+TouchEventUtil.getTouchEventName(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("TouchEventActivity", "onTouchEvent: CustomView====="+TouchEventUtil.getTouchEventName(event));
return super.onTouchEvent(event);
}
}
activity_touch_event布局代码,将原先的CustomButton修改为CustomView即可:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TouchEventActivity"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/teal_700">
<com.homework.stuproject.CustomViewGroup
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/purple_200">
<com.homework.stuproject.CustomView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@color/purple_700">
</com.homework.stuproject.CustomButton>
</com.homework.stuproject.CustomViewGroup>
</LinearLayout>
a.假设控件返回均为默认值:
方法 | dispatchTouchEvent | onInterceptTounchEvent | onTouchEvent |
---|---|---|---|
Activity | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
CustomViewGroup | super.dispatchTouchEvent(ev) | super.onInterceptTouchEvent(ev) | super.onTouchEvent(ev) |
CustomView | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
点击中心区域,打印结果如下:
因为CustomView没有处理事件的能力,因此会一层一层向上执行onTouchEvent(),又因为ACTION_DOWN执行完之后Activity已经知道下级控件不能处理事件,因此ACTION_MOVE和ACTION_UP都不会向下传递,Activity直接调用onTouchEvent()处理。
b.假设控件CustomViewGroup的onInterceptTouchEvent返回true,其余返回均为默认值:
方法 | dispatchTouchEvent | onInterceptTounchEvent | onTouchEvent |
---|---|---|---|
Activity | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
CustomViewGroup | true | super.onTouchEvent(ev) | super.onTouchEvent(ev) |
CustomView | super.dispatchTouchEvent(ev) | – | super.onTouchEvent(ev) |
代码运行结果如下:
事件在CustomViewGroup中被拦截,交由该控件的OnTouchEvent()响应该事件,再根据super.onTouchEvent(ev)返回false交由上层处理。
值得注意的是,对于没有响应事件能力的CustomView来说,只需要在不居中添加“android:clickable="true”就能响应事件了。
三、onTouch vs onTouchEvent vs onClick
1、执行顺序
onClick需要从ACTION_DOWN到ACTION_UP,所以onClick的执行顺序一定在onTouch和onTouchEvent之后,而onTouch又是执行在onTouchEvent之前,具体可看dispatchTouchEvent的源代码:
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
如果view控件没有响应事件能力,在setOnTouchListener()方法之后,view空间就拥有响应事件能力,其实从上述的源码就可以知道,(mViewFlags & ENABLED_MASK) == ENABLED只会判断view控件的enabled值,如果在setOnTouchListener()之前调用了setEnabled(false),则onTouch不会被执行,且会根据view是否有能力响应事件去处理touch事件。但如果给view控件添加了点击监听setOnClickListener(),则控件就有了响应事件的能力,以下是setOnClickListener()的源码:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
2、onTouch返回值的作用
return false:事件不被消费,onTouchEvent继续执行。
return true:事件被消费,onTouchEvent不会被执行。