触摸事件的传递过程

触摸事件的传递过程

事件传递过程涉及的几个API如下:

  • dispatchTouchEvent : 分发事件。如果返回true,表示事件分发下去后被处理了;返回false,则表示分发下去后没有被任何view处理。
  • onInterceptTouchEvent :拦截事件。如果返回true,则表示拦截事件。如果返回false,则表示不拦截。这里拦截的是本来要传给子View的事件,所以这个方法是ViewGroup独有的。
  • onTouchEvent : 处理事件。如果返回true,则表示处理事件,如果返回false 则表示不处理事件。

事件传递首先从父容器ViewGroup开始,父容器调用dispatchTouchEvent 分发事件,在该方法内部会调用onInterceptTouchEvent 判断是否为拦截事件,如果返回为true,表示对事件进行拦截,事件会直接传递给VIewGroup的onTouchEvent方法。如果返回为false(默认就是false),那么事件就会传递给View的dispatchTouchEvent 方法,View中没有onInterceptTouchEvent 方法,会直接调用onTouchEvent的方法处理事件。 如果View的onTouchEvent 的方法返回true,那么表示事件被处理,事件传递结束。
如果返回false 表示不处理,事件又会传递给父容器ViewGroup的onTouchEvent 方法处理。

事件传递细节

事件传递和Activity的关系

触摸事件是由Activity传递给DecorView的。
Activity中dispatchTouchEvent()
在这里插入图片描述

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //获取到Activity的PhoneWindow,将事件传递给其中的DecorView,
        //DecorView再将事件分发给他的子View
        if (getWindow().superDispatchTouchEvent(ev)) {
         	//如果事件分发下去有子View进行消费,就直接返回true
            return true;
        }
        //如果DecorView将事件分发下去后没有任何View处理,就会交给Activity的onTouchEvent 处理
        return onTouchEvent(ev);
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这就进入了ViewGroup的方法中了。

ACTION_DOWN 没有被处理后

ACTION_DOWN 没有被处理后的其他事件会被DecorView拦截。判断逻辑:

 // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

这段代码用于检查是否拦截子View事件的。当actionMasked !=MotionEvent.ACTION_DOWN 并且mFirstTouchTarget == null 是才会拦截事件。
mFirstTouchTarget 是TouchTarget 对象,用来保存处理触摸事件View 以及触摸点的id值,当ACTION_DOWN 事件分发下去后,如果有子View处理事件,也就是其onTouchEvent返回了true,那么ViewGroup 就会处理这个事件的子View用mFirstTouchTarget 保存。如果没有子View处理,那么mFirstTouchTarget 就不会被赋值。
结论:如果 ACTION_DOWN 分发下去没有被处理,会造成.mFirstTouchTarget ==null 后续事件ACTION_MOVE 和 ACTION_UP 就满足了actionMasked !=MotionEvent.ACTION_DOWN ,将导致这些事件被拦截掉。ViewGroup自己处理对应的事件。

点击位置没有子View时的处理

在dispatchTouchEvent时,会去检查子View的可见性及子View所处的位置。
检查子view的可见性

    /**
     * Returns whether this view can receive pointer events.
     *
     * @return {@code true} if this view can receive pointer events.
     * @hide
     */
    protected boolean canReceivePointerEvents() {
        return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
    }

检查子View所在的位置

/**
     * Returns true if a child view contains the specified point when transformed
     * into its coordinate space.
     * Child must not be null.
     * @hide
     */
    @UnsupportedAppUsage
    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempLocationF();
        point[0] = x;
        point[1] = y;
        //将触摸事件位置转换成子View为坐标系的位置
        transformPointToViewLocal(point, child);
        //判断转换后的位置(X,Y) = (point[0],point[1]) 是否在子View的内部。
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }

 /**
     * @hide
     */
    @UnsupportedAppUsage
    public void transformPointToViewLocal(float[] point, View child) {
        point[0] += mScrollX - child.mLeft;
        point[1] += mScrollY - child.mTop;

        if (!child.hasIdentityMatrix()) {
            child.getInverseMatrix().mapPoints(point);
        }
    }


 /** 
     * Utility method to determine whether the given point, in local coordinates,
     * is inside the view, where the area of the view is expanded by the slop factor.
     * This method is called while processing touch-move events to determine if the event
     * is still within the view.
     * 此方法在View类中
     * @hide
     */
    @UnsupportedAppUsage
    public boolean pointInView(float localX, float localY, float slop) {
        return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
                localY < ((mBottom - mTop) + slop);
    }

点击位置有多个子View重叠的处理

ViewGroup会将所有的子View存入到一个数组中,默认是按照添加进这个ViewGroup的顺序排序,在ViewGroup的dispatchTouchEvent 的方法中,会调用buildTouchDispatchChildList() 方法对子View进行排序,在默认的排序基础上,先根据子View的z值大小,再根据绘制顺序进行调整,Z值越大,绘制顺序越靠前的子View会排在数组的最后面。Z值是主要因素,Z值一致时,才会根据绘制顺序排序。在事件传递时,会从数组的后面往前遍历所有的子View,直到有子View消费事件。

在这里插入图片描述

总结下来基本就最后加入ViewGroup的子View最先获取到事件。

拦截子View事件的处理

在这里插入图片描述

触摸事件坐标的转换

因为在一个触摸事件的坐标在ViewGroup和子View的中的坐标可能不一样,怎么进行坐标的转换的?
在ViewGroup的dispatchTouchEvent 方法中会调用dispatchTransformedTouchEvent 方法来转换坐标并将事件传递给子View。
在这里插入图片描述

onTouch 和 onTouchEvent 的关系

处理触摸事件通常有两种做法:一是给控件设置触摸事件的监听器,二是覆写onTouchEvent() 方法,那么监听器中的onTouch方法和View本身的onTouchEvent 方法 都是处理事件,他们之间的有什么区别和联系呢?
在这里插入图片描述
View在事件分发时,在监听器不为空的情况下,会先调用触摸事件监听器的onTouch 方法,如果onTouch 返回true,表示事件在监听器中消费了,result 会被标记成true,那么后面就不会调用onTouchEvent方法,反之则会调用。

参考自 Android 应用开发进阶

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值