摘要:本文将带你了解Android应用开发之从源码的角度分析Android的事件分发机制的具体实现,希望本文对大家学Android有所帮助。
本文将带你了解Android应用开发之从源码的角度分析Android的事件分发机制的具体实现,希望本文对大家学Android有所帮助。
今天,我们从源码的角度来分析Android的事件分发机制的具体实现。
1、Activity的事件处理机制
Android的触屏事件首先是从Activity的dispatchTouch Event开始分发的,所以我们先来看看Activity的dispatchTouchEvent代码:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);}
当是ACTION_DOWN事件时,首先会调用onUserInteraction这个函数,这个方法在Activity里的实现是空的,用户在继承Activity时可以重写该方法。往下看到getWindow这个方法:
?123public Window getWindow() { return mWindow;}
返回的是一个mWindow的Window对象,而这个mWindow实际上是一个PhoneWindow对象,于是就变成了调用PhoneWindow类的superDispatchTouchEvent,继续看源码:
?123public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
这里又跑出了一个mDecor对象,它是DecorView的对象,这里简单提一下Activity的组成,Activity实际是有Window来管理视图的,而这个Window一般由PhoneWindow实现,但PhoneWindow实际并不程序界面效果,这个操作就由DecorView来实现。DecorView继承自Framelayout,是整个视图的根,如下图所示:
所以,上面mDecor.superDispatchTouchEvent最终调的方法是ViewGroup的dispatchTouchEvent这个方法。
2、ViewGroup的事件处理流程
我们接下来看ViewGroup的源码,由于ViewGroup源码较长,我们挑关键部分解析:
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. cancelAndClearTouchTargets(ev); resetTouchState();}
这一段代码主要是在检测到ACTION_DOWN事件做一些标记清除和初始化事件
?1
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;}
从上面代码我们可以看到,当事件类型为ACTION_DOWN或者mFirstTouchTarget不为空时,ViewGroup会调用onInterceptTouchEvent来判断当前事件是否要拦截,ACTION_DOWN我们很熟悉了,那么mFirstTouchTarget是什么呢?在ViewGroup的子元素成功处理事件时,mFirstTouchTarget会被赋值并指向子元素,回头看初始化那一段代码,cancelAndClearTouchTargets实际上就是清除mFirstTouchTarget所指向的元素。从这一段代码可以看到这两种情况:当事件类型为ACTION_DOWN时,VIewGroup肯定会进入拦截判断;而在后续的如果有子元素处理了即消费了事件,那么这个mFirstTouchTarget不为空,那么ViewGroup也会进入拦截判断。这样印证了我们上一节中如果是ViewGroup自己消费了事件,后续的Move和up事件是不会回调onInterceptTouchEvent的。所以如果我们想提前处理所有的点击事件,一般使用dispatchTouchEvent而不是onInterceptTouchEvent。
接着往下看:
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 indexfor (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; }
当ViewGroup不拦截事件时,ViewGroup会遍历子元素,判断子元素是否能够接收到点击事件。如果子元素View能接收到事件,则调用dispatchTransformedTouchEvent这个方法,我们看看这个方法:
?12345678910if (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方法,这里就把事件传递给子View了,从而完成了这一轮的事件分。子元素的事件处理我们后面在分析,我们回到上面的代码,可以看到如果这个dispatchTransformedTouchEvent返回true,就会在
?12newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;
给mFirstTouchTarget赋值,也就是我们前面锁看到的进入拦截判断的那个条件。而假如遍历完整个ViewGroup,mFirstTouchTarget仍为null,由上面代码可知一种情况是子元素的dispatchTouchEvent返回false(即子元素没有消费事件),另一种情况是ViewGroup没有子元素,此时:
?123456// 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);}
这里又再一次调用了dispatchTransformedTouchEvent这个方法,只不过第三个参数(参数传的是子元素)设为null,回头看刚刚dispatchTransformedTouchEvent的方法:
?12345if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }
假如子元素不为null才会调子元素的dispatchTouchEvent,而如果子元素为null,则调ViewGroup的super.dispatchTouchEvent自己处理事件。这里的super.dispatchTouchEvent即调的是View的dispatchTouchEvent方法来处理事件(ViewGroup实际也是View,它继承自View)。
3、View的事件处理过程
接下来我们来看View的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) { ...//省略部分代码 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; } } ...//省略部分代码 return result;}
View的dispatchTouchEvent相对简单一些,从上面的代码可以看到,View在处理事件时,首先会判断有没有设置OnTouchEListener,如果设置了则会先执行onTouch来处理事件,如果onTouch返回true,则不会进入onTouchEvent这个方法,这里正印证了上一篇博客最后面的分析,onTouch先于onTouchEvent执行,且如果onTouch消费了事件,则后面的onTouchEvent不会执行的结论。
接下来分析View的onTouchEvent的实现:
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable;}
先进来判断View是否是disabled不可用的状态,由上面的代码可知,即使VIew处于不可用的状态,也是会消费事件的,只是不会对事件做出响应(看注释翻译),接着往下看
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP:...//省略部分代码boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { .../ 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();} } } ...//省略部分代码}break; } return true;
这里的clickable的赋值过程在上面的代码里已经给出,由此可以知道只要当CLICKABLE或者LONG_CLICKABLE当中一个为true,onTouchEvent就会返回true,即消耗了这个事件,回顾上一章,也印证了这个结论。然后在ACTION_UP触发时,会调用performClick(),如果View设置了OnclickListener,performClick内部就会调用它的onClick方法:
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;}
然后结合View的OnCickListener源码:
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l;}
public void setClickable(boolean clickable) { setFlags(clickable CLICKABLE : 0, CLICKABLE);}
这里把CLICKABLE标志位设置为true,然后给mOnClickListener赋值,结合上面的代码,逻辑就清晰了。
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注移动开发之Android频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号