Activity和ViewGroup
- 简单Activity 和 LineaLayout,点击屏幕打印结果如下
01-19 09:34:14.007 7848-7848/com.list E/MainActivity: dispatchTouchEvent
01-19 09:34:14.007 7848-7848/com.list E/MyLineaLayout: dispatchTouchEvent
01-19 09:34:14.007 7848-7848/com.list E/MyLineaLayout: onInterceptTouchEvent
01-19 09:34:14.007 7848-7848/com.list E/MyLineaLayout: onTouchEvent
01-19 09:34:14.007 7848-7848/com.list E/MainActivity: onTouchEvent
01-19 09:34:14.037 7848-7848/com.list E/MainActivity: dispatchTouchEvent
01-19 09:34:14.037 7848-7848/com.list E/MainActivity: onTouchEvent
返回值中可以看到先调用的Activity的dispatchTouchEvent方法然后调用的ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。由于ViewGroup并没有消费事件所以事件有分发到了上层Activity onTouchEvent、dispatchTouchEvent、onTouchEvent。
- 如果在界面增加一个Button点击按钮
01-19 09:37:10.037 8255-8255/com.list E/MainActivity: dispatchTouchEvent
01-19 09:37:10.037 8255-8255/com.list E/MyLineaLayout: dispatchTouchEvent
01-19 09:37:10.037 8255-8255/com.list E/MyLineaLayout: onInterceptTouchEvent
01-19 09:37:10.037 8255-8255/com.list E/MainActivity: btn - onTouch
01-19 09:37:10.114 8255-8255/com.list E/MainActivity: dispatchTouchEvent
01-19 09:37:10.114 8255-8255/com.list E/MyLineaLayout: dispatchTouchEvent
01-19 09:37:10.114 8255-8255/com.list E/MyLineaLayout: onInterceptTouchEvent
- 如果在界面增加一个TextView
01-19 09:40:38.780 8586-8586/com.list E/MainActivity: dispatchTouchEvent
01-19 09:40:38.780 8586-8586/com.list E/MyLineaLayout: dispatchTouchEvent
01-19 09:40:38.780 8586-8586/com.list E/MyLineaLayout: onInterceptTouchEvent
01-19 09:40:38.780 8586-8586/com.list E/MyLineaLayout: onTouchEvent
01-19 09:40:38.780 8586-8586/com.list E/MainActivity: onTouchEvent
01-19 09:40:38.843 8586-8586/com.list E/MainActivity: dispatchTouchEvent
01-19 09:40:38.843 8586-8586/com.list E/MainActivity: onTouchEvent
对比Button和TextView差别可以看出button消费了点击事件,而TextView没有消费掉,事件传递回了Activity.
- 分析源码来看看上面的图是否正确
- 先按照黑色的箭头
- 先看MainActivity的dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 默认调用的是父类的dispatchTouchEvent方法 那么进入Activity的dispatchTouchEvent方法
return super.dispatchTouchEvent(ev);
}
- Activity的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//空方法 down的时候用户可以添加交互
onUserInteraction();
}
// getWindow().superDispatchTouchEvent方法最后实现是在ViewGroup的dispatchTouchEvent方法
// 接下来进入ViewGroup
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- ViewGroup的dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//做一些事件验证 忽略
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// 判断事件的类型 一些类型的事件会直接调用到click方法 比如自动抢红包和自动化测试等会使用到相关内容
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//定义
boolean handled = false;
//做事件安全校验
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
// down是事件的初始
if (actionMasked == MotionEvent.ACTION_DOWN) {
//这个方法对targets和event进行了 recycle并且 置为null
cancelAndClearTouchTargets(ev);
// 此方法重置一些state 具体先不分析
resetTouchState();
}
// 定义是否拦截的变量
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//此变量是是否 不允许拦截 在子View中有一个方法 requestDisallowInterceptTouchEvent();设置父类是否不允许拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 如果允许拦截
if (!disallowIntercept) {
// 去onInterceptTouchEvent方法看一下
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.
intercepted = true;
}
// ............................
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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);
// Find a child that can receive the event.
// Scan children from front to back.
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);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 判断child是否接收事件 根据焦点 点击位置信息等
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// ......
//进入dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...}
}
- 下面为ViewGroup的dispatchTransformedTouchEvent方法
- 如果child不是null 调用的 handled = child.dispatchTouchEvent(event);
- 接下来进入了view的dispatchTouchEvent方法
- 如果child == null 则返回给父类dispatchTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
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);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
- 接下来看一下view的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
....
....
// 里面有这句 如果result是false 则进入了onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
- view的onTouchEvent方法 一些主要的代码
public boolean onTouchEvent(MotionEvent event) {
//..................
//如果满足条件 会执行switch代码 然后return true 否则 return false
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//.......
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// 调用了performClick方法 接下来看一下此方法
performClick();
}
}
}
break;
//..........
}
return true;
}
return false;
}
- view的performClick()方法
- 如果设置了onClickListener直接回调回去
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
- 在看看 当onTouchEvent return true 或者 false有哪些操作
接下来回到view的dispatchTouchEvent方法刚刚的地方
if (!result && onTouchEvent(event)) {
result = true;
}
- 如果onTouchEvent返回true
那么result直接返回了true 此方法结束
再回到ViewGroup的dispatchTransformedTouchEvent方法 handler 返回为true
此时 ViewGroup的dispatchTouchEvent方法返回true
Activity的dispatchTouchEvent直接返回true就不会走touchEvent了
- 如果onTouchEvent返回false
view的dispatchTouchEvent方法
//此判断将不满足
if (!result && onTouchEvent(event)) {
result = true;
}
最后dispatchTouchEvent() return的是false 接下来回到ViewGroup的dispatchTransformedTouchEvent()方法
返回false 接下来 ViewGroup的dispatchTouchEvent() 返回false,然后再回头看Activity的Activity的dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
进入了 onTouchEvent方法然后无论事件是否消费 都结束传递