【Android】触摸事件相关方法总结

触摸事件相关方法


方法

中文简称            

作用

被谁调用

ViewGroup专有

覆盖

调用

dispatchTouchEvent


分发

找到合适的View(自己或是孩子)处理触摸事件

 父控件的dispatchTouchEvent,具体是dispatchTransformedTouchEvent

 

99%不覆盖,覆盖也会调用super的此方法

99.9%不调用

onIntersetTouchEvent

拦截

返回true代表交由自己处理,返回false正常分发

 自己的dispatchTouchEvent

可能覆盖。

一般需要判断触摸事件进行相应的返回,而不是直接返回true或false

不调用

onTouchEvent

处理

对用户触摸做出反馈

 VG:dispatchTransformedTouchEvent

 View:dispatchTouchEvent

 

自定义控件通常会覆盖此方法

特殊情况会调用

setOnTouchListener

设置触摸监听

在控件的外部获得触摸事件

 开发人员调用

 

覆盖监听的onTouch方法

需要获知控件上的触摸事件

dispatchTransformedTouchEvent

分发变形事件

对触摸事件的位置进行转换

 自己的dispatchTouchEvent

私有方法不能覆盖

私有方法不能调用

performClick

触发点击事件

触发点击监听的onClick方法

 View的 onTouchEvent

 

不覆盖

模拟用户点击

requestDisallowInterceptTouchEvent

要求不拦截

要求控件不拦截触摸事件

开发人员调用

不覆盖

如果不想被父控件抢夺触摸事件,需要调用父控件的此方法

 


 

特殊说明

 

方法

要点

dispatchTouchEvent(View)

 ①    检查是否有触摸监听,如果有则进②,如果没有则进入④

 ②    调用触摸监听的onTouch方法,如果onTouch方法返回true,则进入③,如果false,则进入④

 ③    不会调用自己的onTouchEvent方法,返回true,代表分发成功,结束

 ④    调用自己的onTouchEvent方法,如果返回true则代表分发成功,结束。如果返回false,则分发失败,结束

dispatchTouchEvent(ViewGroup)

 ①     检查是否被调用过requestDisallowInterceptTouchEvent方法[1],如果是则进入③,如果否则进入②,

 ②    调用自己的onInterceptTouchEvent 检查返回值,如果true进入④;false 进入③

 ③    倒序遍历孩子(没有孩子,进入④,有孩子,进入(3.1)
  (3.1) 检查触摸事件的位置,找到位置对应的孩子,调用孩子的分发触摸事件的方法,进入(3.2)
  (3.2) 如果孩子的分发事件返回了true,结束遍历,结束  如果孩子的分发方法返回了false,下一次遍历;如果所有的孩子分发都返回了false,进入④

 ④    调用父类的dispatchTouchEvent,也就是View的dispatchTouchEvent方法,结束

 

对于一组触摸事件:

 1       当一组触摸事件的第一个事件,分发成功的话,会记录下来,在分发下一个触摸事件的时候,就不会进行遍历查找了

 2        如果原来分发给某个孩子在处理了,但onIntersetTouchEvent根据情况返回true了,那么会制作一个CANCEL类型的触摸事件分发给孩子(孩子会递归分发给孙子),代表这一组触摸事件结束了,没有后续事件了。

 

dispatchTransformedTouchEvent

 1       只有ViewGroup才有,而且是一个私有方法,不能覆盖或调用

 2       dispatchTouchEvent(ViewGroup)的③和④,都是通过此方法间接实现·

 3       如果此方法的第三个参数View如果是null,则会调用自己的onTouchEvent方法,如果是不是null,测调用View的

 4       在调用孩子的dispatchTouchEvent时,会根据滚动和孩子的位置进行坐标的偏转

onInterceptTouchEvent

 1       应该根据情况返回true,比如ViewPager会判断是偏向于水平滑动,并且滑动达到了一定距离;再比如ScrollView会检测是垂直的移动,才会拦截。

onTouchEvent

 1返回ture,则代表期望持续获得到触摸事件

 2 up类型的事件表示时间结束了,cancel类型的事件也是一样

performClick

 这个方法会在View的onTouchEvent的up事件中被调用

 当你想模拟用户点击某个View,可以调用View上的performClick方法

requestDisallowInterceptTouchEvent

 1       此方法会在mGroupFlags(用二进制来保存多种状态)添加上FLAG_DISALLOW_INTERCEPT,在分发的时候会检查是否具有此  FLAG_DISALLOW_INTERCEPT

 2       此方法会递归调用父控件的此方法

 3       部分ViewGroup可能会重写此方法,导致在某些情况下分发触摸事件仍然会抢夺孩子的触摸事件,比如SwipeRefreshLayout。

setOnTouchListener

 

 

自身的onTouchEvent

监听的onTouch

持续收到触摸事件

结果

true

true

onTouchEvent 方法不会被调用

false

true

true

false

onTouchEvent 方法会被调用

false

false

 



 

关键代码

方法

 

dispatch TouchEvent (View)

boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
    //各种各样监听的集合
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null       // 检查是否具有触摸监听
            && (mViewFlags & ENABLED_MASK) == ENABLED   // 检查是否enable
            && li.mOnTouchListener.onTouch(this, event) // 调用触摸监听的onTouch方法
            ) {
        // 如果onTouch方法返回true,则分发结果是true,否则用onTouchEvent的返回值作为结果
        result = true;
    }
    if (!result && onTouchEvent(event)) {// 如果onTouch方法返回true,则不会调用onTouchEvent
        result = true;
    }
}
return result;

 

