• 重写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的时候加入的属性,看个例子

在view任意位置长按,显示文本

它的作用是解释这个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(),非滑动控件内的延时就是0private 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;

更多相关文章

  1. activity基本模式
  2. Android(安卓)滑动效果入门篇(一)ViewFlipper
  3. android监听Key事件
  4. Android(安卓)xUtils框架(二) ViewUtils
  5. Android(安卓)-- 程序判断手机ROOT状态,获取ROOT权限
  6. Android(安卓)触摸事件传递机制
  7. Android连续点击多次(类似开发者模式)
  8. Android开发搭建环境
  9. 【Android】MVVM架构 RecyclerView加载数据滑动到后面,数据错乱,点

随机推荐

  1. 跨年游-四姑娘山大峰/二峰初级雪山攀登、
  2. 用 Vue 开发自己的 Chrome 扩展[每日前端
  3. 我还在生产玩 JDK7,JDK 15 却要来了!|新特
  4. 这才是GraphQL最详细的解释[每日前端夜话
  5. linux 破解root密码时遇到的问题
  6. SpringBoot热部署加持
  7. WebRTC 的现状和未来:专访 W3C WebRTC Cha
  8. Spring Boot Admin 2.0开箱体验
  9. 从一份配置清单详解Nginx服务器配置
  10. 没想到,这么简单的线程池用法,深藏这么多坑