举例呈现事件分发机制+源码分析调用关系

事件分发流程

事件分发的起始,由Activity的dispatchTouchEvent开始,以某个View消费了事件。或者无View消费事件作为结束。

在这里插入图片描述

事件分发情景下的不同日志展现

创建一个页面TouchInterceptAty.kt,且在布局中以自定义CGroup.kt作为布局容器。容器中分别通过红圈1-2-3,三种控件CView.kt、CTextView.kt、CButton.kt、放置在容器中。

/** TouchInterceptAty.kt      Activity页面 */
class TouchInterceptAty : AppCompatActivity(){
   ... ...
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        Log.i("Activity", "------dispatchTouchEvent------")
        val res = super.dispatchTouchEvent(ev)
        Log.i("Activity", "dispatchTouchEvent-------->> res=$res")
        return res
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("Activity", "------onTouchEvent------")
        val res = super.onTouchEvent(event)
        Log.i("Activity", "onTouchEvent------>> res=$res")
        return res
    }
}
/** CGroup.kt      继承FrameLayout 自定义Group */
class CGroup : FrameLayout {
    .... ...
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        Log.i("CGroup", "------dispatchTouchEvent------")
        val res = super.dispatchTouchEvent(ev)
        Log.i("CGroup", "dispatchTouchEvent-------->> res=$res")
        return res
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        Log.i("CGroup", "------onInterceptTouchEvent------")
        val res = super.onInterceptTouchEvent(ev)
        Log.i("CGroup", "onInterceptTouchEvent------>> res=$res")
        return res
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CGroup", "------onTouchEvent------")
        val res = super.onTouchEvent(event)
        Log.i("CGroup", "onTouchEvent------>> res=$res")
        return res
    }
}

自定义View情景,定义onTouchEvent消费事件

红圈 · 图1

/** CView.kt  自定义View*/
class CView : View {
   ... ... 
   fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CView", "------dispatchTouchEvent------")
        val res = super.dispatchTouchEvent(event)
        Log.i("CView", "dispatchTouchEvent-------->> res=$res")
        return res
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CView", "------onTouchEvent------")
        val res = true
//        val res = super.onTouchEvent(event)
        Log.i("CView", "onTouchEvent-------->> res=$res")
        return res
    }
	... ...
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        paint.isAntiAlias = true
        paint.setTextAlign(Paint.Align.CENTER)
        paint.setColor(Color.WHITE)
        paint.textSize = 80f
        canvas?.let {cv ->
            cv.drawText("自定义View", (viewWidth / 2).toFloat(), (viewHeight / 2).toFloat(), paint)
        }
    }
}

点击自定义的CView控件,事件分发流程如下日志打印:

onTouchEvent方法直接返回true,以表明当前CView消费了事件。
因此当事件由Acitivity分发经过CGroup,并再次分发到CView时。CView已消费事件,则不再继续执行事件分发,而是通过CView的dispatchTouchEvent告知CGroup、Activity,当前事件已被消费。

 I/Activity: ------dispatchTouchEvent------ #首次触发Activity的事件分发
 I/CGroup: ------dispatchTouchEvent------ #Activity未拦截,事件分发至CGroup
 I/CGroup: ------onInterceptTouchEvent------ #拦截判断,是否进行拦截
 I/CGroup: onInterceptTouchEvent------>> res=false #不拦截事件
 I/CView: ------dispatchTouchEvent------ #由于CGroup未拦截事件[onInterceptTouchEvent=false],事件分发至子View[CView]
 I/CView: ------onTouchEvent------ #由于当前CView是容器最底层控件,事件经分发传递到了这里[onTouchEvent]。目的是让其处理、消费事件。
 I/CView: onTouchEvent-------->> res=true #该CView消费了事件
 I/CView: dispatchTouchEvent-------->> res=true #onTouchEvent返回true,则dispatchTouchEvent也必然返回true,表明已有view消费了事件。然后通过dispatchTouchEvent向父容器层层通知事件已被消费
 I/CGroup: dispatchTouchEvent-------->> res=true #通过dispatchTouchEvent向父容器层通知事件已被某个view消费
 I/Activity: dispatchTouchEvent-------->> res=true #通过dispatchTouchEvent向父容器层通知事件已被某个view消费

