@universal
2017-11-06T14:03:33.000000Z
字数 7935
阅读 344
android 源码分析
view的事件分发主要是指点击事件,也就是系统对MotionEvent的传递
首先,拓展了解下Android界面的架构图
Activity -->PhoneWindow -->DectorView -->TitleView/ContentView
key point:requestWindowFeature(Window.FEATURE_NO_TITLE)一定要在setContentView()方法之前调用才会生效
(至于原因,去看源码...)
这里所谓的点击事件所对应的对象就是MotionEvent, 典型有三种类型:
正常情况下,一次手指触摸屏幕的行为会产生一系列点击事件。事件序列即:
DOWN -> UP 或者 DOWN -> MOVE-> ... ->MOVE -> UP ;
一个事件序列由DOWN开始,中间含有n个MOVE事件,最终以UP结束。
传递规则
注:在处理事件时,如果一个View设置了OnTouchListener,那么listener中的OnTouch方法的返回值将影响事件的下一步传递,如果为true,则OnTouchEvent不会被调用,如果为false,则调用当前View的OnTouchEvent。在OnTouchEvent中,如果设置了OnClickListener,则OnClick方法会被调用。
从故事开始的地方说起,
当一个MotionEvent产生,事件最先传给activity,由activity进行分发,
public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}
activity会将事件交给window的dispatchTouchEvent去处理,如果下级View们都不处理,则调用自身的onTouchEvent处理
注:可以在activity中重写此方法,以便在事件发送到窗口之前拦截所有触摸屏事件
事件传给Window
//Windowpublic abstract boolean superDispatchTouchEvent(MotionEvent event);//PhoneWindow(实现类)@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}
抽象类window会将事件交由它的实现类 PhoneWindow去处理
PhoneWindow直接将事件传递给了DecorView
事件再到DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}
在上面曾提到,DecorView是继承自FrameLayout,所以直接调用父类的dispatchTouchEvent去处理事件,就这样将事件从activity传到了根View
事件进入正轨,从根ViewGroup开始
(一)
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {......// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {cancelAndClearTouchTargets(ev);resetTouchState();}//mFirstTouchTarget:First touch target in the linked list of touch targets.// Check for interception.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);} else {intercepted = false;}} else {intercepted = true;}}
先看其中的两个操作check for interception 、 handle an initial down;
(1)check for interception:
从字面意思来看:判断是否拦截事件。在当前事件为ACTION_DOWN时或者mFirstTouchTarget != null时,ViewGroup会判断是否拦截事件。这里的mFirstTouchTarget != null是指有子view要处理此事件。所以一旦ViewGroup决定拦截事件,那么mFirstTouchTarget就为null,而且当事件序列中后续的move和up事件到来时,判断条件不满足,导致ViewGroup中的OnInterceptTouchEvent不会被重复调用。
满足条件后,ViewGroup会判断标记位FLAG_DISALLOW_INTERCEPT的值(一般在子View中设置),如果禁止拦截,Viewgroup将无法拦截除了ACTION_DOWN之外的事件(因为在处理ACTION_DOWN事件的时候,Viewgroup会在下面的"initial down"中resetTouchState重置标志位);反之则调用onInterceptTouchEvent去判断是否拦截事件。
(2)handle an initial down:
在ACTION_DOWN事件到来的时候,清除上一个触摸手势所保存的状态,包括FLAG_DISALLOW_INTERCEPT标志位的重置。所以在面对ACTION_DOWN事件时,ViewGroup总会调用OnInterceptTouchEvent判断是否拦截事件。
(二)
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {....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....final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {....if (!canViewReceivePointerEvents(child)||!isTransformedTouchPointInView(x, y, child, null)){ev.setTargetAccessibilityFocus(false);continue;}....if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){....newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}}}// 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;}...}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {.....if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}return handled;.....}
如果ViewGroup不拦截事件,则交由分发给子项。具体实现:
先遍历ViewGroup所有子元素,判断子元素是否能收到点击事件,如果能,则事件会传递给它来处理。可以看到dispatchTransformedTouchEvent其实就是调用子view的dispatchTouchEvent。如果子View选择处理事件,则dispatchTouchEvent就会返回true,同时mFirstTouchTarget就会被赋值并结束循环。如果当前子元素不处理,则进入下一个子元素的循环。
如果ViewGroup没有子元素,或者子元素的dispatchTouchEvent返回了false,导致mFirstTouchTarget未被赋值,这时ViewGroup就会自己处理事件。
事件交给了View
(1)View中的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {....if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo 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;}}....return result;}
从代码中可以看出,View会先判断有没有设置OnTouchListener,如果有且onTouch方法返回true,则不再调用下面的onTouchEvent,这样就方便在外部处理MotionEvent。
(2)View中的onTouchEvent
public boolean onTouchEvent(MotionEvent event) {if ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);}// A disabled view that is clickable still consumes the touch// events, it just doesn't respond to them.return (((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);}}
这个地方就应证了上面所说的,view的enable属性不影响view消耗事件,只和CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE属性有关。
public boolean onTouchEvent(MotionEvent event) {if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP:....if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {// This is a tap, so remove the longpress checkremoveLongPressCallback();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();}}}break;case MotionEvent.ACTION_DOWN:// For views inside a scrolling container, delay the pressed feedback for// a short period in case this is a scroll.if (isInScrollingContainer) {....} else {// Not inside a scrolling container, so show the feedback right awaysetPressed(true, x, y);checkForLongClick(0, x, y);}break;case MotionEvent.ACTION_CANCEL:....break;case MotionEvent.ACTION_MOVE:....break;}return true;}}private final class CheckForLongPress implements Runnable {@Overridepublic void run() {if (isPressed() && (mParent != null)&& mOriginalWindowAttachCount == mWindowAttachCount) {if (performLongClick(mX, mY)) {mHasPerformedLongPress = true;}}}}
可以看到,当ACTION_UP事件发生时,如果不是longPress会调用performClick,在这个方法里,如果view设置了OnClickListener,则会调用它的OnClick方法,而且performClick是通过Runnable发布调用的,方便在处理事件之前更新View状态。
当ACTION_DOWN事件发生时,View会检查是否是LongClick事件,如果长按事件设置了回调,则执行回调方法。而且如果回调返回true的话,mHasPerformedLongPress就会设置为true,所以在长按时,当上面UP事件发生,performClick就无法调用,即短按事件就会被屏蔽。
View事件分发的流程大概就是这样,后面还会继续研究滑动冲突的问题。最后,说说源码分析的那点事儿,过程很蓝瘦,但是得学会取舍,一时看不懂先略过,注重大体逻辑,跟着翻译走,没准在后面就能明白前面变量或者方法的意义,水到渠成。