码迷,mamicode.com
首页 > 移动开发 > 详细

Android事件分发机制的学习

时间:2014-12-25 23:21:10      阅读:317      评论:0      收藏:0      [点我收藏+]

标签:

 

最近被Android事件分发机制折磨的很烦躁,网上各种博客资料看完觉得还是得自己写一篇,一方面加深理解,另一方面希望能帮助到也同样在学习相关知识的童鞋们。

话不多说,直接开整。

当用户的手指点击到屏幕,便是整个事件的开始。

  首先获取到该事件的是view层的控制者Activity,具体怎么获得我们不得而知,在此也不追究,而继续我们的主题。Activity获得事件后便执行它自身的方法: 

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

  在这个方法中,首先判断是否是down事件,是的话就实现一个回调方法。当然这不是重点,重点是 getWindow().superDispatchTouchEvent(ev) 这个方法的返回值,如果返回true的话,那么该activity的 dispatchTouchEvent 方法直接返回true,不再执行下面的代码。若是这个方法返回false,那么将执行该activity的onTouchEvent 方法。那么我们就要首先看下getWindow()是什么,它的 superDispatchTouchEvent 方法又是如何:

public Window getWindow() {
        return mWindow;
    }

再继续看mWindow:

mWindow是这么初始化的:
mWindow = PolicyManager.makeNewWindow(this);

public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

继续寻找IPolicy接口的实现类Policy中:
public PhoneWindow makeNewWindow(Context context) {
        return new PhoneWindow(context);
}
再继续看PhoneWindow:

@Override
    public boolean superDispatchTrackballEvent(MotionEvent event) {
        return mDecor.superDispatchTrackballEvent(event);
    }

而mDecor是PhoneWindow的内部类DecorView的对象:
public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
}

decorView是FrameLayout的子类:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

