View & ViewGroup 之 事件分发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/crazy1235/article/details/70767884

转载请注明出处:http://blog.csdn.net/crazy1235/article/details/70767884



事件分发,其实就是 MotionEvent 的分发过程!

MotionEvent

用户在手机屏幕上点击,滑动,触摸等操作都被封装成MotionEvent对象!

主要的事件有四种:

 1. MotionEvent.ACTION_DOWN = 0

 2. MotionEvent.ACTION_MOVE = 2

 3. MotionEvent.ACTION_UP = 1

 4. MotionEvent.ACTION_CANCEL = 3

在屏幕上点击,然后滑动,最后手指离开屏幕,会产生 1个ACTION_DOWN, 0~n个ACTION_MOVE1 个ACTION_UP 事件!这一系列事件称为一个事件序列


基本概念

  • 事件传递的过程是由外向内的! 事件总是给父元素,然后再由父元素分发给子View!

  • View的onTouchView默认返回true,表示默认消耗事件!

  • View的enable属性不影响onTouchEvent的默认返回值!即使一个View是disable状态,只要他的clickable或者longClickable有一个是true,则它的onTouchEvent就返回true

  • 正常情况下,一个事件序列只能被一个View拦截消费!

  • ViewGruop默认不拦截事件

  • View中没有onInterceptTouchEvent(),也就是没有拦截方法,一旦事件传递到view,则View的onTouchEvent() 就会被调用。

  • 子View可以通过requestDisallowInterceptTouchEvent() 方法来干父View的事件分发!但ACTION_DOWN除外!


事件的分发包含个重要的函数:

  • dispatchTouchEvent

  • onInterceptTouchEvent

  • onTouchEvent

  • requestDisallowInterceptTouchEvent

ViewGroup:

public boolean dispatchTouchEvent(MotionEvent event) // 分发
public boolean onInterceptTouchEvent(MotionEvent event) // 拦截
public boolean onTouchEvent(MotionEvent event)  // 消费(处理)
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) // 请求不拦截事件

View:

public boolean dispatchTouchEvent(MotionEvent event) // 分发
public boolean onTouchEvent(MotionEvent event)  // 消费(处理)

Activity的事件分发

Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        // ...

        mWindow = new PhoneWindow(this, window);

        // ...

    }

getWindow() 返回的是一个Window对象。而Window是一个抽象类

public abstract class Window {}

它有一个唯一实现子类是 PhoneWindow 。在Activity的 attach() 函数 中进行了初始化操作!

在PhoneWindow中,又将事件传递给了 DecorView

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

在PhoneWindow中,注释写的很清楚,DecorView是最顶级的view !

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

DecorView

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

decorView中又调用了父类的 dispatchTouchEvent(event) 函数!

最终调用到 ViewGroup.dispatchTouchEvent(MotionEvent ev)


ViewGroup的事件分发

ViewGroup的 dispatchTouchEvent(MotionEvent) 函数非常长~

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // ... 

            // 当事件为ACTION_DOWN,表示处理一个新的事件序列
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 处理掉之前的状态开始一个新的触摸操作
                cancelAndClearTouchTargets(ev); // 取消清空所有的TouchTargets, 函数内部将 mFirstTouchTarget 置为 null
                resetTouchState();
            }

            // 第一部分

            final boolean intercepted; // 拦截的标记变量
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // mFirstTouchTarget !!!
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // FLAG_DISALLOW_INTERCEPT !!!
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev); // 调用拦截事件
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            // ...

            // 第二部分

            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL; // 标志当前touch事件是否取消了

            TouchTarget newTouchTarget = null; // 目标触摸对象
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) { // 没有取消 && 没有被拦截

                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;

                    // ... 

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);

                        // 对子view进行排序
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;

                        // 倒序遍历子View
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);


                            // 判断是否能接收到事件!(包括判断位置和是否在播放动画)
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            // 
                            newTouchTarget = getTouchTarget(child);

                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            // 调用子元素的dispatchTouchEvent()方法

                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                                // 给mFirstTouchTarget赋值 
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true; // 标志为true
                                break;
                            }

                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    // ... 省略代码
                }
            }

            // 第三部分

            // 没有子view元素处理或者子元素dispatchTouchEvent返回false
            if (mFirstTouchTarget == null) {
                // 第三个参数为null
                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;
                }
            }

            // ... 省略代码
        }

        // ... 省略代码

        return handled;
    }

