AndroidUI进阶--触摸反馈和事件分发源码解析
Posted bug樱樱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AndroidUI进阶--触摸反馈和事件分发源码解析相关的知识,希望对你有一定的参考价值。
android触摸事件分发机制
事件分发顺序
当Android设备被触摸的时候,触摸屏事件响应是向下发,但处理是反过来的。Activty会将事件最终发给ViewGroup或者View,然后没有拦截的话层层传递,通过梳理源码流程理解这一机制。
对于机制不太熟悉的同学,可以先看View的dispatchTouchEvent部分会比较好理解。
Activity::dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
if (ev.getAction() == MotionEvent.ACTION_DOWN)
onUserInteraction();
if (getWindow().superDispatchTouchEvent(ev))
return true;
return onTouchEvent(ev);
先简单概括一下dispatchTouchEvent
这个方法。
-
功能:将触摸屏运动事件向下传递到目标view或者目标本身。
-
参数:需要被分发的运动事件
-
返回值: 如果事件被消费则True,否则False(不消费不代表就是传递)
触摸事件先由当前Activity响应,执行dispatchTouchEvent方法,首先判断事件是否为ACTION_DOWN,执行onUserInteraction,空方法,用于自定义实现,通常用来debug设备的交互情况。这里也是在应用开发过程中,最早能接收到触摸事件的地方。
“实际上,onUserInteraction方法主要是用于管理状态栏通知,以及在恰当的时候取消通知。与该方法相关的还有另一个方法,onUserLeaveHint。该方法作为Activity生命周期回调的一部分,会在用户将Activity放到后台时调用(如用户点击Home键),该方法会在onPause方法之前调用。”
superDispatchTouchEvent这个方法是window的方法,window提供了空方法,由唯一子类PhoneWindow实现,而PhoneWindow则又调用了DecorView的superDispatchTouchEvent,这里真正的实现地方是DecorView的dispatchTouchEvent。而ViewGroup又可以将事件分发给View。最后才回到Activity的onTouchEvent,Activity对于事件的消费是最低级的。
最后执行onTouchEvent
public boolean onTouchEvent(MotionEvent event)
if (mWindow.shouldCloseOnTouch(this, event))
finish();
return true;
return false;
判断Window是否要在touch后关闭,如果是就要结束Activity,并消费事件。否则不消费。
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean shouldCloseOnTouch(Context context, MotionEvent event)
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside)
return true;
return false;
window这里主要是判断是否在window外,并且设定了关闭。
ViewGroup::dispatchTouchEvent
从Activity的dispatch方法知道了DecorView会将事件分发给ViewGroup执行其中对dispatchTouchEvent。所以逐步分析一下ViewGroup的dispatchTouchEvent方法。
if (mInputEventConsistencyVerifier != null)
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost())
ev.setTargetAccessibilityFocus(false);
方法的一开始自洽性检查、这里有一个焦点判断。如果事件的目标是一个可访问的焦点,那么就会查找具有可访问焦点的view,如果找到的子view不处理该事件,才会按照正常的流程派发给所有子view。可以理解为ViewGroup是子View的集合,需要判断处理的优先级,那么焦点一般是优先级最高,需要判断一下是否需要优先处理。设置为false就代表不特殊处理,正常派发事件。
if (childWithAccessibilityFocus != null)
if (childWithAccessibilityFocus != child)
continue;
childWithAccessibilityFocus = null;
i = childrenCount - 1;
在Android Q里面还存在focus优先级判断,但是在Android R这里被删掉了,可能google删除了,也可能移到别的地方处理去了,这里不太清楚。
if (onFilterTouchEventForSecurity(ev))
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN)
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
在判断Touch事件安全性以后,判断每次ACTION_DOWN,cancelAndClearTouchTargets初始化TouchTarget链表,保证没有子view正在被按下。
TouchTarget是一个子view对于触摸反馈顺序的链表,在多点触控下会比较复杂。
resetTouchState就和方法名一样。主要就是初始化一个Touch事件的周期,把flag都清除掉。这两个方法重置了触摸反馈。
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept)
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
else
intercepted = false;
else
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null)
ev.setTargetAccessibilityFocus(false);
接下来判断是否拦截,mFirstTouchTarget是链表头节点,如果不为null表示有子View可以消费事件。FLAG_DISALLOW_INTERCEPT这个flag表示子view不希望被拦截事件,子view可以通过实现requestDisallowInterceptTouchEvent这个方法来表示不希望被拦截。如果是在ACTION_DOWN的时候resetTouchState里将flag重置了,所以理解为当前不是ACTION_DOWN且子view表示过不希望被拦截,子view会强行绕过ViewGroup的分发顺序,这在一些滑动的View里可以考虑使用这个方法来优化滑动体验。
之后就是onInterceptTouchEvent,这是拦截的方法,和ontouchevent的响应顺序相反,先由父view来处理是否拦截,表示强行占用touchevent,不让子view用,比如滑动的时候可以让子view先响应,但是如果他滑动了就得交给父view来处理,在recyclerview重写了该方法,这个方法只会返回一次true,true后直接调用子view的onTouchEvent,子view的touchevent为false再去用viewgroup的touchevent,默认false,可重写该方法,但是建议非必要的情况下返回false。
如果没有可以消费事件的子view,默认也是拦截。拦截后将焦点处理设置为false。
if (!canceled && !intercepted)
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE)
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0)
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--)
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null))
continue;
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null)
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
判断事件没有被取消也没有被ViewGroup拦截的话。
得到可以响应的view数组根据遍历规则(可以理解为在xml里面从大往小,从下往上)进行遍历,
protected boolean canReceivePointerEvents()
return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
子view需要visible(就是xml里那个visible)且不在进行动画,且在view范围内。这里可以理解为view动画移位后,触摸事件的响应还在原来的位置。
然后通过touchtarget链表判断多点触控下的view处理。newTouchTarget就是表示不只是一个子view。
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null)
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++)
if (children[childIndex] == mChildren[j])
mLastTouchDownIndex = j;
break;
else
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
if (preorderedList != null) preorderedList.clear();
if (newTouchTarget == null && mFirstTouchTarget != null)
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null)
newTouchTarget = newTouchTarget.next;
newTouchTarget.pointerIdBits |= idBitsToAssign;
这段代码寻找即将分发消费的touchtarget
dispatchTransformedTouchEvent
这个方法是ViewGroup到View分发的方法,判断了子View不为空则调用子View的dispatchTouchEvent,在这里第一次调用是将down事件分发给子view,第二次调用是在touchtarget为空的情况下,分发机制给自己,第三次分发给子view消费并且返回子view是否消费事件。然后处理事件被消费了的标识alreadyDispatchedToNewTouchTarget。
如果没有找到可以消费事件的子view,就采取最近消费事件的子view来消耗事件。
// Dispatch to touch targets.
if (mFirstTouchTarget == null)
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
else
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null)
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)
handled = true;
else
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits))
handled = true;
if (cancelChild)
if (predecessor == null)
mFirstTouchTarget = next;
else
predecessor.next = next;
target.recycle();
target = next;
continue;
predecessor = target;
target = next;
如果没有子类消费或是被拦截,就自己消费。
后面根据alreadyDispatchedToNewTouchTarget判断事件如果被分发了,handle = true。
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE)
resetTouchState();
else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP)
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
if (!handled && mInputEventConsistencyVerifier != null)
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
return handled;
最后的处理,事件被取消就更新链表,事件没被消费的情况下,自洽性检查。返回handle。
ViewGroup本身并没有重写onTouchEvent,具备事件分发、事件拦截,但是没有做事件处理。有些ViewGroup的子类会去重写onTouchEvent,比如RecyclerView。
根据上面的分析,如果ViewGroup有子View的话,一般流程最终会走到子View的dispatchTouchEvent。
View::dispatchTouchEvent(MotionEvent event):
// 如果此event作为第一个可访问的焦点被处理
if (event.isTargetAccessibilityFocus())
// 我们没有焦点,或者没有虚拟后代拥有焦点,因此不处理事件。
if (!isAccessibilityFocusedViewOrHost())
return false;
// 我们有焦点并得到了事件,然后使用常规事件调度。
event.setTargetAccessibilityFocus(false);
首先进行isTargetAccessibilityFocus,判断事件是否t作为第一个可访问的焦点被处理
public boolean isTargetAccessibilityFocus()
final int flags = getFlags();
return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
getFlags调用native方法返回flags,再进行位与操作,目的是判断是否等同于FLAG_TARGET_ACCESSIBILITY_FOCUS,先去判断是不是第一个可访问的焦点,如果是就去判断有没有焦点,如果有焦点就去按常规处理,把flag改掉。这个在ViewGroup里作为子view优先级判断。
也就是说,target View 获取不到焦点(我们将focusable = false) 将直接跳过此次事件处理,他还是能获取到触摸事件,只是跳过处理
焦点的情况主要是EditText或者电视等设备。
设置默认返回result false
if (mInputEventConsistencyVerifier != null)
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
自洽检查,类似于ActionDown和Up是否一一匹配
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN)
// Defensive cleanup for new gesture
stopNestedScroll();
一个Action是32位的,高位表示指针的index,低位表示事件,这里获取事件的第八位,可以理解为掩码,用一个较小的int数来表示事件。这里是表示在滑动的时候,又重新接收到action_down事件,所以用一种无副作用的方式停止嵌套滚动
if (onFilterTouchEventForSecurity(event))
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event))
result = true;
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event))
result = true;
if (!result && onTouchEvent(event))
result = true;
onFilterTouchEventForSecurity首先判断此次事件窗口是否被遮挡,被遮挡则返回false
判断是否添加了OnTouchListener,View要处理Touch事件,就需要添加,并且判断是否enable(默认true)且在onTouch方法里返回true,例如
button.setOnTouchListener(new OnTouchListener()
@Override
public boolean onTouch(View v, MotionEvent event)
Log.i(TAG, "onTouch");
return true;
);
这就是为什么在这里返回true会拦截事件分发的原因,因为这里会在dispatchTouchEvent的result设为true
当然,在这里还看不出来为什么true就拦截了。
然后再进一步执行view的onTouchEvent,这里的逻辑与操作时为了短路避免在上面已经true的情况下进行不必要的运算
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result))
stopNestedScroll();
在事件的结束或者不想继续事件了,停止嵌套滚动。
事件分发机制的顺序就是dispatchTouchEvent → onTouch → onTouchEvent。
这里可以看出当onTouch消费了,那么onClick也就不会执行了,而onClick是在onTouchEvent里。
所以说如果在onTouch里面就返回了true,事件也就被拦截了,不会执行onTouchEvent
再来看一下View的onTouchEvent
其实View的onTouchEvent主要就是要在没有被touch事件消费掉的情况下,区分用户到底是在怎么操作屏幕,是滚动,是点击,是长按,是误触等等(如果一个view只是想触发touch,不是click等的话,上面返回true)。onTouchEvent对于应用开发来说,相当于是一组view的触摸事件的响应接口。
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED)
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0)
setPressed(false);
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
if (mTouchDelegate != null)
if (mTouchDelegate.onTouchEvent(event))
return true;
初始化坐标、viewFlags、action、clickable,clickable的判断表示如果一个View不可用,但是只要它可以点击或长按,都返回true。判断是否交给事件分发代理处理。
然后进入switch,也就是事件的处理。在这个switch外面就是return true;这也表明了一旦进入switch事件分发一定会在这里消费掉
ACTION_DOWN
首先介绍一下两个flag,PFLAG_PRESSED标识事件按下,而PREPRESSED用于标识在ACTION_DOWN后短时间内(getTapTimeout事件)无法确定是哪一种点击事件(长按、触摸等判断)。这两个flag都是二进制int,PREPRESSED是0000 0010 0000 0000 0000 0000 0000 0000,也就是可以通过第七位的1用位或|=和位与非$=~来控制flag的第七位来判断当前状态。
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN)
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
mHasPerformedLongPress = false;
if (!clickable)
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
if (performButtonActionOnTouchDown(event))
break;
首先View需要判断clickable,不可点击的view可以响应Touch,但是在ActionDown就会被拦截,例如ImageView
判断performButtonActionOnTouchDown表示的是类似鼠标右键的事件,不深入。
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer)
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null)
mPendingCheckForTap = new CheckForTap();
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
else
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
isInScrollingContainer遍历判断当前view是否是可滑动容器内,用于处理滑动事件
如果在滚动容器内,mPendingCheckForTap是一个runnable对象,判断在taptimeout时间内,用户的触摸坐标是否变化,变了就是滑动,这是一个delay消息,在延迟执行的tapTimeout中,如果坐标没变,则确认为按下,并且进入判断是否长按。这个runnable会把PFLAG_PREPRESSED取消标记,因为这个时候已经可以确认tap行为。
如果不是在滚动容器,则直接判断长按。
Action_down可以确认的是当前事件是否为长按、滑动。
这里要注意的是这个delay消息是一个延迟,在自定义View的时候非滑动组件要把这个延迟设置为false。
checkForLongClick
再来看一下长按事件,这里经常看到一个方法checkForLongClick
private void checkForLongClick(long delay, float x, float y, int classification)
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();
mPendingCheckForLongPress.setClassification(classification);
postDelayed(mPendingCheckForLongPress, delay);
这里有四个参数,延迟、位置信息,和分类,这个分类在这里只有两种,长按和类似3d touch的deep press,不去管deep press。
首先确认ViewFlag是可长按或TOOLTIP(xml可配置的长按提示功能)。
mHasPerformedLongPress这个标识代表了长按是否已经被调用,设为false,表示还没有,如果已经被调用了,那么就不会识别长按而是tap了。然后就是一个名为CheckForLongPress的Runnable
private final class CheckForLongPress implements Runnable
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
private boolean mOriginalPressedState;
/**
* The classification of the long click being checked: one of the
* FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
*/
private int mClassification;
@UnsupportedAppUsage
private CheckForLongPress()
@Override
public void run()
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount)
recordGestureClassification(mClassification);
if (performLongClick(mX, mY))
mHasPerformedLongPress = true;
public void setAnchor(float x, float y)
mX = x;
mY = y;
public void rememberWindowAttachCount()
mOriginalWindowAttachCount = mWindowAttachCount;
public void rememberPressedState()
mOriginalPressedState = isPressed();
public void setClassification(int classification)
mClassification = classification;
发送延迟消息就是执行该run方法,这里检查了WIndowAttachCount也就是view的attach次数,用于判断长按过程中是否有Activity的生命周期变化,view的实效来判断长按是否失效。然后就是执行performLongClick,并将mHasPerformedLongPress = true。
public boolean performLongClick(float x, float y)
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
public boolean performLongClick()
return performLongClickInternal(mLongClickX, mLongClickY);
在这里调用了view的onLongClickListener。
private boolean performLongClickInternal(float x, float y)
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null)
handled = li.mOnLongClickListener.onLongClick(View.this);
if (!handled)
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
if ((mViewFlags & TOOLTIP) == TOOLTIP)
if (!handled)
handled = showLongClickTooltip((int) x, (int) y);
if (handled)
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return handled;
第一行是辅助功能,用于一些特殊需求,可以不管。这个handled是返回值,表示事件是否被消费。在这里就调用了onLongClick方法
如果消费了,会提供震动反馈HapticFeedbackConstants。
ACTION_MOVE
case MotionEvent.ACTION_MOVE:
if (clickable)
drawableHotspotChanged(x, y);
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback())
if (!pointInView(x, y, touchSlop))
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
touchSlop *= mAmbiguousGestureMultiplier;
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop))
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0)
setPressed(false);
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
这里主要执行移动,判断手势操作,判断移动边际。
view的边界范围touchSlop,用于一些手指有部分在view外的情况下判断是否算是该view的时间,扩大这个值可以增加边界,这里判断是否在范围外。
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback())
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
break;
这是用于判断压感的,类似3dtouch。
ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)
// take focus if we don't have it already and we should in
// touch mode.
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);
首先获取焦点、然后要判断是否button在还没有来得及响应的时候就被释放了,那也要继续完成点击事件。
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent)
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
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))
performClickInternal();
removeLongPressCallback删除长按检测计时器。
为了保证view的时序,使用线程发布消息,让界面可以先更新
public boolean performClick()
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null)
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
else
result = false;
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
performClickInternal最终会走到performClick,先后两次notifyAutofillManagerOnClick确保view的时序,然后就是onClick,result = true。后面就是状态判断removeTapCallback
ACTION_CANCEL
触控事件被系统取消,类似于移动事件被父view拦截。
case MotionEvent.ACTION_CANCEL:
if (clickable)
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
cancel就是把原来的一些状态记录都去除,是一个重置的操作。
总结
经典U型图(来源于网络)
文末
要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、架构师筑基必备技能
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
二、Android百大框架源码解析
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
三、Android性能优化实战解析
- 腾讯Bugly:对字符串匹配算法的一点理解
- 爱奇艺:安卓APP崩溃捕获方案——xCrash
- 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
- 百度APP技术:Android H5首屏优化实践
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 携程:从智行 Android 项目看组件化架构实践
- 网易新闻构建优化:如何让你的构建速度“势如闪电”?
- …
四、高级kotlin强化实战
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
-
从一个膜拜大神的 Demo 开始
-
Kotlin 写 Gradle 脚本是一种什么体验?
-
Kotlin 编程的三重境界
-
Kotlin 高阶函数
-
Kotlin 泛型
-
Kotlin 扩展
-
Kotlin 委托
-
协程“不为人知”的调试技巧
-
图解协程:suspend
五、Android高级UI开源框架进阶解密
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
六、NDK模块开发
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
七、Flutter技术进阶
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
八、微信小程序开发
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓
以上是关于AndroidUI进阶--触摸反馈和事件分发源码解析的主要内容,如果未能解决你的问题,请参考以下文章
从源码角度分析Android 事件分发机制以及常见滑动冲突解决方案