Android view触摸反馈原理和源码分析

  • 重写onTouchEvent(),在方法内部定制触摸反馈算法
    • 是否消费事件取决于ACTION_DOWN事件或POINTER_DOWN事件是否返回true

    • MotionEvent

      • getActionMasked()和getAction()有什么区别
      • POINTER_DOWN/POINTER_UP和getActionIndex()的联系

首先了解下getAction(),getActionMasked()和getActionIndex()区别

getAction() 返回的是响应的事件,考虑使用getActionMasked或者getActionIndex来得到分隔开的动作和手指的下标。返回的一般是ACTION_DOWN,合并起来的已经转移的手指下标的ACTION_POINTER_DOWN。

getActionMasked()返回的是具体的响应事件,没有手指下标的信息。可以使用getActionIndex来返回和手指动作相关的下标,返回的值:比如:ACTION_DOWN,ACTION_POINTER_DOWN。

getActionIndex()返回的是如果调用getActionMasked返回ACTION_POINTER_DOWN或者ACTION_POINTER_UP,这个方法返回的是ACTION_POINTER_DOWN,ACTION_POINTER_UP相关的下标,这个下标可能会在getPointerId,getX,getY,getPressed,getSize中用到来获取手指的关于按下或者抬起的信息。

event.getAction()是早期代码中使用的,在多点触控时期才有的event.getActionMasked()方法,由于需要区分是第一个手指按下或者非第一个手指按下,最后一个手指抬起还是非最后一个手指抬起,所以有了ACTION_DOWN和ACTION_POINTER_DOWN,ACTIN_UP和ACTION_POINTER_UP,如果是普通的DOEN,MOVE,UP,CANCLE事件这两个方法都可以用,如果支持多点触控,那就只能用getActionMasked(),所以直接用getActionMasked()即可

源码分析

查看view的的源码onTouchEvent方法

public boolean onTouchEvent(MotionEvent event) {
        //获取基本信息
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        //判断是否是可点击,CONTEXT_CLICKABLE上下文菜单类似于右键
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        // 如果是不可用状态,返回clickable,问题来了,问什么不可用状态还要返回clickable呢,打个比方,
        个人中页面上面的头像区域和头像背景区域都能点击,当头像的imageview设置了DISABLED,只是头像不能点击了,
        但是头像的背景区域仍能点击,相当于头像只把事件吃掉,但不处理事件。
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // 可单击的禁用视图仍然消耗触摸事件,只是不响应它们
            return clickable;
        }
        //增大点击范围,一般不需要考虑
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //关键代码
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
           ...
           ...
           ...
        }

在看onTouchEvent()关键代码 之前,什么叫TOOLTIP
Android API 28的时候加入的属性,看个例子
Android view触摸反馈原理和源码分析_第1张图片
在view任意位置长按,显示文本
Android view触摸反馈原理和源码分析_第2张图片
它的作用是解释这个view是什么,干什么用等,进入上述话题,关键代码:

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    ...
                case MotionEvent.ACTION_DOWN:
                    ...
                case MotionEvent.ACTION_CANCEL:
                    ...
                case MotionEvent.ACTION_MOVE:
                    ...
            }
            return true;
        }

MotionEvent.ACTION_DOWN

case MotionEvent.ACTION_DOWN:
                    //是否触摸到屏幕
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;
                    
                    if (!clickable) {
                        //1.如果不可点击,设置长按等待器,
                        //checkForLongClick的源码见 1.
                        checkForLongClick(0, x, y);
                        break;
                    }
                    //2.检测鼠标和右键点击
                    //performButtonActionOnTouchDown源码见 2.
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // 是否在滑动控件内.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    //在滑动控件内
                    if (isInScrollingContainer) {
                        //状态置为预按下
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                        //点击等待期,是一个runnable
                        //CheckForTap 源码见 3.
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {   
                    //如果不在滑动控件内,设置为按下状态和长按等待器(设置长按事件但不触发)
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

1.checkForLongClick(delayOffset,x, y)

private void checkForLongClick(int delayOffset, float x, float y) {
//在滑动控件内,预按下等待时间减去100等待期
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

2.performButtonActionOnTouchDown(event)

protected boolean performButtonActionOnTouchDown(MotionEvent event) {
        //如果是鼠标右键
        if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
            (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
            //显示上下文菜单
            showContextMenu(event.getX(), event.getY());
            mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

3.CheckForTap

private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
            //把预按下状态置空
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true, x, y);//设置按下状态
            // 设置长按的等待期
            //checkForLongClick源码见 4.
            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
        }
    }

4.checkForLongClick(delayOffset, x, y)

//delayOffset 预按下的延时时间,最终会被减掉,比如上面的3.CheckForTap,在滑动控件中的延时时间是ViewConfiguration.getTapTimeout(),非滑动控件内的延时就是0
private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            //减掉预按下状态的延时时间
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

MotionEvent.ACTION_MOVE

case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        //改变水波纹中心位置
                        drawableHotspotChanged(x, y);
                    }

                    // 如果没有在view范围,移出状态
                    if (!pointInView(x, y, mTouchSlop)) {
                        // mTouchSlop:溢出距离,默认为8dp
                        // 删除后续所有的长按/点击检查
                        removeTapCallback();//从预按下到按下
                       removeLongPressCallback();//长按Callback
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;

MotionEvent.ACTION_UP

case MotionEvent.ACTION_UP:
                    //TOOLTIP是否额外移动给置空
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    //松手之后TOOLTIP消失
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    //如果不可点击,初始化状态
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    //按下或预按下
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        //isFocusableInTouchMode实体按键,比如电视
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }
                        //处理点击事件
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            //仅在我们处于按下状态时才执行点击操作
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            //预按下状态,手动加4帧延迟,稍后触发UP事件点击事件
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

MotionEvent.ACTION_CANCEL

case MotionEvent.ACTION_CANCEL:
                    //恢复状态
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

你可能感兴趣的:(Android)