FrameLayout中并没有dispatchTouchEvent方法,而FrameLayout是ViewGroup的子类,所以终于来到来第一个重点:viewGroup的dispatchTouchEvent 方法。

  

  1 @Override
  2     public boolean dispatchTouchEvent(MotionEvent ev) {
  3         if (!onFilterTouchEventForSecurity(ev)) {
  4             return false;
  5         }
  6 
  7         final int action = ev.getAction();
  8         final float xf = ev.getX();
  9         final float yf = ev.getY();
 10         final float scrolledXFloat = xf + mScrollX;
 11         final float scrolledYFloat = yf + mScrollY;
 12         final Rect frame = mTempRect;
 13 
 14         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 15 
 16         if (action == MotionEvent.ACTION_DOWN) {
 17             if (mMotionTarget != null) {
 18                 // this is weird, we got a pen down, but we thought it was
 19                 // already down!
 20                 // XXX: We should probably send an ACTION_UP to the current
 21                 // target.
 22                 mMotionTarget = null;
 23             }
 24             // If we‘re disallowing intercept or if we‘re allowing and we didn‘t
 25             // intercept
 26             if (disallowIntercept || !onInterceptTouchEvent(ev)) {
 27                 // reset this event‘s action (just to protect ourselves)
 28                 ev.setAction(MotionEvent.ACTION_DOWN);
 29                 // We know we want to dispatch the event down, find a child
 30                 // who can handle it, start with the front-most child.
 31                 final int scrolledXInt = (int) scrolledXFloat;
 32                 final int scrolledYInt = (int) scrolledYFloat;
 33                 final View[] children = mChildren;
 34                 final int count = mChildrenCount;
 35 
 36                 for (int i = count - 1; i >= 0; i--) {
 37                     final View child = children[i];
 38                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
 39                             || child.getAnimation() != null) {
 40                         child.getHitRect(frame);
 41                         if (frame.contains(scrolledXInt, scrolledYInt)) {
 42                             // offset the event to the view‘s coordinate system
 43                             final float xc = scrolledXFloat - child.mLeft;
 44                             final float yc = scrolledYFloat - child.mTop;
 45                             ev.setLocation(xc, yc);
 46                             child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 47                             if (child.dispatchTouchEvent(ev))  {
 48                                 // Event handled, we have a target now.
 49                                 mMotionTarget = child;
 50                                 return true;
 51                             }
 52                             // The event didn‘t get handled, try the next view.
 53                             // Don‘t reset the event‘s location, it‘s not
 54                             // necessary here.
 55                         }
 56                     }
 57                 }
 58             }
 59         }
 60 
 61         boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
 62                 (action == MotionEvent.ACTION_CANCEL);
 63 
 64         if (isUpOrCancel) {
 65             // Note, we‘ve already copied the previous state to our local
 66             // variable, so this takes effect on the next event
 67             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
 68         }
 69 
 70         // The event wasn‘t an ACTION_DOWN, dispatch it to our target if
 71         // we have one.
 72         final View target = mMotionTarget;
 73         if (target == null) {
 74             // We don‘t have a target, this means we‘re handling the
 75             // event as a regular view.
 76             ev.setLocation(xf, yf);
 77             if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
 78                 ev.setAction(MotionEvent.ACTION_CANCEL);
 79                 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 80             }
 81             return super.dispatchTouchEvent(ev);
 82         }
 83 
 84         // if have a target, see if we‘re allowed to and want to intercept its
 85         // events
 86         if (!disallowIntercept && onInterceptTouchEvent(ev)) {
 87             final float xc = scrolledXFloat - (float) target.mLeft;
 88             final float yc = scrolledYFloat - (float) target.mTop;
 89             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 90             ev.setAction(MotionEvent.ACTION_CANCEL);
 91             ev.setLocation(xc, yc);
 92             if (!target.dispatchTouchEvent(ev)) {
 93                 // target didn‘t handle ACTION_CANCEL. not much we can do
 94                 // but they should have.
 95             }
 96             // clear the target
 97             mMotionTarget = null;
 98             // Don‘t dispatch this event to our own view, because we already
 99             // saw it when intercepting; we just want to give the following
100             // event to the normal onTouchEvent().
101             return true;
102         }
103 
104         if (isUpOrCancel) {
105             mMotionTarget = null;
106         }
107 
108         // finally offset the event to the target‘s coordinate system and
109         // dispatch the event.
110         final float xc = scrolledXFloat - (float) target.mLeft;
111         final float yc = scrolledYFloat - (float) target.mTop;
112         ev.setLocation(xc, yc);
113 
114         if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
115             ev.setAction(MotionEvent.ACTION_CANCEL);
116             target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
117             mMotionTarget = null;
118         }
119 
120         return target.dispatchTouchEvent(ev);
121     }

  首先第14行 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 中判断该ViewGroup的是否允许拦截事件的标志位,一般情况下disallowIntecept的值都是为false的,因为在67行中会在up或cancel事件中将这个标志位重置。

  进入down事件判断后会将 mMotionTarget 置为null,而mMotionTarget是下面要寻找的目标。进入 disallowIntercept || !onInterceptTouchEvent(ev) 判断中, disallowIntercept为false,onInterceptTouchEvent(ev)方法直接返回false,所以结果为true进入该判断。在这个判断的for循环中,会一直遍历子view (VISIBLE且没有执行动画的view),直到找到包含被用户点击坐标的子view。进入 child.dispatchTouchEvent(ev) 判断中,如果该方法返回true,就将mMotionTarget设置为该view,并且返回true。如果没有找到,那么mMotionTarget便为null。

  找到mMotionTarget的情况下,最终activity的dispatchTouchEvent方法返回true,表示down事件被mMotionTarget消费了。

  没有找到mMotionTarget的情况下,看72行,target也就为null,进入target==null的判断,在81行返回super.dipatchTouchEvent,而viewGroup的父类是View,所以继续看View的dispatchTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) {
            return false;
        }

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

