Android View事件分发处理

1.View事件分发重要的方法


Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)onInterceptTouchEvent(MotionEvent ev)onTouchEvent(MotionEvent ev);这里说的事件实际上是指MotionEvent ,能够响应这些方法的控件包括:ViewGroupViewActivity。方法与控件的对应关系如下表所示:


Touch 事件相关方法   方法功能 
  ViewGroup   
       View       
     Activity     
  public boolean dispatchTouchEvent(MotionEvent ev) 事件分发 
 Yes  Yes  Yes
  public boolean onInterceptTouchEvent(MotionEvent ev) 
事件拦截 
 Yes  No  No
  public boolean onTouchEvent(MotionEvent ev) 事件响应 
 Yes  Yes  Yes


从这张表中我们可以看到 ViewGroup 和 View 对与 Touch 事件相关的三个方法均能响应,而 Activity 对 onInterceptTouchEvent(MotionEvent ev) 也就是事件拦截不进行响应。另外需要注意的是 View 对 dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev) 的响应的前提是可以向该 View 中添加子 View,如果当前的 View 已经是一个最小的单元 View(比如 TextView),那么就无法向这个最小 View 中添加子 View,也就无法向子 View 进行事件的分发和拦截,所以它没有dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev),只有 onTouchEvent(MotionEvent ev)

三者的关系用伪代码表示最清楚不过了(摘自Android开发艺术探索)

public boolean dispathTouchEvent(MotionEvent event){
	boolean consume = false;
	if(onInterceptTouchEvent(event)){
		consume = onTouchEvent(event);
	}else{
		consume = child.dispathTouchEvent(event);
	}
	return consume;
}


2.Touch 事件分析

2.1 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:

  • 如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
  • 如果 return false,事件分发分为两种情况:
  1. 如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
  2. 如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的  onTouchEvent 进行消费。
  • 如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的  onTouchEvent (ViewGroup 则为 onInterceptTouchEvent )方法。


2.2 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev) 

外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:

  • 如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
  • 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
  • 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件一般不会拦截,ViewGroup默认返回false。


2.3 事件响应:public boolean onTouchEvent(MotionEvent ev)

在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:

  • 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
  • 如果返回了 true 则会接收并消费该事件。
  • 如果返回 super.onTouchEvent(ev) 默认处理事件一般返回true,除非该View不可点击(clickable和clickable同时为false,例如TextView)才会返回false。

到这里,与Touch事件相关的三个方法就分析完毕了。


3.案例分析

一个主activity(MainActivity),布局文件一个自定义LinearLayout(MyLinearLayout),LinearLayout上面加一个自定义Button(MyButton)

直接上示意图比布局文件更直观


MainActivity、MyLinearLayout、MyButton 都只重写上面表格分析的三个方法,并在每个方法中添加打印,点击一下MyButton




给MyButton添加OnClickListener,OnTouchListener,并添加打印,其中OnTouchListener返回false


接下来把OnTouchListener改为true,再看打印


通过这个可以说明 调用顺序  

mOnTouchListener.onTouch——>OnTouchEvent——>mOnClickListener.OnClick

如果mOnTouchListener不为空且mOnTouchListener.onTouch(this, event) 返回值为false,则向下执行onTouchEvent(event) 

onTouchEvent(event) 在ACTION_UP消息处理后如果mOnClickListener不为空,则执行 mOnClickListener.OnClick

我们看View的代码也可以证实这一点

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }


把MyButton的dispatchTouchEvent返回值直接改为true,其他都为默认值


把MyButton的dispatchTouchEvent返回值直接改为false,其他都为默认值


ACTION_DOWN消息过来,MyButton不再下发了return false,给上层MyLinearLayout的OnTouchEvent来处理,MyLinearLayout又交给上层MainActivity的OnTouchEvent来处理。这里验证了2.1的结论。

后面的事件流ACTION_UP直接就给MainActivity处理了,不再经过ACTION_DOWN之前的一系列分发途径。


这里我有个疑问,为啥MyLinearLayout的OnTouchEvent会又交给上层MainActivity处理呢?我大胆揣测LinearLayout默认不可点击(clickable和clickable同时为false)为了验证这点,我在 MyLinearLayout的 super.onTouchEvent(event)的前面加一句 setClickable(true);


结果ACTION_DOWN真的被MyLinearLayout消费掉了,后续事件流也不往下分发了。这也验证了2.3的返回 super.onTouchEvent(ev) 的结论。


本文主要纠正其他博文不严谨之处,避免跟我当初一样被误导受其所困。其他结论不再一一贴出Logcat结果,留给敢兴趣的朋友验证。

如有疏忽和疑问欢迎留言探讨,最后附上本文demo,点此下载


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值