理解Android 触摸分发机制详解

理解Android 触摸分发机制详解

概述

View中的手势流程

        View中关于点击触摸的方法有以下几个相关的方法:
                setOnClickListener()点击监听
                setOnTouchListener()触摸监听(里面重写了onTouch()方法)
                dispatchTouchEvent()事件分发
                onTouchEvent()触摸事件(用于解析处理事件)

  1. setOnClickListener()点击监听
    对于需要点击操作的控件增加一个点击监听,这样才能在点击完成后指定响应
button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //这里编写完成响应的具体代码
            }
        });
  1. onTouchListener()触摸监听
    对需要手势操作的控件增加一个触摸监听,这样才能在有手势的时候进行判断
    例:
button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;  //默认返回 false
            }
        });

3.View的dispatchTouchEvent()事件分发
手指操作屏幕就会触发dispatchTouchEvent()方法,dispatchTouchEvent()源码如下:

public boolean dispatchTouchEvent(MotionEvent event) {
        //mOnTouchListener触摸监听;如果我们给控件设置了setOnTouchListener监听,mOnTouchListener就不为null
        //(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是可以点击的
        //mOnTouchListener.onTouch(this, event),回调控件setOnTouchListener()中的onTouch方法
        //三个条件全部成立,从而整个方法直接返回true
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        //如果上述三个条件一个为false则调用onTouchEvent()
        return onTouchEvent(event);
    }
}

onTouch()与onTouchEvent()方法都是在dispatchTouchEvent中调用的;onTouch()方法调用在onTouchEvent()方法之前调用;如果一个可以点击的控件在onTouch()方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

  1. onTouchEvent()触摸事件
    这方法用于解析处理事件的,源码如下:
public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;
        //一个可点击view可点击被禁用了但是仍然需要使用触摸,只是不响应
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果控件是可以点击的执行switch-case 返回true,否则不执行switch-case返回false
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                //只需要明白在手指抬起的时候最终调用了performClick()方法
                //performClick()方法判断如果设置了setOnClickListener()方法来给控件注册一个点击事件时
                //都会在performClick()方法里回调被点击控件的onClick方法
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        //如果在触摸的时候还没有取得焦点
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }
                        if (!mHasPerformedLongPress) {
                            //去除长按
                            removeLongPressCallback();
                            //只有当处于按下状态时才执行采取点击操作
                            if (!focusTaken) {
                                // 使用Runnable并发送点击,而不是直接调用performClick
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick(); //点击方法
                                }
                            }
                        }
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // 如果发送点击失败了,马上解压缩
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;
                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;
                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();
                    //按钮不要移位
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        //去除按钮回调
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            //去除长按
                            removeLongPressCallback();
                            //需要从按下切换到未按下
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }

ViewGroup中的手势分发流程

        ViewGroup就是一组View的集合,它可以包含多个子View和子VewGroup;例如:LinearLayout、RelativeLayout
        ViewGroup中关于点击触摸的方法有以下几个相关的方法:
                dispatchTouchEvent()事件分发
                disallowIntercept判断事件拦截是否被禁用(默认false;可以通过requestDisallowInterceptTouchEvent()方法设置)
                onInterceptTouchEvent()事件拦截

  1. ViewGroup的dispatchTouchEvent()事件分发
    ViewGroup的dispatchTouchEvent()方法与View中的dispatchTouchEvent()方法不同;源码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        final float xf = ev.getX();
        final float yf = ev.getY();
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                mMotionTarget = null;
            }
            //disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到下面判断中
            //disallowIntercept为false启用拦截方法;true为不启用拦截方法
            //onInterceptTouchEvent为false不拦截;true为拦截
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                ev.setAction(MotionEvent.ACTION_DOWN);
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;  //子控件数
                //遍历所有的子控件
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {
                        child.getHitRect(frame);
                        if (frame.contains(scrolledXInt, scrolledYInt)) {  //是否是当前点击的view
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            if (child.dispatchTouchEvent(ev))  {//调用该View的dispatchTouchEvent()事件分发
                                mMotionTarget = child;
                                return true; //
                            }
                        }
                    }
                }
            }
        }
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
                (action == MotionEvent.ACTION_CANCEL);
        if (isUpOrCancel) {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
        final View target = mMotionTarget;
        if (target == null) {
            ev.setLocation(xf, yf);
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            }
            return super.dispatchTouchEvent(ev);//50
        }
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            if (!target.dispatchTouchEvent(ev)) {
            }
            mMotionTarget = null;
            return true;
        }
        if (isUpOrCancel) {
            mMotionTarget = null;
        }
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);
        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            mMotionTarget = null;
        }
        return target.dispatchTouchEvent(ev);
    }
  1. disallowIntercept判断事件拦截是否被禁用(默认false;可以通过requestDisallowInterceptTouchEvent()方法设置)
    例:需改disallowIntercept值为true
    requestDisallowInterceptTouchEvent(true);

  2. onInterceptTouchEvent()事件拦截
    源码如下:
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
    }

        Android事件的传递是从外层控件传递到内层控件(父控件传子控件);操作屏幕后系统收到事件后首先传递给最外层的容器,调用外层容器的事件分发dispatchTouchEvent()方法,外层容器通过自己的disallowIntercept先判断事件拦截是否被禁用(disallowIntercept默认false,通过requestDisallowInterceptTouchEvent()设置),如果没被禁用(false),则会调用onInterceptTouchEvent()判断事件是否被拦截。如果返回false则事件不会被拦截,就会遍历外层控件的子控件,并调用子控件的dispatchTouchEvent(),子控件如果是容器又会重复上面步骤,如果不是容器,就会执行View的dispatchTouchEvent()进行上面介绍的View中的流程;如果外层控件的onInterceptTouchEvent()返回true,事件则不会继续往下传递;这个过程都是在外层容器的disallowIntercept方法中执行的;所以需要指出的是外层控件的disallowIntercept方法和子View的disallowIntercept虽然方法名相同,但是内部的功能是有区别的!理想状况下外层的父控件是disallowIntercept方法是负责判断是否阻拦手势,并把手势的分发到子控件;子控件的disallowIntercept方法是通过onTouchEvent()触摸事件执行具体的手势操作!
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值