依旧忽略第一个判断,看第二个判断:先看mOnTouchListener是否为null,也就是是否给view设置了onTouchListener;再看这个view是否enable;最后看设置的OnTouchListener 的 onTouch 方法的返回值是否为true。如果三个条件都满足,该方法返回true,ViewGroup的dispatchTouchEvent返回true,activity的dispatchTouchEvent返回true,表示该viewGroup消费了此事件。如果三个条件有一个不满足,那么执行view的 onTouchEvent 方法。

  1 public boolean onTouchEvent(MotionEvent event) {
  2         final int viewFlags = mViewFlags;
  3 
  4         if ((viewFlags & ENABLED_MASK) == DISABLED) {
  5             // A disabled view that is clickable still consumes the touch
  6             // events, it just doesn‘t respond to them.
  7             return (((viewFlags & CLICKABLE) == CLICKABLE ||
  8                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  9         }
 10 
 11         if (mTouchDelegate != null) {
 12             if (mTouchDelegate.onTouchEvent(event)) {
 13                 return true;
 14             }
 15         }
 16 
 17         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 18                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 19             switch (event.getAction()) {
 20                 case MotionEvent.ACTION_UP:
 21                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
 22                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
 23                         // take focus if we don‘t have it already and we should in
 24                         // touch mode.
 25                         boolean focusTaken = false;
 26                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
 27                             focusTaken = requestFocus();
 28                         }
 29 
 30                         if (!mHasPerformedLongPress) {
 31                             // This is a tap, so remove the longpress check
 32                             removeLongPressCallback();
 33 
 34                             // Only perform take click actions if we were in the pressed state
 35                             if (!focusTaken) {
 36                                 // Use a Runnable and post this rather than calling
 37                                 // performClick directly. This lets other visual state
 38                                 // of the view update before click actions start.
 39                                 if (mPerformClick == null) {
 40                                     mPerformClick = new PerformClick();
 41                                 }
 42                                 if (!post(mPerformClick)) {
 43                                     performClick();
 44                                 }
 45                             }
 46                         }
 47 
 48                         if (mUnsetPressedState == null) {
 49                             mUnsetPressedState = new UnsetPressedState();
 50                         }
 51 
 52                         if (prepressed) {
 53                             mPrivateFlags |= PRESSED;
 54                             refreshDrawableState();
 55                             postDelayed(mUnsetPressedState,
 56                                     ViewConfiguration.getPressedStateDuration());
 57                         } else if (!post(mUnsetPressedState)) {
 58                             // If the post failed, unpress right now
 59                             mUnsetPressedState.run();
 60                         }
 61                         removeTapCallback();
 62                     }
 63                     break;
 64 
 65                 case MotionEvent.ACTION_DOWN:
 66                     if (mPendingCheckForTap == null) {
 67                         mPendingCheckForTap = new CheckForTap();
 68                     }
 69                     mPrivateFlags |= PREPRESSED;
 70                     mHasPerformedLongPress = false;
 71                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
 72                     break;
 73 
 74                 case MotionEvent.ACTION_CANCEL:
 75                     mPrivateFlags &= ~PRESSED;
 76                     refreshDrawableState();
 77                     removeTapCallback();
 78                     break;
 79 
 80                 case MotionEvent.ACTION_MOVE:
 81                     final int x = (int) event.getX();
 82                     final int y = (int) event.getY();
 83 
 84                     // Be lenient about moving outside of buttons
 85                     int slop = mTouchSlop;
 86                     if ((x < 0 - slop) || (x >= getWidth() + slop) ||
 87                             (y < 0 - slop) || (y >= getHeight() + slop)) {
 88                         // Outside button
 89                         removeTapCallback();
 90                         if ((mPrivateFlags & PRESSED) != 0) {
 91                             // Remove any future long press/tap checks
 92                             removeLongPressCallback();
 93 
 94                             // Need to switch from pressed to not pressed
 95                             mPrivateFlags &= ~PRESSED;
 96                             refreshDrawableState();
 97                         }
 98                     }
 99                     break;
100             }
101             return true;
102         }
103 
104         return false;
105     }

  先看第4行判断,如果这个view是disable的话,返回这个view是否是clickable的或者longClickable的,意思就是这个view如果可点击或者可长点击,onTouchEvent直接返回true,表示该view消费了此事件。

  再看第11行,如果这个view设置了代理,那么事件交给代理处理。这个功能很奇怪,大概就是点击一个地方,而另一个地方会有反馈信息,很不符合正常人的使用习惯,可能有一些特殊用途吧,不然用户还觉得屏幕坏了...

  接着第17行,如果view是可点击或者可长点击的话进入判断。还是分析down事件。

if (mPendingCheckForTap == null) {
       mPendingCheckForTap = new CheckForTap();
 }

先确保mPendingCheckForTap 有个实例对象,mPendingCheckForTap是什么呢?

private CheckForTap mPendingCheckForTap = null;

private
final class CheckForTap implements Runnable { public void run() { mPrivateFlags &= ~PREPRESSED; mPrivateFlags |= PRESSED; refreshDrawableState(); if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(ViewConfiguration.getTapTimeout()); } } }

从第69行,将记录状态值的mPrivateFlags设置为PREPRESSED(可以理解为即将点击的状态),将 mHasPerformedLongPress设置为false(可以理解为还没有执行长点击动作),再post一个延时任务(因为view最终继承的是handler,所以可以发送消息)。ViewConfiguration.getTapTimeout()返回的是一个常量TAB_TIMEOUT == 115,表示115ms后执行这个任务。如果执行了mPendingCheckForTap就是指执行了CheckForTap的 run 方法。先将mPrivateFlags设置为PRESSED,之后 refreshDrawableState 方法中进行了一些操作,在此不作分析。接着进入判断如果这个view是可长点击的,进入方法 postCheckForLongClick,参数依旧是TAB_TIMEOUT。

private void postCheckForLongClick(int delayOffset) {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.rememberWindowAttachCount();
        postDelayed(mPendingCheckForLongPress,
                ViewConfiguration.getLongPressTimeout() - delayOffset);
    }

在此方法中先确保 mPendingCheckForLongPress 有实例对象,再来看下CheckForLongPress,也是实现Runnable的类

class CheckForLongPress implements Runnable {

