Android30
文章目录
- MotionEvent
- 手势
- 点击事件的传递规则
- 事件接收流程分析
- ViewRootImpl#setView
- ViewRootImpl#WindowInputEventReceiver#onInputEvent
- ViewRootImpl#enqueueInputEvent
- ViewRootImpl#doProcessInputEvents
- ViewRootImpl#deliverInputEvent
- ViewRootImpl#InputStage#deliver
- ViewRootImpl#ViewPostImeInputStage#onProcess
- View#dispatchPointerEvent
- DecorView#dispatchTouchEvent
- Activity#dispatchTouchEvent
- PhoneWindow#superDispatchTouchEvent
- DecorView#superDispatchTouchEvent
- ViewGroup#dispatchTouchEvent
- 事件分发分析
- 事件处理分析
- GestureDetector
- ScaleGestureDetector
- 示例演示1
- 示例演示2
- 示例演示3
- 参考博文
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():地址