一个小Demo带你理解安卓事件分发

本文深入探讨安卓事件分发机制,解析事件如何从屏幕触摸开始,经由ViewRootImpl生成MotionEvent,再到DecorView、Activity等组件的传递流程。通过实例分析,详细解释了事件在ViewGroup和View之间的分发、拦截与处理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   安卓事件分发是个老生常谈的话题, 在ScrollView高度测量原理介绍Android View测量原理, 现在再用个实例讲解事件分发原理。

   本文要搞懂2个问题。

1、 事件是怎么产生的?

2、事件是怎么传递的?

    手指在屏幕上一滑, framework层的ViewRootImpl中广播监听WindowInputEventRecevier最先收到事件, 将native层传来的物理坐标转换成MotionEvent后继续传递。

    事件传递过程可总结为 native层--->ViewRootImpl--->DecorView--->Activity--->PhoneWindow--->DecorView--->ViewGroup(可以理解为contentView)。 PS:记住这个顺序,很重要~~~。

dispatchTouchEvent:2604, ViewGroup (android.view)
superDispatchTouchEvent:549, DecorView (com.android.internal.policy)
superDispatchTouchEvent:1953, PhoneWindow (com.android.internal.policy)
dispatchTouchEvent:3548, Activity (android.app)
dispatchTouchEvent:21, EventActivity (com.byrcegao.myscrollviewtest.event)
dispatchTouchEvent:502, DecorView (com.android.internal.policy)
dispatchPointerEvent:12027, View (android.view)
processPointerEvent:5278, ViewRootImpl$ViewPostImeInputStage (android.view)
......
onInputEvent:7325, ViewRootImpl$WindowInputEventReceiver (android.view)
dispatchInputEvent:192, InputEventReceiver (android.view)
nativePollOnce:-1, MessageQueue (android.os)
next:379, MessageQueue (android.os)
loop:144, Looper (android.os)
main:7425, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:245, Zygote$MethodAndArgsCaller (com.android.internal.os)
main:921, ZygoteInit (com.android.internal.os)

  小结:1、在Framework层的ViewRootImpl.java里产生了MotionEvent;  2、事件是从根节点传递到叶子节点(一般安卓视图是个多叉树结构)

   上面是framework层的事件传递顺序,那么我们实际开发的View视图是如何传递的呢? 看个例子:  红色FrameLayout是祖父容器、绿色FrameLayout是父容器、蓝色TextView是子View(多叉树的叶子节点)。

假如手指在蓝色区域点击一下, 事件是如何传递的???  做个比喻, 事件传递就像西游记里猪八戒问大师兄你吃不吃, 大师兄不吃则被八戒吃掉了。   

 View包括dispatchTouchEvent(分发)和onTouchEvent(处理)函数, ViewGroup继承于View并新增了onInteruptTouchEvent(拦截器)函数。  

时序GrandView(ViewGroup派生类)

FatherView(ViewGroup派生类)

SonView(View派生类)
1dispatchTouchEvent  
2onInteruptTouchEvent  
3 dispatchTouchEvent 
4 onInterptTouchEvent 
5  dispatchTouchEvent
6  onTouchEvent
7 onTouchEvent 
8onTouchEvent  
    

 

上面是事件传递的最简单模型, 画成表格后形状就像大于号。 从日志可以看到GrandView、FatherView和SonView只收到了ACTION_DOWN, 都没收到ACTION_UP事件。

  PS:DOWN是事件的起点, 如果你不消费(消费就是返回true)DOWN事件则父容器不会再传递后续的MOVE/UP/CANCEL事件给自己。   原理可以查看ViewGroup的dispatchTouchEvent函数。

    下面修改一下代码, FatherView拦截Down事件(在onInteruptTouchEvent返回true), SonView还能收到事件吗?  答案是不能。

public class FatherView extends FrameLayout {
   ...
  @Override public boolean onInteruptTouchEvent(MotionEvent ev) {
    Log.d("brycegao", "FatherView dispatchTouchEvent:" + ev.toString());
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
      return true;  //子View不会收到任何事件
    }
    return onInteruptTouchEvent(ev);
  }
   ...
}

     在SonView的父容器FatherView里拦截Down事件后, SonView不会收到任何事件。    

     删除FatherView的拦截down逻辑, 在FatherView的onTouchEvent函数里消费down事件。 又会怎么样呢?

public class FatherView extends FrameLayout {
  ... 
  @Override public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d("brycegao", "FatherView dispatchTouchEvent:" + ev.toString());
    return super.dispatchTouchEvent(ev);
  }

  @Override public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.d("brycegao", "FatherView onInterceptTouchEvent:" + ev.toString());
    return super.onInterceptTouchEvent(ev);
  }

  @Override public boolean onTouchEvent(MotionEvent ev) {
    Log.d("brycegao", "FatherView onTouchEvent:" + ev.toString());
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
      return true;
    }
    return super.onTouchEvent(ev);
  }

}

   消费down事件后当前view可以收到后续的move、up事件, 且消费事件后就不需要再继续传递了(GrandView的onTouchEvent没收到down,因为被FatherView消费了)。从日志看到SonView仍然无法收到move、up事件。

      应用层的事件传递逻辑,都在ViewGroup的dispatchTouchEvent函数里。

总结:

1、View的事件传递是自根节点向叶子节点询问, 如果被某个节点消费了(onTouchEvent或onTouch回调返回true),就不再执行父容器的onTouchEvent函数。 (从根节点DecorView到叶子节点的某个View消费了事件)

2、如果拦截了Down事件(onInteruptTouchEvent函数返回true), 则子节点不会收到任何事件;

3、如果当前View消费了Down事件(onTouchEvent函数或onTouch回调返回true), 则当前View才能收到后续的Move、Up和Cancel事件;

 

坑: onTouch和onTouchEvent的关系??? 

   在View.java的dispatchTouchEvent函数里处理了onTouch回调, 执行时序dispatchTouchEvent---onTouch回调---onTouchEvent, 即onTouch回调在前,onTouchEvent在后。  

        如果onTouch返回true,则不会执行当前视图和父容器的onTouchEvent函数。

 

 /**
     * Interface definition for a callback to be invoked when a touch event is
     * dispatched to this view. The callback will be invoked before the touch
     * event is given to the view.
     */
    public interface OnTouchListener {
        /**
         * Called when a touch event is dispatched to a view. This allows listeners to
         * get a chance to respond before the target view.
         *
         * @param v The view the touch event has been dispatched to.
         * @param event The MotionEvent object containing full information about
         *        the event.
         * @return True if the listener has consumed the event, false otherwise.
         */
        boolean onTouch(View v, MotionEvent event);
    }

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值