- 重写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(),非滑动控件内的延时就是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;