经过滴滴 尚德 秒拍 凡普金科 等这么多家公司的面试,这个问题每个公司都会被提问而且提问的方式也各有不同。但是百问不离其宗:你知道安卓的时间分发机制吗?一开始我对这种问题态度基本上都是不屑,因为最早面试的时候这种问题就是靠背面试题了解到的,到了后来写了这么多的自定义控件,自定义组合控件view 的事件机制具体是怎么一个逻辑也明白个七七八八,但是一直没有系统起来,反而在面试的时候说的模模糊糊,面试官的脸色瞬间就变了,蜜汁尴尬。
这是一个我自定义的ViewGroup,只是复写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent 在调用super方法前打印出方法名:
I/System.out: Activity:dispatchTouchEvent
I/System.out: dispatchTouchEvent
I/System.out: onInterceptTouchEvent
I/System.out: onTouchEvent
I/System.out: Activity:dispatchTouchEvent
1 由此可知每个ViewGroup 都经历
dispatchTouchEvent >>> onInterceptTouchEvent >>> onTouchEvent
其中 除了onTouchEvent是View的 其余dispatchTouchEvent,onInterceptTouchEvent都属于ViewGroup的方法
2 最先接收到事件的地方竟然是Activity的 dispatchTouchEvent
问题:那activity接触到事件是怎么分发下去的呢?
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
从这段注释看来 这个方法是最早处理屏幕触摸事件。
问题:MotionEvent从何而来?
这个方法其实并不是activity本身的方法,该方法来自于Window.Callback 这个回调,并在activity的attach这个方法中mWindow.setCallback(this);
分析Activity的dispatchTouchEvent方法真正内容在这里:getWindow().superDispatchTouchEvent(ev) 我们都知道Activity的window的实现是PhoneWindow:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor最顶层的view 进入源码查看:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
看来事件分发机制本质上就是从ViewGroup的dispatchTouchEvent开始的,
dispatchTouchEvent
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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.
intercepted = true;
}
onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
当ViewGroup不拦截事件的时候事件是如何向下分发
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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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();
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);
}
这段代码很长,但是大概逻辑还是能看明白的,就是遍历ViewGroup的所有子View,然后判断子view是不是能顺利接收到事件(有没有焦点,是否显示,能否接收到事件)主要还是要有两点来衡量:子元素是否在播放动画和事件的坐标是否在子元素内:
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
就是这两个方法。如果满足就会调用:
dispatchTransformedTouchEvent
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;
}
看到源码就知道 这里实际上是调用子元素的dispatchTouchEvent,如果没有子元素 就直接调用super的dispatchTouchEvent方法。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
这里完成了mFirstTouchTarget的赋值,如果子元素的dispatchTouchEvent返回为false,ViewGroup就会把事件分发给下一个子元素(如果还有子元素的话),如果没有给mFirstTouchTarget赋值(mFirstTouchTarget=null)viewgroup将会拦截同一序列中的所有事件
我们都知道ViewGroup继承自View,当group没有自view的时候这些事件就交由View来处理了:
View的dispatchTouchEvent
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
}
重点在这里:首先判断有没有设置touchListener之类的事件将事件传给监听,如果没有设置监听,view自己的onTouchEvent会响应
网友评论