Android view事件的分发机制

Android30

MotionEvent

在手指接触屏幕后,所产生的一些列事件:

事件简介
ACTION_DOWN第一个手指初次接触到屏幕时触发。
ACTION_MOVE手指在屏幕上滑动时触发,会多次触发。
ACTION_UP最后一个手指离开屏幕时触发。
ACTION_POINTER_DOWN有非主要的手指按下(即按下之前已经有手指在屏幕上
ACTION_POINTER_UP有非主要的手指抬起(即抬起之后仍然有手指在屏幕上
ACTION_CANCEL事件被上层拦截时触发。

点击屏幕后离开松开,事件序列为:ACTION_DOWN --> ACTION_UP
点击屏幕滑动一会再松开,事件序列为:ACTION_DOWN --> ACTION_MOVE --> … --> ACTION_MOVE --> ACTION_UP

通过MotionEvent对象,可以得到点击事件发生的x和y坐标。方法:getX/getY 和 getRawX/getRawY
视图坐标系

手势

在这里插入图片描述

点击事件的传递规则

    点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。

    三个重要的方法
        dispatchTouchEvent
        onIntercepTouchEvent
        onTouchEvent

     public boolean dispatchTouchEvent(MotionEvent ev)
          用来进行事件的分发。如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前view的onTouchEvent和下级view的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

     public boolean onInterceptTouchEvent(MotionEvent event)
          在上述方法内部调用,用来判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

     public boolean onTouchEvent(MotionEvent event)
          在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前view无法再次接收到事件。

     对于一个根ViewGroup,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个viewGroup 的 onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就交给这个viewGroup处理,即它的onTouchEvent方法就会被调用;如果返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。

     当一个view需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法就会被调用,如果返回false,则当前view的onTouchEvent就会被调用,如果返回true,那么onTouchEvent方法不会被调用。给view设置的OnTouchListener,其优先级比onTouchEvent高。onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。

     当一个点击事件产生后,它的传递过程如下:Activity --> Window --> View. 即事件总是先传递给Activity,Activity在传递给Window,最后Window再传递给顶级view。顶级view接收到事件后,就会按照事件分发机制去分发事件。考虑到一种情况,如果一个view的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用。

     1)同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,整个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
     2)正常情况下,一个事件序列只能被一个view拦截且消耗。一旦一个元素拦截了此事件,那么同一个事件序列内所有事件都会直接交给它处理,因此同一个事件序列中事件不能分别由两个view同时处理,但是通过特殊手段可以做到,比如一个view将本该自己处理的事件通过onTouchEvent强行传递给其他view处理。
     3)某个view一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。
     4)某个view一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一个事件序列中的其它事件都不会再交给它来处理,并且事件将重新交给它的父元素去处理,即父元素的onTouchEvent会被调用。
     5)如果view不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前view可以持续收到后续事件,最终这些消失的事件会传递给Activity处理。
     6)ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false。
     7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
     8)View的onTouchEvent默认都会消耗事件(返回ture),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如button的clickable属性默认为true,而textView的clickable属性默认为false。
     9)View的enable属性不影响onTouchEvent的默认返回值。
     10)onClick会发生的前提是当前view是可点击的,并且它收到了down和up的事件。
     11)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子view,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素等事件分发过程,但是ACTION_DOWN事件除外。
事件分发消费
在这里插入图片描述

事件接收流程分析

ViewRootImpl#setView

public void setView(View view, WindowManager.LayoutParams attrs, 
		View panelParentView, int userId) {
	......
	InputChannel inputChannel = null;
	if ((mWindowAttributes.inputFeatures
            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
		inputChannel = new InputChannel();
	}
	......
	if (inputChannel != null) {
		if (mInputQueueCallback != null) {
			mInputQueue = new InputQueue();
			mInputQueueCallback.onInputQueueCreated(mInputQueue);
		}
		// 接收事件
		// WindowInputEventReceiver 为 ViewRootImpl 的内部类
		mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
				Looper.myLooper());
	}
    ......
	mSyntheticInputStage = new SyntheticInputStage();
	InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
    ......
}

ViewRootImpl#WindowInputEventReceiver#onInputEvent