        private int mOriginalWindowAttachCount;

        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                    mHasPerformedLongPress = true;
                }
            }
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }
    }

 

 忽略这个方法 mPendingCheckForLongPress.rememberWindowAttachCount(); 之后又是post 一个延时的消息,延时LONG_PRESS_TIMEOUT - TAB_TIMEOUT,也就是500 - 115,确保你从按下到执行此事件用的时间是500ms。那么具体就要看 CheckForLongPress 的run 方法了。run 方法中的第一个判断我们也不做深究,所以就看performLongClick 方法。

public boolean performLongClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        if (mOnLongClickListener != null) {
            handled = mOnLongClickListener.onLongClick(View.this);
        }
        if (!handled) {
            handled = showContextMenu();
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }

依旧忽略一些。看第一个判断,如果设置了onLongClickListener,并且onLongClick 方法也返回true,那么handled 也就为true,此方法返回true。那么在CheckForLongPress中mHasPerformedLongPress也设置为true,表示已经执行了长点击事件。

  down事件结束,其中因为长按事件是一个延迟执行的事件,所以在之后的事件中还是有可能被取消的,我们继续往下看后面的事件。

  先看move。

  依旧是actiivty到viewGroup,从ViewGroup的dispatchTouchEvent开始看。

  如果在down事件中找到了target,并且传递过程中某个view 重写 onInterceptTouchEvent 方法使之返回值为true的话,直接进入代码中的86行的判断。将target的坐标转化为这个中断事件view的坐标,将事件改为cancel事件等操作之后,返回true,activity中也返回true,表示该view消费了move事件。

  如果在down事件中找到了target,并且没有被中断,直到120行执行target.dispatchTouchEvent ,还是之前那些判断,看有没重写dispatchTouchEvent方法,没的话看有没添加监听,没的话执行onTouchEvent方法。在View的onTouchEvent方法中,从代码81行开始。先获取起始坐标,获取系统预设的一个值mTouchSlop(一直跟过去发现这个值预设的是16px,但获取出来的时候是根据当前机器有个缩放的过程的  mTouchSlop = (int) (density * TOUCH_SLOP + 0.5f);)。之后判断是否移动超过控件,超过的话就取消down事件中的事件回调。如果mPrivateFlags中设置了标记为PRESSED,那么移除长点击事件的回调,将PRESSED标志位移除,刷新界面,相当于给用户一个从被点击到点击的反馈。

  如果在down事件中没找到target,那么进入第81行执行View的dispatchTouchEvent,之后过程基本同上,不再赘述。

  再看up。

  在viewGroup的dispatchTouchEvent中67行,将mGroupFlags的 FLAG_DISALLOW_INTERCEPT标志位取消,以便在下次事件中 disallowIntercept的值依然为false。之后的一些判断与move事件类似,不同点在View的onTouchEvent中。

  先看这个 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 这个值如果第一个延时消息(115ms的那个)没执行,那么为true,执行了的话为false。之后的判断能进去,忽略 关于focus的判断和操作。下面判断 mHasPerformedLongPress的值,如果长点击还未执行,那么易移除掉长点击事件的延时任务,之后执行点击事件。

if (mPerformClick == null) {
         mPerformClick = new PerformClick();
 }
 if (!post(mPerformClick)) {
          performClick();
}

private final class PerformClick implements Runnable {
        public void run() {
            performClick();
        }
}

public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }

      return false;
}

在performClick方法里,判断是否设置了OnClickListener。如果没设置直接返回false,设置了的话,先声音反馈,之后回调listener的onClick 方法,并且返回true。之后的内容就是将mPrivateFlags中PRESSED标记为职位1,然后 refreshDrawableState ,post 个延时消息设置pressed为false,最后移除第一个延时任务。

 private final class UnsetPressedState implements Runnable {
        public void run() {
            setPressed(false);
        }
    }  

  View的onTouchEvent中的cancel方法就几句话,取消PRESSED标志位,刷新界面给反馈,移除延时任务。

 mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();

总结:

  整个事件分发过程对本人来说还是非常的复杂,其中很多细节没有深究,稍稍的总结一些重点:

  down事件是唯一一个必须要发生的。

  事件传递的过程中ViewGroup可以重写OnInterceptTouchEvent 方法来拦截事件,自己处理而不交给子view处理。

  如果一个view是clickable或者longClickable(就算是disable)那么肯定会消费事件。

  触发longClick事件只需要down事件即可,而click事件却还需要up事件。

 

由于整个事件分发的流程复杂,本人才疏学浅加上时间不足,先大致总结这些。当然这其中不乏错误或者一些不严谨的地方,希望各位大牛指正。

Android事件分发机制的学习

标签:

原文地址:http://www.cnblogs.com/touge/p/4185692.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!