TextView 情景,默认不消费事件

红圈 · 图2

/** CTextView.kt   继承Textiew*/
class CTextView : TextView {
    ... ...
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CTextView", "------dispatchTouchEvent------")
        val res = super.dispatchTouchEvent(event)
        Log.i("CTextView", "dispatchTouchEvent-------->> res=$res")
        return res
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CTextView", "------onTouchEvent------")
        val res = super.onTouchEvent(event)
        Log.i("CTextView", "onTouchEvent-------->> res=$res")
        return res
    }
}

点击自定义的CTextView控件,事件分发流程如下日志打印:
CTextView控件默认不消费事件。当点击CTextView后,事件经TouchInterceptAty过CGroup到CTextView的onTouchEvent。CTextView不消费,事件又会经过CGroup交还给TouchInterceptAty的onTouchEvent。

I/Activity: ------dispatchTouchEvent------ #首次触发Activity的事件分发
I/CGroup: ------dispatchTouchEvent------ #Activity未拦截,事件分发至CGroup
I/CGroup: ------onInterceptTouchEvent------ #拦截判断,是否进行拦截
I/CGroup: onInterceptTouchEvent------>> res=false #不拦截
I/CTextView: ------dispatchTouchEvent------ #由于CGroup未拦截事件,则事件继续分发到子View[CTextView]
I/CTextView: ------onTouchEvent------ #由于CTextView是View且非GroupView,则事件会经过dispatchTouchEvent分发事件到onTouchEvent,目的是让其处理、消费事件。
I/CTextView: onTouchEvent-------->> res=false #不消费事件
I/CTextView: dispatchTouchEvent-------->> res=false #当前CTextView不消费事件,dispatchTouchEvent=false。并向父容器交还没有View消费的事件[交还处理权]。
I/CGroup: ------onTouchEvent------ #交还事件给父容器的onTouchEvent处理
I/CGroup: onTouchEvent------>> res=false #父容器的onTouchEvent也不消费不处理
I/CGroup: dispatchTouchEvent-------->> res=false #不处理则继续向祖父容器交还没有View处理的事件[交还处理权]
I/Activity: ------onTouchEvent------ #交事件给祖父容器onTouchEvent处理
I/Activity: onTouchEvent------>> res=false #祖父容器的onTouchEvent也不消费不处理
I/Activity: dispatchTouchEvent-------->> res=false #没有子View处理,则向更高层View告知交还事件

依据打印的执行日志绘制时序图 ~
在这里插入图片描述

定义dispatchTouchEvent消费了事件

修改CGroup方法dispatchTouchEvent,令其直接return true。当CGroup由此消费了事件,则事件不会在CGroup中继续向下层分发。

/** 修改类CGroup.kt的分发事件方法 */
class CGroup: GroupView{
    ... ....
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CGroup", "------dispatchTouchEvent------")
        // val res = super.dispatchTouchEvent(event)
        val res = true // 
        Log.i("CGroup", "dispatchTouchEvent-------->> res=$res")
        return res
    }
	... ...
}
#如果在事件分发方法dispatchTouchEvent消费,则事件不再继续分发。
#而是将事件已消费的事实告知Activity
I/Activity: ------dispatchTouchEvent------
I/CGroup: ------dispatchTouchEvent------
I/CGroup: dispatchTouchEvent-------->> res=true #事件已消费,并向上层告知
I/Activity: dispatchTouchEvent-------->> res=true

定义onInterceptTouchEvent拦截事件,但不消费事件