WindowInputEventReceiver 为 ViewRootImpl 的内部类。

final class WindowInputEventReceiver extends InputEventReceiver {
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        super(inputChannel, looper);
    }

    @Override
    public void onInputEvent(InputEvent event) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
        List<InputEvent> processedEvents;
        try {
            processedEvents =
                mInputCompatProcessor.processInputEventForCompatibility(event);
        } finally {
			Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        if (processedEvents != null) {
            if (processedEvents.isEmpty()) {
                // InputEvent consumed by mInputCompatProcessor
                finishInputEvent(event, true);
            } else {
                for (int i = 0; i < processedEvents.size(); i++) {
                    enqueueInputEvent(
                        processedEvents.get(i), this,
                        QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                }
            }
        } else {
            enqueueInputEvent(event, this, 0, true);
        }
    }

    @Override
    public void onBatchedInputEventPending(int source) {
        // mStopped: There will be no more choreographer callbacks if we are stopped,
        // so we must consume all input immediately to prevent ANR
        final boolean unbuffered = mUnbufferedInputDispatch
                || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE
                || mStopped;
        if (unbuffered) {
            if (mConsumeBatchedInputScheduled) {
                unscheduleConsumeBatchedInput();
            }
            // Consume event immediately if unbuffered input dispatch has been requested.
            consumeBatchedInputEvents(-1);
            return;
        }
        scheduleConsumeBatchedInput();
    }

    @Override
    public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
        windowFocusChanged(hasFocus, inTouchMode);
    }

    @Override
    public void dispose() {
        unscheduleConsumeBatchedInput();
        super.dispose();
    }
}

ViewRootImpl#enqueueInputEvent

void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, 
    int flags, boolean processImmediately) {
    ......
    doProcessInputEvents();
    ......
}

ViewRootImpl#doProcessInputEvents

void doProcessInputEvents() {
    ......
    deliverInputEvent(q);
    ......
}

ViewRootImpl#deliverInputEvent

private void deliverInputEvent(QueuedInputEvent q) {
    ......
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }
    ......
    // stage 为 ViewPostImeInputStage
    stage.deliver(q);
    ......
}

ViewRootImpl#InputStage#deliver

abstract class InputStage {
    public final void deliver(QueuedInputEvent q) {
        // 调用的是 ViewPostImeInputStage 的 onProcess
        result = onProcess(q);
    }
}

ViewRootImpl#ViewPostImeInputStage#onProcess

final class ViewPostImeInputStage extends InputStage {
    @Override
    protected int onProcess(QueuedInputEvent q) {
        return processPointerEvent(q);
    }
    
    private int processPointerEvent(QueuedInputEvent q) {
        // view 就是 DecorView
        boolean handled = mView.dispatchPointerEvent(event);
    }
}

View#dispatchPointerEvent

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        // 这里是 DecorView 的 dispatchTouchEvent
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

DecorView#dispatchTouchEvent

会调用 Activity 的 dispatchTouchEvent

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // cb 是 Activity
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

Activity#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // 调用 PhoneWindow 的 superDispatchTouchEvent
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

PhoneWindow#superDispatchTouchEvent

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

DecorView#superDispatchTouchEvent

最终调用 ViewGroup 的 dispatchTouchEvent 进行事件分发。

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

ViewGroup#dispatchTouchEvent

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
}

事件分发分析

