Android view 事件处理
1、事件处理
事件通过ViewGroup分发完后,就到了事件处理,事件处的方法也是dispatchTouchEvent,只不过这里的dispatchTouchEvent是在View里面的。下面我们来看一下源码。
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
源码看似很长,但是涉及到事件的也就一小段代码而已,下面这段代码涉及两个事件处理li.mOnTouchListener.onTouch(this, event),这个事件是通过setOnTouchListener(OnTouchListener l)设置进来的 和 onTouchEvent(event)事件处理 , 这个处理时View本身自带的,继承View可以重写它。看这段代码是先执行li.mOnTouchListener.onTouch(this, event),返回true就不再执行onTouchEvent(event)了,返回false才继续执行onTouchEvent(event)。所以通过setOnTouchListener(OnTouchListener l)设置的事件监听的优先级要大于View本身自带的onTouchEvent(event)事件监听。想要li.mOnTouchListener.onTouch(this, event)与onTouchEvent(event)事件处理同时执行,必须让li.mOnTouchListener.onTouch(this, event)返回结果false。
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;
}
}
2、onOnClickListener事件
上面讲了OnTouchListener事件和onTouchEvent事件处理。哪onOnClickListener事件在哪里执行呢?通过查看onTouchEvent源码,发现在onTouchEvent方法里的MotionEvent.ACTION_UP事件处理是调用了performClickInternal();
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();
}
}
查看performClickInternal()方法的代码,最后调用了performClick()方法
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
查看performClick()方法的代码,在一系列的判断后,调用li.mOnClickListener.onClick(this);返回true,到此onOnClickListener监听结束了
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;
}
3、onLongClickListener事件
onOnClickListener事件是在onTouchEvent方法里执行的,那onLongClickListener肯定也是在onTouchEvent方法里执行的执行的,只不过执行的地方不一样,onLongClickListener事件执行是在MotionEvent.ACTION_DOWN事件中执行的checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);。
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;
}
// 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;
查看checkForLongClick方法代码,初始化CheckForLongPress线程对象,postDelayed(mPendingCheckForLongPress, delay)固定时间运行线程。
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);
}
}
在这里我们看一下CheckForLongPress线程运行了哪里代码,看下面代码最后调用的是performLongClick(mX, mY)方法。
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;
}
}
查看performLongClick方法代码,最后调用的是performLongClick()方法
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
查看performLongClick()方法,最后调用的是performLongClickInternal方法。
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
查看performLongClickInternal方法,到了这里看到了调用了li.mOnLongClickListener.onLongClick(View.this);到此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;
}
4、总结
1)一个事件只能消费一次,返回true代表事件已消费,返回false代表事件没有被消费
2)onTouchListener优先级大于onTouchEvent,当两者同时存在时,如果onTouchListener消费了事件返回true了,则onTouchEvent不执行;只有onTouchListener返回false时,onTouchEvent才能被执行(两者都被执行)。
3)onClickListener事件是在onTouchEvent中的ACTION_UP事件中被执行的,也就是说同时设置了onTouchListener事件和onClickListener事件时,onTouchListener在ACTION_UP事件时必须返回false,onClickListener事件才会被执行。
4)onLongClickListener事件是在onTouchEvent中的ACTION_DOWN事件中被执行的,同时设置了onTouchListener事件和onLongClickListener事件时,在onTouchListener在ACTION_DOWN事件时必须返回false,onLongClickListener事件才会被执行。并且onLongClickListener事件一定得长按的时间大于长按事件固定的事件才会被执行。默认是400毫秒,但是实际的还得根据应用场景来。
/**
* Defines the default duration in milliseconds before a press turns into
* a long press
* @hide
*/
public static final int DEFAULT_LONG_PRESS_TIMEOUT = 400;
onLongClickListener事件监听有一个boolean的返回值,看看返回值对其他事件的影响
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
由上代码得知,onLongClickListener的返回值最后会影响mHasPerformedLongPress的值,返回true的时候,mHasPerformedLongPress = true; 再看看mHasPerformedLongPress对事件的影响,最后找到在ACTION_UP事件执行onClickListener事件之前有判断。mHasPerformedLongPress为true时,onClickListener事件不会执行
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();
}
}
}
5)onLongClickListener事件和onClickListener事件同时存在时,若是onLongClickListener事件已经执行了,onLongClickListener返回的结果是true时,onClickListener事件不执行;onLongClickListener返回的结果是false时,onClickListener事件会在ACTION_UP事件中执行。
6)onTouchEvent正常情况下,若是没有设置onClickListener事件和onLongClickListener事件,其返回的值是false,就是子View不消费事件,把事件给回到父View执行。在只设置onTouchListener事件时要注意了,不要返回false,否则后面的事件都处理不了了。
nClickListener事件同时存在时,若是onLongClickListener事件已经执行了,onLongClickListener返回的结果是true时,onClickListener事件不执行;onLongClickListener返回的结果是false时,onClickListener事件会在ACTION_UP事件中执行。