dispatch TouchEvent (ViewGroup)

// 是否处理,这个是返回值
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 清除上一组触摸事件遗留的各种信息
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
  
    // 检查是否拦截
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        // 检查是否不允许拦截
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {// 如果允许拦截则会调用 onInterceptTouchEvent
            intercepted = onInterceptTouchEvent(ev);
        } else {
            intercepted = false;
        }
    } else {
        intercepted = true;
    }
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
        // 如果没有取消,也没有拦截,则进行正常的分发
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            // 各种down事件才遍历孩子
            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final View child = (preorderedList == null)
                            ? children[childIndex] : preorderedList.get(childIndex);
                    // 各种跳出循环和下一次循环
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        //这个方法内部会把需要消费触摸事件的孩子记录为 mFirstTouchTarget
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }
        }
    }
  
    // 如果孩子都不要触摸事件,则自己处理,注意dispatchTransformedTouchEvent的第三个参数是null
    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            // down已经分发过了,不再重发分发
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                // 这里分发的是除down类型的各种触摸事件
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
            }
            target = next;
        }
    }
}
return handled;

dispatch Transformed TouchEvent

    // 注意第三个参数
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
    final boolean handled;
  
    // 发送取消类型事件,对于取消类型事件,位置不重要,所以不需要进行偏移
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    // 部分代码跟多点触摸相关,重点关注child = null 调用父类View的分发方法,
    // 不然会对触摸事件中的位置进行偏移再调用孩子的分发方法,这就是此方法名称的核心
    if (/*...*/) {
        if (/*...*/) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                //根据滚动孩子的位置对触摸事件进行偏移,
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                // 发送偏移后的触摸事件,保证发送给孩子的触摸事件的xy是相对于其左上角的位置
                handled = child.dispatchTouchEvent(event);
                // 偏移回来,保证在后续的处理中位置是对的
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
    } 
    // 逻辑与上相同,略
    return handled;
}

 

request Disallow Intercept TouchEvent

// 检查上次是否已经设置过一样的flag,如果重复就及时返回,不处理
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
    // We're already in this state, assume our ancestors are too
    return;
}
if (disallowIntercept) {// 如果不允许(即调用此方法传入的是true,则给 mGroupFlags 添加上 FLAG_DISALLOW_INTERCEPT
    mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else { // 如果允许,则给 mGroupFlags 删除掉 FLAG_DISALLOW_INTERCEPT
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 将会递归调用父控件的此方法
if (mParent != null) {
    mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}

onTouchEvent(View)

//...
 
if ((viewFlags & ENABLED_MASK) == DISABLED) {
    if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
    }
     // 如果Viewdisable的,就及时返回了,不会执行后续的逻辑
   
return (((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}

//...
// 如果此控件是 CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE 的才会返回true,否则返回false
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
    
   (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
    switch (action) {// 判断触摸事件类型
       
case MotionEvent.ACTION_UP:
            if (/*...*/) {
                 performClick();// 在某种条件下触摸performClick方法
           
}
            break;

        case MotionEvent.ACTION_DOWN:
            if (/*...*/) { } else {
                checkForLongClick(0);//检测长按事件
           
}
            break;
        case MotionEvent.ACTION_CANCEL:
            //...
            // 还原某些成员变量
           
break;
        case MotionEvent.ACTION_MOVE:
            //...
            break;
    }
return true;

setOn Touch Listener

// ListenerInfo中的对象赋值称传入的监听对象
getListenerInfo().mOnTouchListener = l;

 

performClick

final boolean result;
final ListenerInfo li = mListenerInfo;
// 检查是否有点击监听
if (li != null && li.mOnClickListener != null) {
    // 播放音效
    playSoundEffect(SoundEffectConstants.CLICK);
    // 调用点击监听的onClick方法
    li.mOnClickListener.onClick(this);
    result = true;
} else {
    result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;

 

 



[1] 每次down或up的时候会重置,不同的API版本不同,但保证一组触摸事件开始或之后可以恢复所有触摸事件相关状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值