ViewGroup#dispatchTouchEvent

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

        // 重置状态,只会执行一次
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 取消并清除所有触摸目标
            // 通过mFirstTouchTarget记录链表,将它进行清空
            cancelAndClearTouchTargets(ev);
            // 清除所有触摸目标
            resetTouchState();
        }

        // 检查拦截
        final boolean intercepted;
		
        // 触摸事件经过此判断,mFirstTouchTarget 在 addTouchTarget 方法中赋值
        if (actionMasked==MotionEvent.ACTION_DOWN || mFirstTouchTarget != null){
            // 子view是否允许父布局拦截,即调用requestDisallowInterceptTouchEvent设置的
            // requestDisallowInterceptTouchEvent() 设置为 true不拦截,false拦截
            // 当为 down事件时,会进行重置,即disallowIntercept 为false默认值
            final boolean disallowIntercept =
                        (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            // 当允许拦截时,查看是否需要拦截
            if (!disallowIntercept) {
                // 通过 onInterceptTouchEvent() 的返回值判断是否拦截,true拦截,false不拦截
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else { 
                // 当父布局不允许拦截时,设置为false,不需要关注
                // onInterceptTouchEvent()的返回值
                intercepted = false;
            }
        } else {
            // 如果不是down事件,且没有下一个触摸目标,则此时直接拦截,设置为true
            // 如果down事件子view没有消费,即mFirstTouchTarget为null,也会拦截
            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);
        }

        // 检查是否为取消事件(之前被取消了 || 事件本身是取消事件)
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        // 是否允许多指操作,默认为true (true可以多指,false不允许多指)
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;
        // 新的触摸目标
        TouchTarget newTouchTarget = null;
        // 标记已经找到了新的触发目标
        boolean alreadyDispatchedToNewTouchTarget = false;
        // 不是取消 并且 没有被拦截,会去分发事件了
        if (!canceled && !intercepted) {
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

            // down事件、多指down事件、鼠标
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 事件的index,ACTION_DOWN的index为0
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                // 手指id,最多识别32个手指,位运算,一位代表一个手指
                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);

                // 子view的个数
                final int childrenCount = mChildrenCount;
                // newTouchTarget 肯定为null,也就是当有子view的时候进入if
                if (newTouchTarget == null && childrenCount != 0) {
                    // 获取触摸的坐标
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // 将子view进行排序
                    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);
                        // 是否能处理点击事件(view是否可见、Animation不为空)
                        // 点击的位置是否在view的范围内
                        if (!child.canReceivePointerEvents() 
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
                        // 单指操作,为null,多指才不为空
                        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;
                        }
						
                        // 重置子view被设置取消的标志
                        resetCancelNextUpFlag(child);
						
                        // dispatchTransformedTouchEvent 是询问 child 是否处理事件
                        // 为true,进入if
                        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 赋值,这个值就是mFirstTouchTarget
                            // 链表的next就是这个child
                            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;
                }
            }
        }

        // mFirstTouchTarget为空,表示没有child,或者child没有处理事件
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            // 询问自己是否处理
            // 第三个参数child为null,name就会调用super.dispatchTouchEvent()
            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只会执行一次
            while (target != null) {
                // 单指操作,next为 null
                final TouchTarget next = target.next;
                // 如果已经处理了,直接返回
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    // 是否取消事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    	    || intercepted;
                    // 询问 target.child 是否消费事件
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                    	    target.child, target.pointerIdBits)) {
                        handled = true; // 消费
                    }
                    // 取消child处理事件
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
		        }
                predecessor = target;
                target = next;
            }
        }

        // 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;
}

ViewGroup#cancelAndClearTouchTargets

// 取消并清除所有触摸目标
private void cancelAndClearTouchTargets(MotionEvent event) {
    if (mFirstTouchTarget != null) {
        boolean syntheticEvent = false;
        if (event == null) {
            final long now = SystemClock.uptimeMillis();
            event = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            syntheticEvent = true;
        }

        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            resetCancelNextUpFlag(target.child);
            // 执行取消事件
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
        // 清除所有触摸目标
        clearTouchTargets();

        if (syntheticEvent) {
            event.recycle();
        }
    }
}

ViewGroup#resetTouchState

// 重置所有触摸状态以准备新的循环
private void resetTouchState() {
    // 清除所有触摸目标
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    // 重置 mGroupFlags 的值
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

ViewGroup#clearTouchTargets

// 清除所有触摸目标
private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
	}
}

