目录
3、ViewGroup.dispatchTouchEvent方法
一、源码分析(activity为例)
1、逻辑
我们先想清楚。我们点击屏幕,响应单击事件都需要先点下去,然后抬起来。这对应的事件是DOWN、UP。以DOWN开始,UP触发响应为结束。那滑动呢?以点击开始,左右滑动响应,以手抬起来为滑动结束。以DOWN开始,中间无数个MOVE,UP结束滑动。
2、开头时序
直接从上层(java)开始描述,首先上层是从ViewRootImpl.ViewPostImeInputStage类中的processPointerEvent()方法开始响应底层传上来的事件信息。
//ViewRootImpl#ViewPostImeInputStage
private int processPointerEvent(QueuedInputEvent q) {
//使用底层传上来的信息
final MotionEvent event = (MotionEvent)q.mEvent;
......
//这里的mView,熟悉activity的启动流程的知道这是DecorView
boolean handled = mView.dispatchPointerEvent(event);
......
return handled ? FINISH_HANDLED : FORWARD;
}
然而我们在DecorView中并没有发现dispatchPointerEvent方法,需要去他的父类View中寻找。接下去的调用我就快速过了。
//View
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
//调用了dispatchTouchEvent方法,注意当前是DecorView调用的,所以需要去DecorView中寻找该方法
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
//DecorView
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//注意这边的cb是我们的activity,不了解的可以去了解activity的启动
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
//这边DecorView是继承FrameLayout extends ViewGroup,调用该的是ViewGroup中的方法
return super.dispatchTouchEvent(event);
}
//Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//这边的window是PhoneWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//这边又调用了DecorView的方法
return mDecor.superDispatchTouchEvent(event);
}
总结:这段的时序ViewRootImpl->View->DecorView->Activity->PhoneWindow->DecorView->ViewGroup。可以看到以上的一些方法都是普通的调用方法,没逻辑。其实可以直接理解为Activity->ViewGroup。如果说有mView为什么是DecorView,getWindow为什么是PhoneWindow的疑问的话,可以了解一下activity的启动。
3、ViewGroup.dispatchTouchEvent方法
我们需要仔细看ViewGroup的dispatchTouchEvent方法。
3.1.清理之前触摸对象
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
//如果事件是DOWN,就把之前触摸对象、状态移除
cancelAndClearTouchTargets(ev);
resetTouchState();
}
3.2.是否需要拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//如果是down 或者 触摸对象不为空
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//是否允许父类拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//表示没有触摸对象,且事件不是DOWN,那么就没有分发的意义了,直接拦截掉
intercepted = true;
}
3.3.是否取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
3.4.生成TouchTarget对象
//如果存在子View的话,进入下边的判断中寻找到点击的View,并生成对应的TouchTarget
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//isTransformedTouchPointInView 传了xy坐标,判断child这个子View是否在我们点击的范围内
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
//如果不是,不走下面的。直接下一次循环
continue;
}
//获取child这个View的TouchTarget对象
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;
}
resetCancelNextUpFlag(child);
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();
//生成当前View的TouchTarget对象
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//如果没有找到一个子View接受这个事件
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;
}
3.5.调度以触及目标
//Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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.
......
//注意第三个参数,把子View传进去了
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
......
}
3.6.准备调用dispatchTouchEvent方法
//核心代码 如果有传过来子View就调用子View的方法,否则就自身调用该方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
......
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
......
}
整个ViewGroup.dispatchTouchEvent的方法就调用结束了。开始调用View的dispatchTouchEvent方法了。
4.View.dispatchTouchEvent方法
//该方法中值得注意的代码块儿
ListenerInfo li = mListenerInfo;
//第三个条件:此View是否是enabled
//第四个条件:调用onTouch方法。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果result=true 就不会调用onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
所以从这我们就能够了解到了方法的正常情况执行顺序,onInterceptTouchEvent->onTouch->onTouchEvent。如果你不希望后边消费掉事件,onTouch返回false即可。onTouch是我们设置的TouchListener,具体要看咱们自己的实现。我们需要往下边onTouchEvent看。
5.View.onTouchEvent方法
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
.......
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//响应点击事件
performClickInternal();
}
......
break;
case MotionEvent.ACTION_DOWN:
......
//设置按压状态
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
......
break;
case MotionEvent.ACTION_MOVE:
......
//判断xy是否在目前的View中
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
//设置取消按压,方法里有对mPrivate的十六进制数进行一些与非操作
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
......
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
}
二、例子思考
实现一个自定义View,设置事件。重写onTouchEvent方法。
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Log.d("test===LongClick", "onLongClick");
return false;
}
});
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("test===Click", "onClick");
}
});
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("test===onTouch", "ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d("test===onTouch", "ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d("test===onTouch", "ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d("test===onTouch", "ACTION_CANCEL");
break;
}
return false;
}
});
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("test===onTouchEvent", "ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d("test===onTouchEvent", "ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d("test===onTouchEvent", "ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d("test===onTouchEvent", "ACTION_CANCEL");
break;
}
return super.onTouchEvent(event);
}
1、点击View,划出View的范围的执行。
运行结果:
2、点击View,正常点击事件
3、长按事件,两种情况(有无滑动)
总结:这样的顺序咱们再看源码的时候其实,已经看清楚了。当然了长按后续的方法我并没有继续点下去,他是用了Handler(这个handler是初始化是传进来的是ViewRootImpl里的Handler)进行延迟执行,大家有兴趣可以自行看看。