目录
2.1 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
2.2 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
2.3 事件响应:public boolean onTouchEvent(MotionEvent ev)
相关文章:
浅谈Android事件分发机制 (详细、专业)
完全理解android事件分发机制 (有demo)
Android:事件分发机制 (自己的)
Android事件分发之Activity篇 -- dispatchTouchEvent、onTouchEvent之间关系
描述一下Android的事件分发机制?
Android事件分发机制的本质:事件从哪个对象发出,经过哪些对象,最终由哪个对象处理了该事件。此处对象指的是Activity、Window与View。
Android事件的分发顺序:Activity(Window) -> ViewGroup -> View
Android事件的分发主要由三个方法来完成,如下所示:
// 父View调用dispatchTouchEvent()开始分发事件
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
// 父View决定是否拦截事件
if(onInterceptTouchEvent(event)){
// 父View调用onTouchEvent(event)消费事件,如果该方法返回true,表示
// 该View消费了该事件,后续该事件序列的事件(Down、Move、Up)将不会在传递
// 该其他View。
consume = onTouchEvent(event);
}else{
// 调用子View的dispatchTouchEvent(event)方法继续分发事件
consume = child.dispatchTouchEvent(event);
}
return consume;
}
两个实际遇到的案例:
1、 ScrollView和ListView滑动冲突:
解决办法:重写ScrollView,在其onInterceptTouchEvent方法中进行相应处理:覆写onInterceptTouchEvent方法,点击操作发生在ListView的区域的时候, 返回false让ScrollView的onTouchEvent接收不到MotionEvent,而是把Event传到下一级的控件中
public class ListScrollView extends ScrollView {
private XListView xListView;
public ListScrollView(Context context) {
super(context);
}
public ListScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setxListView(XListView xListView) {
this.xListView = xListView;
}
/**
* 覆写onInterceptTouchEvent方法,点击操作发生在ListView的区域的时候,
* 返回false让ScrollView的onTouchEvent接收不到MotionEvent,而是把Event传到下一级的控件中
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (xListView != null && checkArea(xListView, ev)) {
return false;
}
return super.onInterceptTouchEvent(ev);
}
/**
* 测试view是否在点击范围内
* @param v
* @return
*/
private boolean checkArea(View v, MotionEvent event){
float x = event.getRawX();
float y = event.getRawY();
int[] locate = new int[2];
v.getLocationOnScreen(locate);
int l = locate[0];
int r = l + v.getWidth();
int t = locate[1];
int b = t + v.getHeight();
if (l < x && x < r && t < y && y < b) {
return true;
}
return false;
}
}
Demo:
TouchEventActivity
public class TouchEventActivity extends AppCompatActivity {
@BindView(R.id.local_btn)
LocalButton localBtn;
@BindView(R.id.local_rl)
LocalRelativeLayout localRl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch_event);
ButterKnife.bind(this);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LogUtils.e("TouchEventActivity --> dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
// return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogUtils.e("TouchEventActivity --> onTouchEvent");
return super.onTouchEvent(event);
}
@OnClick({R.id.local_btn, R.id.local_rl})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.local_btn:
LogUtils.e("local_btn --> 我被点击了");
break;
case R.id.local_rl:
break;
}
}
}
activity_touch_event
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/lightBlue">
<com.gs.sumok2.touch.LocalRelativeLayout
android:id="@+id/local_rl"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@color/lightGreen"
android:layout_centerInParent="true">
<com.gs.sumok2.touch.LocalButton
android:id="@+id/local_btn"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerInParent="true"
android:background="@color/lightRed"
/>
</com.gs.sumok2.touch.LocalRelativeLayout>
</RelativeLayout>
colors.xml
<color name="lightBlue">#ABDFDC</color>
<color name="lightGreen">#87C09B</color>
<color name="lightYellow">#B5BC85</color>
<color name="lightRed">#E7B1AD</color>
LocalRelativeLayout
public class LocalRelativeLayout extends RelativeLayout {
public LocalRelativeLayout(Context context) {
this(context, null);
}
public LocalRelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LocalRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LogUtils.e("LocalRelativeLayout --> dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
LogUtils.e("LocalRelativeLayout --> onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogUtils.e("LocalRelativeLayout --> onTouchEvent");
return super.onTouchEvent(event);
}
}
LocalButton
public class LocalButton extends android.support.v7.widget.AppCompatButton {
public LocalButton(Context context) {
this(context, null);
}
public LocalButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LocalButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LogUtils.e("LocalButton --> dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogUtils.e("LocalButton --> onTouchEvent");
return super.onTouchEvent(event);
}
}
测试:
测试一:
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),结果如下:
TouchEventActivity --> dispatchTouchEvent
LocalRelativeLayout --> dispatchTouchEvent
LocalRelativeLayout --> onInterceptTouchEvent
LocalButton --> dispatchTouchEvent
LocalButton --> onTouchEvent
local_btn --> 我被点击了
说明: Android事件响应机制是“由外到内”分发、“由内到外”处理的形式实现的。
测试二:
将Activity的dispatchTouchEvent的返回值设为true或者false,那么事件都不会传到下一层。
- return true : View消费所有事件。
- return false :停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,那么事件将被系统消费、处理。
打印结果为:
TouchEventActivity --> dispatchTouchEvent
结论:事件被Activity拦截了。
测试三:
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回false,
- return false :停止分发,交由上层控件的onTouchEvent方法进行消费
TouchEventActivity --> dispatchTouchEvent
LocalRelativeLayout --> dispatchTouchEvent
TouchEventActivity --> onTouchEvent
结论:LocalRelativeLayout将事件返回给Activity的onTouchEvent处理。
测试四:
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回true,
TouchEventActivity --> dispatchTouchEvent
LocalRelativeLayout --> dispatchTouchEvent
Android事件处理的三个重要函数
Android事件分发机制主要由“事件分发”—>“事件拦截”—>“事件响应”这三步来进行逻辑控制的。本文也将从这三步对应的函数来分析。
2.1 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
当监听到有触发事件时,首先由Activity进行捕获,然后事件就进入事件分发的流程。Activity本身没有事件拦截,从而将事件传递给最外层的View的dispatchTouchEvent(MotionEvent ev)方法,该方法将对事件进行分发。
- return true : View消费所有事件。
- return false :停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,那么事件将被系统消费、处理。
- super.dispatchTouchEvent(ev): 将事件交由本层的事件拦截onInterceptTouchEvent方法处理。
2.2 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
- return true: 对事件拦截,交由本层的onTouchEvent进行处理。
- return false: 不拦截,分发到子View,由子View的dispatchTouchEvent方法处理。
- super.onInterceptTouchEvent(ev):默认表示事件拦截,交由本层的onTouchEvent进行处理。
2.3 事件响应:public boolean onTouchEvent(MotionEvent ev)
- return true: 表示onTouchEvent处理完事件后消费了此次事件。
- return false: 不响应事件,不断的传递给上层的onTouchEvent方法处理,直到某个View的onTouchEvent返回true,则认为该事件被消费。如果到最顶层View还是返回false,那么该事件不消费,将交由Activity的onTouchEvent进行处理。
- return: super.onTouchEvent,不响应事件,结果与return返回false一样。
事件分发流程图:
结合上面的理解,我们再来看看Touch事件传递机制流程图
为什么会有事件分发机制?
安卓上面的View是树形结构,View可能会重叠在一起,当我们点击的地方有多个View都可以响应的时候,这个点击事件应该由谁处理呢?为了解决这一个问题,就有了事件分发机制。我们看一下示例:
再来看一下layout的布局结构:
PhoneWindow和DecorView是什么?
在图二中我们看到了PhoneWindow和DecorView,这两个是什么玩意呢?
先来说一下DecorView,在图一中,没有被view覆盖的界面会显示主题的颜色,这部分区域以及最上面的标题也没有出现在layout布局当中,它们都属于DecorView。
PhoneWindow是抽象类Window的一个实现类,而Window是所有视图最顶层的管理容器。view视图、viewgroup的外观和行为还有背景显示、标题栏、事件处理都是属于Window来管理。但是它是抽象类,所以管理的实现都是由它唯一的实现PhoneWindow来实现。PhoneWindow也可以说是View的事件管理容器。而PhoneWindow一般都是通过它的内部类DecorView来进行消息传递的。PhoneWindow通过指示DecorView来给下面的View传递消息,而下面View的信息也是通过DecorView返回给PhoneWindow。
Window.java的源码:
public abstract class Window {
public interface Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchKeyShortcutEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
public boolean dispatchTrackballEvent(MotionEvent event);
public boolean dispatchGenericMotionEvent(MotionEvent event);
}
}
PhoneWindow.java的源码:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {...}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent ev) {...}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {}
@Override
public boolean dispatchTrackballEvent(MotionEvent ev) {}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev) {}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {...}
}
}
三个重要的事件分发方法:
1、dispatchTouchEvent
2、onInterceptTouchEvent
3、onTouchEvent
需要注意的是:Activity和View是没有第二个方法的,因为Activity是作为事件的最原始分发者,如果Activity拦截了这个事件,就会导致整个屏幕都无法响应事件。而View作为事件传递的最末端,要么消费这个事件要么把这个事件回传,是不需要有这个方法的。
事件分发流程:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View