ViewGroup#dispatchTransformedTouchEvent

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

	// 取消事件
	// 不需要执行任何转换或过滤。 重要的部分是动作,而不是内容。
	final int oldAction = event.getAction();
	// if 取消事件
	if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
		event.setAction(MotionEvent.ACTION_CANCEL);
		// 不包含child,则由父类(View类)处理
		if (child == null) {
			handled = super.dispatchTouchEvent(event);
		} else { // 包含child,向child进行分发,拿到返回值,表示是否消费了
			handled = child.dispatchTouchEvent(event);
		}
		event.setAction(oldAction);
		return handled;
	}

	// 计算要传递的指针数
	final int oldPointerIdBits = event.getPointerIdBits();
	final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

	// 如果由于某种原因我们最终处于不一致的状态
	// 看起来我们可能会产生一个没有指针的运动事件,然后删除该事件
	if (newPointerIdBits == 0) {
		return false;
	}

	// If the number of pointers is the same and we don't need to perform any fancy
	// irreversible transformations, then we can reuse the motion event for this
	// dispatch as long as we are careful to revert any changes we make.
	// Otherwise we need to make a copy.
	final MotionEvent transformedEvent;
	if (newPointerIdBits == oldPointerIdBits) {
		if (child == null || child.hasIdentityMatrix()) {
			if (child == null) {
				handled = super.dispatchTouchEvent(event);
			} else {
				final float offsetX = mScrollX - child.mLeft;
				final float offsetY = mScrollY - child.mTop;
				event.offsetLocation(offsetX, offsetY);

				handled = child.dispatchTouchEvent(event);

				event.offsetLocation(-offsetX, -offsetY);
			}
			return handled;
		}
		transformedEvent = MotionEvent.obtain(event);
	} else {
		transformedEvent = event.split(newPointerIdBits);
	}

	// 执行任何必要的转换和调度
	if (child == null) {
	    // View.dispatchTouchEvent 事件处理
		handled = super.dispatchTouchEvent(transformedEvent);
	} else {
		final float offsetX = mScrollX - child.mLeft;
		final float offsetY = mScrollY - child.mTop;
		transformedEvent.offsetLocation(offsetX, offsetY);
		// 如果没有转换,则进行转换
		if (! child.hasIdentityMatrix()) {
			transformedEvent.transform(child.getInverseMatrix());
		}
        // child是容器,viewGroup的dispatchTouchEvent 事件分发
        // child是view,view的dispatchTouchEvent 事件处理
		handled = child.dispatchTouchEvent(transformedEvent);
	}

	// Done.
	transformedEvent.recycle();
	return handled;
}

ViewGroup#requestDisallowInterceptTouchEvent

disallowIntercept 为 true,不允许父布局拦截;
disallowIntercept 为 false,允许父布局拦截;
当设置为true、false的时候,会修改标志位,影响 ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0) 的值;
进而影响 dispatchTouchEvent方法中 disallowIntercept 的值:
     final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

@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);
    }
}

事件处理分析

View#dispatchTouchEvent

执行 onTouch 方法,如果它返回true,即result为true时,result取非,也就不会再执行 onTouchEvent 方法了,那么也自然不会再执行onClick方法了。

public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    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;
    }
    ......
}

// ListenerInfo 的初始化
// 当调用setOnTouchListener方法的时候
// View#setOnTouchListener
public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}
ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

View#onTouchEvent

onClick 是在 ACTION_UP 中调用的。

public boolean onTouchEvent(MotionEvent event) {
    // 获取点击的坐标
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    // 获取事件 down/up/move等
    final int action = event.getAction();

    ......

        switch (action) {
            case MotionEvent.ACTION_UP:
                // 当move滑出view的时候,会设置mPrivateFlags,这里就不会满足,无法进入到if里面了,不会再有 onClick 执行。
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        setPressed(true, x, y);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // 移除长按
                        removeLongPressCallback();

                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                // 这里
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }

                    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;

            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);
                    // 这里处理长按,当松手时,会到 ACTION_UP中进行判断是否长按
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;

            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)) {
                        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;
                }

                // 当坐标不在view中,通过 setPressed 设置 mPrivateFlags,
                // 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;
                }

                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;
        }

        return true;
    }

    return false;
}

View#PerformClick

private final class PerformClick implements Runnable {
    @Override
    public void run() {
        recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
        performClickInternal();
    }
}

View#performClickInternal

private boolean performClickInternal() {
    notifyAutofillManagerOnClick();

    return performClick();
}

View#performClick

这里就会去调用 onClick 方法了。

public boolean performClick() {
    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;
}

View#setPressed

设置 mPrivateFlags 标志位