我将 dispatchTouchEvent() 分为三个部分,下面分开来说!

在说第一部分之前,先来看第一部分上面的一段代码:

if (actionMasked == MotionEvent.ACTION_DOWN) {
      // 处理掉之前的状态开始一个新的触摸操作
      cancelAndClearTouchTargets(ev); // 取消清空所有的TouchTargets, 函数内部将 mFirstTouchTarget 置为 null
      resetTouchState();
            }

只要当前进入的操作时 ACTION_DOWN 就会进入这个方法体!

主要来看 resetTouchState()

private void resetTouchState() {
        clearTouchTargets(); //清楚touch目标
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
/**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

从clearTouchTargets() 函数可以看出,TouchTarget是一个 链表结构 。方法体最后 将mFirstTouchTarget置为null

所以每次一个新的事件序列进入,都会将 mFirstTouchTarget = null

mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;


OK,下面来看第一部分代码!

第一部分

一个事件序列的第一个时间就是MOTION_DOWN,而mFirstTouchTarget = null, 所以if判断为true。

接着通过运算得到一个 disallowIntercept 标志,当 disallowIntercept = false 时,才会运行 onInterceptTouchEvent(ev) 函数!

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

上面mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT

mGroupFlags & FLAG_DISALLOW_INTERCEPT = mGropFlag & ~FLAG_DISALLOW_INTERCEPT & FLAG_DISALLOW_INTERCEPT = 0

所以此时disallowIntercept = false, 然后进入if条件,运行 onInterceptTouchEvent 函数

从后面的代码可以看出,当事件被ViewGroup的子View处理时,mFirstTouchTarget 会被指向子元素。所以ViewGruop不拦截事件交给子View处理时,mFirstTouchTarget != null

但是当ViewGroup拦截事件时,mFirstTouchTarget 并没有被赋值,所以mFirstTouchTarget = null ,那么该时间序列之后的事件, actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null 就成了false,所以就不在调用该ViewGroup的 onInterceptTouchEvent(ev) 函数!这些后续事件都交由它处理!

还有另外一种情况,加入说MOTION_DOWN被子View处理成功了,此时mFirstTouchTarget就不为null, 判断条件 (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null ) == true , 此时onInterceptTouchEvent(ev) 函数的调用还受到 FLAG_DISALLOW_INTERCEPT 这个标志的影响!

在ViewGroup中有一个 requestDisallowInterceptTouchEvent() 函数,用来控制该ViewGroup是否拦截事件!

@Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

当disallowIntercept = true,才会生效!

因为当 disallowIntercept = false时,mGroupFlags & FLAG_DISALLOW_INTERCEPT 是等于0 的,此时直接return了。注释中说,已经是这种状态了,也就是默认不拦截

  1. 当disallowIntercept = true时,mGroupFlags |= FLAG_DISALLOW_INTERCEPT

  2. 当disallowIntercept = false是,mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT,与上面函数 resetTouchState() 中对 mGroupFlags 的赋值操作一致!

当对一个ViewGroup调用了requestDisallowInterceptTouchEvent(true),此时MOTION_DOWN 的后续事件在进行 mGroupFlags & FLAG_DISALLOW_INTERCEPT 运算时,就不为0!那么此时 disallowIntercept = true,将不再执行onInterceptTouchEvent(ev) 函数!

也就是说,调用了requestDisallowInterceptTouchEvent(true)之后,该ViewGruop将无法拦截除了ACTION_DOWN以外的事件(同一事件序列)!但是不影响对ACTION_DOWN事件的拦截处理,因为在dispatchTouchEvent()函数的最开始,对mGroupFlags 状态进行重置!上面已经叙述了~

所以此时得到如下结论:

  1. 当ViewGroup拦截一个时间序列的起始事件(MOTION_DOWN)之后,后续的事件都默认交由它来处理,并且 onInterceptTouchEvent() 函数只调用一次(MOTION_DOWN事件时调用的)!

  2. onInterceptTouchEvent() 并不是每次事件都会调用!


现在来看 onInterceptTouchEvent(ev)

public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

普通的布局isOnScrollbarThumb() 返回false。

所以一般情况下,onInterceptTouchEvent 返回的是 false。就是默认不拦拦截事件!

OK,第一部分分析完毕,来看第二部分!


第二部分

  1. 当事件没有取消和没有被拦截时,如果事件时 ACTION_DOWN 、ACTION_POINTER_DOWN、ACTION_HOVER_MOVE 三者中的任一个,则进入if语句内部!

  2. newTouchTarget == null && childrenCount != 0 ,一般情况下是为true的。当一个ViewGroup没有子元素(子View或者子ViewGroup)时,childrenCount = 0,则条件不成立,这个时候第二部分就没它事情了,直接运行到了第三部分!

  3. 然后接着是对子元素进行排序,具体的排序规则这里不讲!

  4. 所以主要代码就在对子元素的遍历上!

先来看下面这个判断

if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
   ev.setTargetAccessibilityFocus(false);
   continue;
}

这个条件判断的意思就是 判断子元素是否可以接收到事件

// 判断view的状态,GONE或者INVISIBLE状态返回false;如果view有动画则返回true
private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }
// 判断点击位置是否在view内部
protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]); // View内部方法
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }

当当前子元素满足这两个条件,则往下继续执行;不满足则执行下一轮for循环!

当该子元素满足条件时,调用了 dispatchTransformedTouchEvent()函数,先暂时不管这个函数内部是怎么处理的! 当它返回true时, 调用了addTouchTarget(),然后跳出循环!

在addTouchTarget() 函数内部对 mFirstTouchTarget 进行赋值操作!

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

从这个方法体可以看出,ToughTarget是一中链表结构,有点类似于Handler内部维护消息的方式!

当有一个子元素成功处理了事件,则mFirstTouchTarget != null


第三部分

第二部分分析完毕,除了一个dispatchTransformedTouchEvent()函数。第三部分里面也有调用这个函数,所以在这里一块来分析。

这部分开始先判断 mFirstTouchTarget 是否为null

经过第二部分的分析,mFirstTouchTarget = null有两种情况:

  1. ViewGroup根本没有子元素,也就没有对mFirstTouchTarget赋值!
  2. 找到了子元素处理事件,但是返回了false

这两种情况下,if条件为true,执行了

handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);

可以看出,此时第三个参数为null,根据上面的分析,在此函数内部调用了

handled = super.dispatchTouchEvent(transformedEvent);

显然,调用了 View.dispatchTouchEvent(ev) 函数!

至此,一个MOTION_DOWN 事件就分析完毕!

总结一下

  • 当ViewGroup拦截一个时间序列的起始事件(MOTION_DOWN)之后,后续的事件都默认交由它来处理,并且 onInterceptTouchEvent() 函数只调用一次(MOTION_DOWN事件时调用的)!

  • 调用requestDisallowInterceptTouchEvent(true) 将会影响MOTION_DOWN 除外的事件序列!

  • 如果ViewGroup的某个字View决定拦截事件,那么后序事件也由该view来处理,并且当前ViewGroup的onInterceptTouchEvent() 不在调用!

  • ViewGroup中的onInterceptTouchEvent() 函数,在一般情况下返回false!

  • ViewGroup没有子View处理或者子View处理返回false,则当前事件由当前ViewGroup来处理!


dispatchTransformedTouchEvent

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        final int oldAction = event.getAction();
        // 如果是取消事件!
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                // 调用View类的dispatchTouchEvent()方法
                handled = super.dispatchTouchEvent(event);
            } else {
                // 调用子元素的dispatchTouchEvent函数,向下传递ACTION_CANCEL事件!
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // ... 省略代码

        // 非ACTION_CANCEL事件
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        return handled;
    }

可以看出,当第三个参数child部位null时,交给child继续往下分发处理;当child == null时,交由super处理!


View的事件分发

由于View不包含子元素,所以没有(也没必要)onInterceptTouchEvent()

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

    // ... 

    if (onFilterTouchEventForSecurity(event)) {

       ListenerInfo li = mListenerInfo;

        // 首先判断有没有设置onTouchListener,并判断onTouch的返回值
        if (li != null && li.mOnTouchListener != null
               && (mViewFlags & ENABLED_MASK) == ENABLED
               && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        // result == false时才执行 onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // ... 

    return result;
}

所有的view控件默认都是enable状态!

首先判断是否设置了 OnTouchListener , 当onTouchListener != null,并且onTouch()返回了true,则下一步 onTouchEvent() 将不会执行!onTouch() 返回false时,会执行 onTouchEvent() 函数!

当onTouchListener == null 时,也会执行 onTouchEvent() 函数!

所以

当对控件设置了onTouchListener,并且onTouch函数返回了true,则onTouchEvent() 不再处理!可见,onTouchListener的优先级比onTouchEvent要高

接着来看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();

        //
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false); //设置按下状态
            }
            // disable状态下的view照样可以消费事件,但是不响应而已!
            // 如果clickable、long_clickable、context_clickable任意一个设置为true,则返回true
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

        // 代理回调
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP: // 抬起事件
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // ...

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // up事件,所以需要移除长按操作
                            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();
                                }
                            }
                        }

                        // 移除tap callback
                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN: // 按下事件
                    mHasPerformedLongPress = false; // 表示长按事件还未开始

                    // ...

                    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(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL: // 事件取消
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE: // 移动事件
                    drawableHotspotChanged(x, y);

                    // 判断是否还在view区域内
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

第8行,disable状态下的view依旧会消费事件!

接着判断是否有触摸代理(TouchDelegate),如果有则触发回调函数!

((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE

当判断条件为false时,直接返回了false,onTouchEvent事件结束!

主要View的CLICKABLE 和 LONG_CLICKABLE 有一个为true,就会进入if 函数 内部,不管内部怎么处理的,最后返回了 true ,表示成功消费当前事件!

MotionEvent.ACTION_DOWN

当当前view在一个滚动容器中时,系统设定要延迟触摸反馈!

在if函数内,发送了一个延迟消息!

postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); // 100ms
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);
        }
    }

CheckForTap 的run() 方法内部,重置mPrivateFlags 的状态!接着调用checkForLongClick(),该函数用于检测是否要处理长按事件,如果需要则发送长按消息来处理!

private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { // 判断是否支持长按操作
            mHasPerformedLongPress = false; // 处理长按操作标志

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress(); // runnable
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset); // 发送延迟消息
        }
    }

ViewConfiguration.getLongPressTimeout() == 500ms

所以如果支持长按事件,则当 接受到 MOTION_DOWN 事件时,500ms之后,执行了 CheckForLongPress 任务!

来看CheckForLongPress 的run()函数:

@Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }

首先isPressed()来判断是否还处于按压状态,也就是手指还没厉害屏幕。

然后出发了 performLongClick() ,进而回调长按事件函数!


MotionEvent.ACTION_MOVE

pointInView(x, y, mTouchSlop)

这个函数是用来判断当前触摸的位置是否还在View中!
如果不在,则 移除我们设置两个任务,并回执view的press状态!

removeTapCallback();
removeLongPressCallback();

MotionEvent.ACTION_UP

当手指抬起时,会触发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);
        return result;
    }

在函数内部去判断是否设置了 onClickListener 监听,如果设置了则回调onClick() ! 也即是泛指的”点击事件”!

不同view的CLICKABLE 默认值不同,button的是true,而textView的就是false。

但是通过setOnClickListener() 可以改变这个值!

所以当我们在activity中对某一view控件设置了OnClickListener,则会回调onClick 函数!


猜你喜欢

转载自blog.csdn.net/crazy1235/article/details/70767884
今日推荐