修改CGroup方法onInterceptTouchEvent,令其直接return true。此时CGroup拦截事件但不消费,则事件会在CGroup中向上层交还处理权。

/** 修改类CGroup.kt的事件拦截方法 */
class CGroup: GroupView{
    ... ....
        override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        Log.i("CGroup", "------onInterceptTouchEvent------")
//        val res = super.onInterceptTouchEvent(ev)
        val res = true
        Log.i("CGroup", "onInterceptTouchEvent------>> res=$res")
        return res
    }
	... ...
}
I/Activity: ------dispatchTouchEvent------
I/CGroup: ------dispatchTouchEvent------
I/CGroup: ------onInterceptTouchEvent------ #进入拦截判断,是否进行事件拦截
I/CGroup: onInterceptTouchEvent------>> res=true #事件被拦截
I/CGroup: ------onTouchEvent------ #事件被拦截后,则会将事件交由onTouchEvent消费处理
I/CGroup: onTouchEvent------>> res=false #表明该方法不消费[不处理]事件
I/CGroup: dispatchTouchEvent-------->> res=false #没有view消费事件,则将事件处理权交还上层控件
I/Activity: ------onTouchEvent------ #事件处理权,交给了Activity的方法onTouchEvent
I/Activity: onTouchEvent------>> res=false
I/Activity: dispatchTouchEvent-------->> res=false

Button情景,默认消费事件

红圈 · 图3

/** CButton.kt    继承Button*/
class CButton : Button {
    ... ... 
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CButton", "------dispatchTouchEvent------")
        val res = super.dispatchTouchEvent(event)
        Log.i("CButton", "dispatchTouchEvent-------->> res=$res")
        return res
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("CButton", "------onTouchEvent------")
        val res = super.onTouchEvent(event)
        Log.i("CButton", "onTouchEvent-------->> res=$res")
        return res
    }
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        setMeasuredDimension(800, 500)
    }

}

点击自定义的CButton控件,事件分发流程如下日志打印:
[CButton事件的分发流程同CView]

 I/Activity: ------dispatchTouchEvent------
 I/CGroup: ------dispatchTouchEvent------
 I/CGroup: ------onInterceptTouchEvent------
 I/CGroup: onInterceptTouchEvent------>> res=false
 I/CButton: ------dispatchTouchEvent------
 I/CButton: ------onTouchEvent------
 I/CButton: onTouchEvent-------->> res=true #该CButton消费了事件
 I/CButton: dispatchTouchEvent-------->> res=true
 I/CGroup: dispatchTouchEvent-------->> res=true
 I/Activity: dispatchTouchEvent-------->> res=true

源码分析

事件分发的调用关系

基于android API 30 分析当前情景同红圈 · 2图情景


// ********************* Activity.java *********************
/** Activity的方法dispatchTouchEvent中有几行事件分发的代码 */
public boolean dispatchTouchEvent(MotionEvent ev) {
        ... .... ..
        if (getWindow().superDispatchTouchEvent(ev)) { // 事件分发的起点
            return true;
        }
        return onTouchEvent(ev);
    }
/** 点击getWindow()查看方法 */
public Window getWindow() {
   return mWindow;
}

/** mWindow[Window],作为抽象类,其实现类是PhoneWindow。且实例化在attach方法中 */
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, 
        IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        ... ... ...
    // getWindow().superDispatchTouchEvent(ev)真正执行是由mWindow[PhoneWindow]的实例
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ... ... ...
}

// ********************* PhoneWindow.java *********************
/** 进入到PhoneWindow的方法superDispatchTouchEvent会发现*/
/** 事件的分发由Activity执行,传递到PhoneWindow,并将事件分发又交给了mDecor[DecorView]执行*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

// ********************* DecorView.java *********************
/** 从下面的继承关系知,事件分发执行到ViewGroup后,将事件分发交给ViewGroup来继续执行。*/
public class DecorView extends FrameLayout 
public class FrameLayout extends ViewGroup

