View的事件分发(三)源码分析

栏目: 编程工具 · 发布时间: 5年前

内容简介:那么下面我们就来分析具体的执行流程:在 DOWN 事件来临的时候,首先在
View的事件分发(三)源码分析

前言

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 的按下状态。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

社交红利

社交红利

徐志斌 / 北京联合出版公司 / 2013-8 / 42

如今的互联网,社交网络已占据了主要的位置。如腾讯微博、微信、QQ空间、人人网、新浪微博、唱吧、美丽说、啪啪等等,都可以算是社交网络,将大部分活跃的人们聚集起来,通过文字、图片、语音等形式分享着身边的事。这些社交网络吸引着更多兴趣相投的陌生人成为朋友结成圈子,也衍生出的海量流量和机会,为业界和创业者提供着源源不绝的新机会。可以这样说,社交网络在将散落在人们中的需求汇聚起来,等待着企业来提供服务。因此......一起来看看 《社交红利》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码