内容简介:那么下面我们就来分析具体的执行流程:在 DOWN 事件来临的时候,首先在
前言
View的事件分发(二)源码分析 中,我们提到 OnLongClickListener 和 OnClickListener 的回调都是在 onTouchEvent 中执行的。
那么下面我们就来分析具体的执行流程:
源码分析
以下所有源码都是基于版本 27。为方便阅读,有所删减。
setOnClickListener 源码
//View 类 public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) {//是否已经设置了点击,没有就设置 setClickable(true); } getListenerInfo().mOnClickListener = l; } ----------------------------------------------------------------------- public void setClickable(boolean clickable) { //设置点击标识 setFlags(clickable ? CLICKABLE : 0, CLICKABLE); } 复制代码
setOnLongClickListener 源码
//View 类 public void setOnLongClickListener(@Nullable OnLongClickListener l) { if (!isLongClickable()) {//是否已经设置了长按点击,没有就设置 setLongClickable(true); } getListenerInfo().mOnLongClickListener = l; } ----------------------------------------------------------------------- public void setLongClickable(boolean longClickable) { //设置长按标识 setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE); } 复制代码
首先来看 onTouchEvent 中对 DOWN 事件的处理:
//View 类 public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_DOWN: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } --------代码① mHasPerformedLongPress = false; if (!clickable) { checkForLongClick(0, x, y); break; } if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); 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(0, x, y); } break; } } 复制代码
在 DOWN 事件来临的时候,首先在 代码① 处将 mHasPerformedLongPress 赋值为 false,表示长按事件还未触发。然后在 代码② 处为 mPrivateFlags 添加 PFLAG_PREPRESSED 标识,又发送了一个实现了 Runnable 接口(CheckForTap)的延迟消息,延迟时长 ViewConfiguration.getTapTimeout() 固定返回 100。
private final class CheckForTap implements Runnable { public float x; public float y; @Override public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); checkForLongClick(ViewConfiguration.getTapTimeout(), x, y); } } ------------------------------------------------------------------ public void setPressed(boolean pressed) { ...... if (pressed) { mPrivateFlags |= PFLAG_PRESSED; } else { mPrivateFlags &= ~PFLAG_PRESSED; } ...... } 复制代码
100ms 后,将 mPrivateFlags 取消 PFLAG_PREPRESSED 标识,然后添加了 PFLAG_PRESSED 标识。刷新按下状态的背景。然后检测长按事件。
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); } } 复制代码
如果设置了长按事件,就发送一个实现了 Runnable 接口(CheckForLongPress)的延迟消息。延时时长为 ViewConfiguration.getLongPressTimeout() - delayOffset
,其中 ViewConfiguration.getLongPressTimeout() 固定返回 500ms,delayOffset 为 100ms,延迟时长为 400ms。
private final class CheckForLongPress implements Runnable { @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick(mX, mY)) { 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); } --------------------------------------------------------------- private boolean performLongClickInternal(float x, float y) { boolean handled = false; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } //-------代码省略------- return handled; } 复制代码
400ms后,执行了 CheckForLongPress 中的 run 方法。run 方法中,执行了 mOnLongClickListener 的 onLongClick 方法。并且 onLongClick 返回为 true 时( performLongClickInternal 返回为 true ----> performLongClick 返回 true ),将 mHasPerformedLongPress 设置为 true。
所以在按下的时候,如果超过100ms,就设置 View 为按下状态,500ms后如果设置了长按点击事件,就回调长按点击的监听。
继续看 onTouchEvent 中对 MOVE 的处理
public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } break; } } 复制代码
通过 pointInView 来判断手指是否移出了这个 View 。如果移出了这个 View,就调用 removeTapCallback 和 removeLongPressCallback 方法。并且如果 mPrivateFlags 设置了 PFLAG_PRESSED 标识(按下时长超过 100ms)就执行 setPressed(false),取消按下状态,刷新背景。
private void removeTapCallback() { if (mPendingCheckForTap != null) { mPrivateFlags &= ~PFLAG_PREPRESSED; removeCallbacks(mPendingCheckForTap); } } --------------------------------------------------------------- private void removeLongPressCallback() { if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } } 复制代码
mPrivateFlags 移除 PFLAG_PREPRESSED 标识,移除按下检测(mPendingCheckForTap)和长按检测(mPendingCheckForLongPress)。
所以用户在按下后,手指移动,如果移除了 View 的范围,就取消 View 的按下状态,移除了 DOWN 事件中设置的 检测 和 长按。
查看 onTouchEvent 中对 UP 的处理
public boolean onTouchEvent(MotionEvent event) { final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; 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) { // 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) { setPressed(true, x, y); } 代码②-------- 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) { 代码③-------- if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } 代码④-------- if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; } } 复制代码
首先在 代码① 判断 mPrivateFlags 是否包含了 PFLAG_PRESSED 或 PFLAG_PREPRESSED。如果包含,就执行到了 代码② ,判断 mHasPerformedLongPress 和 mIgnoreNextUpEvent 是否为 false。都为 false 就调用 removeLongPressCallback() 方法,移除长按检测。执行到 代码③ ,添加一个实现 Runnable 接口的消息(mPerformClick),如果添加失败,则直接执行 mPerformClick 的 performClick() 方法。最后执行 removeTapCallback 方法,将按下检测移除。(如果从按下到抬起时间间隔不到100ms,不会执行按下状态刷新背景,但是会执行 onClickListener.onClick 回调。)
通过前面的分析可知,mHasPerformedLongPress 为 true 的条件就是 mOnLongClickListener.onLongClick 返回 true。所以如果没有设置长按监听,或者设置了长按事件且 mOnLongClickListener.onLongClick 返回 false,或者按下时长不够 500ms(没有达到 mOnLongClickListener.onLongClick 触发条件),则继续往下执行。
private final class PerformClick implements Runnable { @Override public void run() { performClick(); } } --------------------------------------------------------------- public boolean performClick() { 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; } 复制代码
可以看到 performClick 方法中执行了 mOnClickListener.onClick。也就是我们平时熟知的控件点击回调。
最后,代码继续执行到 代码④ ,如果 prepressed 为 true(mPrivateFlags 包含了 PFLAG_PREPRESSED),则发送一个延迟消息,ViewConfiguration.getPressedStateDuration() 发回固定值 64,延迟时长为 64ms。
private final class UnsetPressedState implements Runnable { @Override public void run() { setPressed(false); } } 复制代码
取消了按下状态,刷新背景。
所以,在 UP 事件的时候,如果长按监听的 onLongClick 返回 false(意味着 mHasPerformedLongPress 不为 true),就执行 mOnClickListener 的 onClick 回调。最后再取消 View 的按下状态。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Android事件分发源码归纳
- View的事件分发(二)源码分析
- Android事件分发机制[-View-] 源码级
- 基于源码分析 Android View 事件分发机制
- Laravel Queue——消息队列任务与分发源码剖析
- Android读书笔记--从源码角度剖析View事件分发机制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。