触摸手势详解

ViewRootImpl源码分析事件分发
在这里插入图片描述
​​
当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件;若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MOVE的触摸事件,系统中预定义的用来判断用户手指在屏幕上的滑动是否是一个ACTION_MOVE动作的这个距离常量叫做TouchSlop,可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取。

1 onTouchEvent与onTouch

View.java(基于android2.3.3):

public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {//此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法
            return true;
        }
     
        if (onTouchEvent(event)) {// onTouchEvent参考下面源码
            return true;
        }
public boolean onTouchEvent(MotionEvent event) {
    ...
 
    // 当前onTouch的组件必须是可点击的比如Button,ImageButton等等,此处CLICKABLE为true,才会进入if方法,最后返回true。
// 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false,最后返回false。当然会有特殊情况,如果给这些View设置了onClick监听器,此处CLICKABLE也将为true,参考下面源码
    if (((viewFlags & CLICKABLE) == CLICKABLE ||   
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                ...
                            if (!post(mPerformClick)) {
                                performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法,源码下面源码
                            }
                 ...
                break;
 
            case MotionEvent.ACTION_DOWN:
               ...
                break;
 
            case MotionEvent.ACTION_CANCEL:
                ...
                break;
 
            case MotionEvent.ACTION_MOVE:
               ...
                break;
        }
        return true;
    }
 
    return false;
}
public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}
public boolean performClick() {
    ...
 
    if (li != null && li.mOnClickListener != null) {
        ...
        li.mOnClickListener.onClick(this);
        return true;
    }
 
    return false;
}

总结:

只有我们注册OnTouchListener时重写的onTouch()方法中返回false ——> 执行onTouchEvent方法 ——> 导致onClick()回调方法执行

onTouch()方法返回true ——> onTouchEvent方法不执行 ——> 导致onClick()回调方法不会执行

2 ViewGroup

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
 
    ...
     
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        //onInterceptTouchEvent返回false,说明向下传递
        //onInterceptTouchEvent返回true,说明拦截
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
         
            ...
            // 伪代码如下:
            //1,找到当前控件子控件
            //2,判断当前touch的点的坐标(x,y)在哪个子控件的矩形区域内
            //3,判断当前子控件是viewgroup的子类对象,还是view的子类对象
                //3.1  如果是viewgroup的子类: 调用其dispatchTouchEvent方法,上述操作再来一遍
                //3.2  view 尝试让当前view去处理这个事件(
                             true,dispatchTouchEvent方法结束,并且返回true
                             false,dispatchTouchEvent继续向下执行)
                 
            ...
             
        }
    }
     
    ...
     
     target = mMotionTarget
    //target一定是null
    if (target == null) {
     
        ...
         
        //调用当前viewgroup的父View的处理事件的方法
        return super.dispatchTouchEvent(ev);
    }
     
   ...
    
}
 public boolean dispatchTouchEvent(MotionEvent ev) {   
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
	//处理down事件,主要目的是寻找target
        if (action == MotionEvent.ACTION_DOWN) {
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {    
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                           //找到了target
                            if (child.dispatchTouchEvent(ev))  {
                                mMotionTarget = child;
                                return true;
                            }
                        
                        }
                    }
                }
            }
        }
      
        //如果允许对后续事件进行拦截,并且拦截成功的话
        //通过这段代码可以知道,在childView中可以调用
      if (!disallowIntercept && onInterceptTouchEvent(ev)) {       
            if (!target.dispatchTouchEvent(ev)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            }
            mMotionTarget = null;
         
            return true;
        }

    
        //后续事件:ACTION_MOVE系列事件和up事件都交给target处理
        return target.dispatchTouchEvent(ev);
    }

处理拦截事件有两种方式:
1 直接在父类中重写onInterceptTouchEvent(),让其返回true或false;
2 在子类中调用父类的requestDisallowInterceptTouchEvent(),让父类决定是否拦截。

这里写图片描述
上图是view事件分发的整个流程,注意Decorview的调用了两次。
1、Android中,Touch事件的分发分服务端和应用端。在Server端由WindowManagerService负责采集和分发的,在client端则是由ViewRootImpl(内部有个mView,也即Decorview ,负责控制View树的UI绘制和事件消息的分发)负责分发的。
2、WMS在启动之后,经过逐层调用,会在native层启动两个线程:InputReaderThread和InputDispatchThread,前者用来读取输入事件,
后者用来分发输入事件,输入事件经过nativie层的层层传递,最终会传递到Java层的ViewRootImpl中,调用
ViewPostImeInputStage(ViewRootImpl的内部类)中的各个方法来分发不同的事件,而Touch事件是通过processPointerEvent方法进行分发的。
3、processPointerEvent方法中调用mView.dispatchPointerEvent(event)方法,这里的mView就是DecorView,而dispatchPointerEvent方法会对event进行判断,如果是Touch事件的话,就调DecorViewd的dispatchTouchEvent进行事件分发,而Decorview的dispatchTouchEvent重写了,其内部调用了CallBack接口的dispatchTouchEvent方法,因为Activity实现了CallBack接口,所以实际调用的是Activity的dispatchTouchEvent方法,Activity的dispatchTouchEvent方法内部调用phongwindow的superDispatchTouchEvent方法,这个方法实际上调用了DecorView的superDispatchTouchEvent方法,因此到目前位置DecorView被调用了两次。然后DecorView的superDispatchTouchEvent方法中调用了ViewGroup的dispatchTouchEvent方法正式进行View树事件的派发

1一定要记住dispatchTouchEvent是所有View 的Touch事件入口,在它里面调用了onInterceptTouchEvent和OnTouchEvent两个方法以及被包含类的dispatchTouchEvent方法(如果此View是布局类的话),相当于责任链模式,dispatchTouchEvent对MotionEvent进行一级级的分发。
当View的dispatchTouchEvent方法返回true时,表示此View成功处理了这次事件,而具体由谁处理的,可能是此View的某个被包含类(如果有的话)处理的(譬如某个被包含类的dispatchTouchEvent返回true),也可能就是此View本身处理的(譬如此View的OnTouchEvent返回true)。
一个LinearLayout中有一个TextView,当触摸这个TextView时触摸事件会先打到LinearLayout,然后再到达TextView。如果LinearLayout将触摸事件拦截了,那么TextView就会收到一个CANCEL事件,其他触摸就收不到了。

如果ViewGroup的onInterceptTouchEvent方法中返回false,每个后续的事件(从当前事件到最后ACTION_UP事件)将会先分发到onInterceptTouchEvent中,然后再交给目标子控件的onTouchEvent处理 (前提是子控件的onTouchEvent返回是true )。
如果ViewGroup的onInterceptTouchEvent返回true,onInterceptTouchEvent方法中将不会收到后续的任何事件,也就是跳过onInterceptTouchEvent方法,目标子控件中除了ACTION_CANCEL外也不会接收所有这些后续事件,所有的后续事件将会直接被交付到ViewGroup的onTouchEvent()方法中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值