public void setPressed(boolean pressed) {
    final boolean needsRefresh = pressed != 
            ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

    if (pressed) {
        mPrivateFlags |= PFLAG_PRESSED;
    } else {
        mPrivateFlags &= ~PFLAG_PRESSED;
    }

    if (needsRefresh) {
        refreshDrawableState();
    }
    dispatchSetPressed(pressed);
}

GestureDetector

GestureDetector可以进行手势的识别,其内部类SimpleOnGestureListener有进行手势处理的方法。

SimpleOnGestureListener

通过extends派生,根据需要重写需要的函数。

利用GestureDetector.onTouchEvent 在 View.onTouchEvent 方法中进行替换

private GestureDetector mGestureDetector;

mGestureDetector = new GestureDetector(context, new PhotoGestureListener());
class PhotoGestureListener extends GestureDetector.SimpleOnGestureListener {
    // 点击,抬起时触发
    // 双击,第二次抬起时触发
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.d(TAG, "onSingleTapUp: ");
        return super.onSingleTapUp(e);
    }

    // 长按触发,300ms
    // 触发顺序:onDown --> onShowPress --> onLongPress
    @Override
    public void onLongPress(MotionEvent e) {
        Log.d(TAG, "onLongPress: ");
        super.onLongPress(e);
    }

    /**
     * 滑动,类似move
     * @param e1
     * @param e2
     * @param distanceX 在 X轴 上的滑动的距离, 旧位置 - 新位置,也就是有可能是负的
     * @param distanceY 在 Y轴 上的滑动的距离, 旧位置 - 新位置,也就是有可能是负的
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.d(TAG, "onScroll: ");
        return super.onScroll(e1, e2, distanceX, distanceY);
    }

    /**
     * 抛掷,按下屏幕快速滑动松开,使其还有惯性的效果
     * @param e1 第一个 down事件
     * @param e2 最后一个 move事件
     * @param velocityX 在 X轴 上的滑动速度
     * @param velocityY 在 Y轴 上的滑动速度
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.d(TAG, "onFling: ");
        return super.onFling(e1, e2, velocityX, velocityY);
    }

    // 延时触发 100ms  如实现点击的效果
    @Override
    public void onShowPress(MotionEvent e) {
        Log.d(TAG, "onShowPress: ");
        super.onShowPress(e);
    }

    // 用户按下屏幕就会触发
    // 需要返回true,表示消费
    @Override
    public boolean onDown(MotionEvent e) {
        Log.d(TAG, "onDown: ");
        return true;
    }

    // 双击 第二次按下的时候触发  40ms - 300ms  当小于40ms的时候,有可能是抖动导致的
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.d(TAG, "onDoubleTap: ");
        return super.onDoubleTap(e);
    }

    // 双击的第二次的down、move、up事件触发
    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        Log.d(TAG, "onDoubleTapEvent: ");
        return super.onDoubleTapEvent(e);
    }

    // 单击按下时触发,双击时不触发
    // 延时300ms触发
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.d(TAG, "onSingleTapConfirmed: ");
        return super.onSingleTapConfirmed(e);
    }
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    boolean result = mGestureDetector.onTouchEvent(event);
    return result;
}

ScaleGestureDetector

手指缩放效果。

OnScaleGestureListener

private ScaleGestureDetector mScaleGestureDetector;

mScaleGestureDetector = new ScaleGestureDetector(context, new PhotoScaleGestureDetector());
@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d(TAG, "onTouchEvent: ");

    boolean result = mScaleGestureDetector.onTouchEvent(event);
    
    // isInProgress 用于返回是否正在进行手势缩放
    if (!mScaleGestureDetector.isInProgress()) {
        // TODO
    }
    
    return result;
}
class PhotoScaleGestureDetector implements ScaleGestureDetector.OnScaleGestureListener {
    // 手指缩放
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        return false;
    }

    // 返回true,消费事件
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }
}

示例演示1

viewPager 左右滑动,ListView上下滑动,解决冲突问题。
代码:地址

请添加图片描述

示例演示2

实现效果:双击放大、滑动、双指放大缩小等效果

代码:地址

请添加图片描述

示例演示3

实现效果:多指触控,拖动图片!

代码:地址

在这里插入图片描述

参考博文

深入理解ViewGroup的dispatchTouchEvent():地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值