// ********************* ViewGroup.java *********************
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
	... .... ....
    if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) { // mFirstTouchTarget 非空表示子View消费了事件
                
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev); // ViewGroup中是否通过onInterceptTouchEvent进行事件的拦截[默认不拦截intercepted = false]
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false; // 不拦截
        }
    .. ... ....
    if (!canceled && !intercepted) {// 当不是取消事件,且事件未被拦截继续执行逻辑
    	... .....
    	// 仅当canceled=false时,执行遍历子View,并分发事件到child
    	// 会执行dispatchTransformedTouchEvent方法内部canceled=false时的事件分发程序。
    	if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 
    	.... ....
    	  ... ....
    	if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            // 无触摸目标。即当前ViewGroup中没有子View,
            // 执行dispatchTransformedTouchEvent的内部调用,chilidView=null会执行从View中继承的分发方法'dispatchTouchEvent'
            // 然后分发方法内部又默认调用onTouchEvent。
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                      TouchTarget.ALL_POINTER_IDS);
        } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 该分发方法执行内部,会根据cancelChild的值执行对应的'child.dispatchTouchEvent(transformedEvent);'实现事件的继续分发。
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                       ... ...
                    }
                    ... ...
                }
            }
        ... ... ...
        ......
    }   
}

/** dispatchTouchEvent方法中如何执行dispatchTransformedTouchEvent?  */
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
            	// 如果ViewGroup中没有子View[child ]
            	// 实质上是通过super调用dispatchTouchEvent来实现事件分发,并将事件分发给自己[ViewGroup]的onTouchEvent]
                handled = super.dispatchTouchEvent(event); 
            } else {
            	// 如果ViewGroup中有子View[child ]
            	// 实质上是通过child调用View的dispatchTouchEvent来实现View的事件分发[View的dispatchTouchEvent又会将事件分发给onTouchEvent]
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
	... ......
    }

// ********************* View.java *********************
 public boolean dispatchTouchEvent(MotionEvent event) {
 	... ... ...
     // If the event should be handled by accessibility focus first.
     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;
        }
		// 如果未设置OnTouchListener则事件就会分发到onTouchEvent
		// 所以默认事件最终会分发到这里,然若result=true则消费该事件
        if (!result && onTouchEvent(event)) { 
            result = true;
        }
    }
... .... 
        return result;// result=false 不消费该事件
    }

// ********************* ============== ********************
// ********************* View消费了事件 *********************
// ********************* ============== ********************
/** View消费了事件,onTouchEvent返回true,则dispatchTouchEvent必然也返回true。
  *即child.dispatchTouchEvent(...)=true,child消费了事件。
  *根据递归的调用关系,ViewGroup中事件分发方法dispatchTouchEvent的内部会调用dispatchTransformedTouchEvent(,,,)[等同执行child.dispatchTouchEvent(...)]
  *且使得handled = true。由此ViewGroup.dispatchTouchEvent(...)=true,
  *同理Activity.dispatchTouchEvent(...)=true
  */


// ********************* ============== ********************
// ********************* View不消费事件 *********************
// ********************* ============== ********************
/** View不消费事件,onTouchEvent返回false,则dispatchTouchEvent必然也返回false。
  *由此View会将事件重新交还给ViewGroup的onTouchEvent处理,onTouchEvent无法处理[不消费],
  *则继续交由ViewGroup的dispatchTouchEvent处理。若仍不能处理,则继续将事件交还到Activity的onTouchEvent、dispatchTouchEvent进行处理消费。
  */

dispatchTouchEventonTouchEvent返回值相同


/** View.java 的事件分发方法 'dispatchTouchEvent'  */
public boolean dispatchTouchEvent(MotionEvent event) {
           .. .....
        if (onFilterTouchEventForSecurity(event)) {
           . ... ... ..
            //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;
            }
        }
	... ..... ..
        return result;
    }

