Android ViewGroup的事件分发流程分析

前言

前面一篇博客我们简单分析了View的onTouchListener、onTouchEvent、onClickListener执行时机,并总结了一些结论,这一篇我们分析下ViewGroup的事件分发流程。Android View的onTouchListener、onTouchEvent、onClickListener执行时机

示例演示

布局显示

ViewGroup事件分发
这里我们定义了一个TouchViewGroup继承自LinearLayout和一个TouchView继承自View,其中TouchView包裹在TouchViewGroup中。

相关代码
  • 布局文件
<androidx.constraintlayout.widget.ConstraintLayout 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=".MainActivity">

   <com.crystal.view.TouchViewGroup
       android:layout_width="match_parent"
       android:layout_height="300dp"
       android:background="@color/red"
       android:gravity="center"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent">

       <com.crystal.view.TouchView
           android:id="@+id/touchView"
           android:layout_width="100dp"
           android:layout_height="100dp"
           android:background="@color/green" />
   </com.crystal.view.TouchViewGroup>


</androidx.constraintlayout.widget.ConstraintLayout>
  • TouchView
class TouchView : View {
   constructor(context: Context?) : super(context)
   constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
   constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
       context,
       attrs,
       defStyleAttr
   )

   override fun dispatchTouchEvent(event: MotionEvent): Boolean {
       Log.d(
           "TAG",
           "View --> dispatchTouchEvent:" + if (event.action == ACTION_DOWN) "ACTION_DOWN" else if (event.action == ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
       )
       return super.dispatchTouchEvent(event)
   }

   override fun onTouchEvent(event: MotionEvent): Boolean {
       Log.d("TAG", "View --> onTouchEvent:" + if (event.action == ACTION_DOWN) "ACTION_DOWN" else if (event.action == ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP")
       return super.onTouchEvent(event)
   }
}
  • TouchViewGroup
class TouchViewGroup : LinearLayout {

   constructor(context: Context?) : super(context)
   constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
   constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
       context,
       attrs,
       defStyleAttr
   )

   override fun dispatchTouchEvent(event: MotionEvent): Boolean {
       Log.d(
           "TAG",
           "ViewGroup --> dispatchTouchEvent:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
       )
       return super.dispatchTouchEvent(event)
   }

   override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
       Log.d(
           "TAG",
           "ViewGroup --> onInterceptTouchEvent:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
       )
       return super.onInterceptTouchEvent(event)
   }

   override fun onTouchEvent(event: MotionEvent): Boolean {
       Log.d(
           "TAG",
           "ViewGroup --> onTouchEvent:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
       )
       return super.onTouchEvent(event)
   }

}
  • Activity中代码
       val touchView = findViewById<TouchView>(R.id.touchView)
       touchView.setOnTouchListener { v, event ->
           Log.d("TAG", "View --> TouchListener:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP")
           false
       }

       touchView.setOnClickListener {
           Log.d("TAG", "View --> OnClickListener")
       }
点击TouchView打印结果
ViewGroup --> dispatchTouchEvent:ACTION_DOWN

ViewGroup --> onInterceptTouchEvent:ACTION_DOWN

View --> dispatchTouchEvent:ACTION_DOWN

View --> TouchListener:ACTION_DOWN

View --> onTouchEvent:ACTION_DOWN

ViewGroup --> dispatchTouchEvent:ACTION_MOVE

ViewGroup --> onInterceptTouchEvent:ACTION_MOVE

View --> dispatchTouchEvent:ACTION_MOVE

View --> TouchListener:ACTION_MOVE

View --> onTouchEvent:ACTION_MOVE

ViewGroup --> dispatchTouchEvent:ACTION_UP

ViewGroup --> onInterceptTouchEvent:ACTION_UP

View --> dispatchTouchEvent:ACTION_UP

View --> TouchListener:ACTION_UP

View --> onTouchEvent:ACTION_UP

View --> OnClickListener

那为什么执行顺序是这样的呢?接下来我们就分析下ViewGroup的dispatchTouchEvent方法。

源码分析

    public boolean dispatchTouchEvent(MotionEvent ev) {
       	...
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // 当点击事件为ACTION_DOWN时
            if (actionMasked == MotionEvent.ACTION_DOWN) {
            	//这里简单理解成清除touchTarget,将mFirstTouchTarget=null
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            // 标志是否拦截事件
            final boolean intercepted;
            //当点击事件为ACTION_DOWN时,命中If(即使第一次mFirstTouchTarget为null)
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                    //这里也比较关键,FLAG_DISALLOW_INTERCEPT标志可以通过调用requestDisallowInterceptTouchEvent方法进行调整【表示子view不想让父控件通过调用onInterceptTouchEvent进行事件拦截】
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //这里默认disallowIntercept是false,则会命中If,调用ViewGroup的onInterceptTouchEvent(ev)
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
              
                intercepted = true;
            }

         	....
         	//这里由于点击事件没有canceled且intercepted = onInterceptTouchEvent(ev)默认为false,则命中If
            if (!canceled && !intercepted) {
                .....
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  	.....
                  	//newTouchTarget没有值并且childrenCount 子View不为空
                    if (newTouchTarget == null && childrenCount != 0) {
         				...
                        final View[] children = mChildren;
                        //注意这里为倒序遍历,例如当ViewGroup为RelativeLayout时,最上层的子View优先响应触摸事件
                        for (int i = childrenCount - 1; i >= 0; i--) {
                          	...
                            newTouchTarget = getTouchTarget(child); //找到触摸的子child
                          	....
                          	// 当child不为null时,调用child.dispatchTouchEvent(event)进行事件分发【本文对应TouchView】
                          	//当child为null时,调用super.dispatchTouchEvent(event)进行事件分发【ViewGroup同样继承自View,其实就是调用View的dispatchTouchEvent方法】
                          	//返回值表示是否消费此事件
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                ...
                                //当dispatchTransformedTouchEvent为true时,会调用addTouchTarget方法,这里会对mFirstTouchTarget进行赋值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                break;
                            }
                        ....
                        }
                       ....
                    }	
             ...
                }
            }

         	//如果没有子view处理本次事件,则调用自己的【父类为View】dispatchTouchEvent(event)进行事件分发【注意这里的第三个参数为null】
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
              
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    	// 如果已经有子view处理事件并且找到了,方法返回true
                        handled = true;
                    } else {
                    // 如果事件被拦截,会走这里
                    // 还没有找到相应的子view,就依次调用每个touchTarget的子view或viewGroup父类(View)的dispatchTouchEvent()
                        // 一旦有一个的dispatchTouchEvent()返回true,整体就返回true
                        // 如果事件被拦截,就销毁target链表
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

          ...
        return handled;
    }

总结

对于ViewGroup而言:

  • 事件分发总是从dispatchTouchEvent开始,如果onInterceptTouchEvent返回false,即不进行事件拦截时,事件会传递到子view,由子view进行处理,子view不消费,则事件会流传回ViewGroup中进行处理;

  • 对于存在多个子View的情况,由于是倒序遍历,事件优先从最上层View向下进行传递;

  • 如果子View的dispatchTouchEvent(event)方法返回true,则表示子View消费了本次事件【dispatchTransformedTouchEvent方法返回true,对mFirstTouchTarget进行了赋值操作】,则后续均交给此View进行处理;

  • 如果子View没有消费事件,则会交给ViewGroup自己进行处理,执行View.dispatchTouchEvent(event)方法;

  • 可以通过调用requestDisallowInterceptTouchEvent方法进行拦截,传递true,表示不允许ViewGroup进行事件拦截;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值