为什么dispatchTouchEvent返回值会与onTouchEvent返回值一致?
从上述代码片中看,如果没有对该View添加Touch的监听事件[mOnTouchListener ]。那么代码就会执行代码方法onTouchEvent(event),而onTouchEvent方法的执行会直接影响dispatchTouchEvent的返回值result

由此又引出了另一个问题:
onTouch方法和 onTouchEvent方法,执行的先后顺序是怎样的?
继续看上面代码片,变量li[mListenerInfo]、li.mOnTouchListener、li.mOnTouchListener.onTouch
三个变量条件若能得到满足,执行顺序则方法onTouch优先于onTouchEvent

/** 设置触摸监听 */
public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}
/** 设置触摸监听后,会执行ListenerInfo的实例化并赋值给mListenerInfo 。 */
@UnsupportedAppUsage
ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

//=======================================
/**  由此可见,当为触摸事件设置监听时,  */
/**  `onTouch`方法执行先于`onTouchEvent`方法的逻辑就产生了。 */
//=======================================

自定义一个组件

自定义城市导航组件,事件分发+事件处理逻辑+绘制算法

参考文章
https://zhuanlan.zhihu.com/p

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
# 委托与事件关系 委托和事件是 C# 中两个重要的概念,它们之间存在着紧密的关系。 委托是一种类型,它能够存储对方法的引用,可以将委托看作是一个函数指针,可以将其传递给其他方法作为参数或者保存在实例变量中。通过委托,可以将方法作为一种数据类型来操作。 事件是一种特殊的委托,它用于在程序中发生某些特定的动作或状态改变时通知其他对象。事件通常与委托一起使用,委托用于保存事件处理程序的引用,而事件则用于触发委托。 # 举例说明 以下是一个简单的示例,说明委托和事件的使用。 ```C# using System; // 定义一个委托类型,用于保存方法的引用 delegate void MyDelegate(int num); // 定义一个包含事件的类 class MyClass { // 声明一个事件,类型为 MyDelegate 委托 public event MyDelegate MyEvent; // 触发事件的方法 public void TriggerEvent(int num) { // 如果事件不为空,则触发事件调用事件处理程序 if (MyEvent != null) { MyEvent(num); } } } // 定义一个事件处理程序 class MyEventHandler { // 定义一个方法,用于处理事件 public void HandleEvent(int num) { Console.WriteLine("Event handled: " + num); } } // 主程序入口 class Program { static void Main() { // 创建一个 MyClass 对象 MyClass myClass = new MyClass(); // 创建一个 MyEventHandler 对象 MyEventHandler myHandler = new MyEventHandler(); // 将委托实例化为 MyDelegate 委托类型,并将事件处理程序 myHandler.HandleEvent 作为参数传递给委托 MyDelegate myDelegate = new MyDelegate(myHandler.HandleEvent); // 将委托保存在 MyClass 对象的事件中 myClass.MyEvent += myDelegate; // 触发事件,并传递参数 myClass.TriggerEvent(123); } } ``` 上述示例中,定义了一个委托类型 MyDelegate,它用于保存方法的引用。然后定义了一个包含事件 MyEvent 的 MyClass 类,这个事件的类型就是 MyDelegate 委托。在 MyClass 类中,还定义了一个 TriggerEvent 方法,用于触发事件。如果事件不为空,则调用事件处理程序。 然后定义了一个 MyEventHandler 类,它包含了一个 HandleEvent 方法,用于处理事件。最后,在主程序中,创建了 MyClass 和 MyEventHandler 对象,并将 MyEventHandler 对象的 HandleEvent 方法作为参数传递给了 MyDelegate 委托。然后将委托保存在 MyClass 对象的事件中,并触发事件。 当 TriggerEvent 方法被调用时,事件 MyEvent 会被触发,委托 MyDelegate 中保存的事件处理程序 MyEventHandler.HandleEvent 会被调用,输出 "Event handled: 123"。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值