Android 事件传递
来源:互联网 发布:淘宝美工怎么上架图片 编辑:程序博客网 时间:2024/05/01 18:29
Android 事件传递
看了很多的博客都没有涉及到源码的层面来讲解事件传递,看完都感觉有点理解又有点疑惑,所以自己整理一篇。
我是从Activity的dispatchTouchEvent方法入手的,至于Activity的dispatchTouchEvent方法是怎么被调用的,具体是在WindowManagerService中,有兴趣的可以自己去了解。
代码流程:
一般我们点击屏幕之后,WindowManagerService会获取到Touch事件,并将该Touch事件派发给Activity,也就是会调用Activity的dispatchTouchEvent方法。具体的可以看代码。
代码都是从Android 5.1.1中截出来的
Activity的dispatchTouchEvent方法:
/*** 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是一个空函数,什么都没做 onUserInteraction(); } //getWindow获取到的是PhoneWindow类 if (getWindow().superDispatchTouchEvent(ev)) { return true; } //如果superDispatchTouchEvent返回false,则调用Activity的onTouchEvent方法。 return onTouchEvent(ev);}
从getWindow().superDispatchTouchEvent(ev)这里可以看到,Activity又将事件派发给PhoneWindow来处理。
如果superDispatchTouchEvent返回false,则会调用Activity的OnTouchEvent方法。
我们接着看一下PhoneWindow的代码。
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
这个方法很简单,又将事件传给mDecor来处理,mDecor是一个DecorView对象,DecorView又继承了FrameLayout。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { public boolean superDispatchTouchEvent(MotionEvent event) { //这里又调用了父类的dispatchTouchEvent, //也就是FrameLayout的dispatchTouchEvent方法。 return super.dispatchTouchEvent(event); }}
FrameLayout没有重写dispatchTouchEvent方法,因此调用的是ViewGroup的dispatchTouchEvent方法。该方法代码比较多,慢慢分析。需要注意TouchTarget,TouchTarget主要是用来记录事件接收情况,包括接收该事件的子view、事件详情等。dispatchTouchEvent就是根据TouchTarget来决定派发事件到哪些子View中。
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { ...... //最后会返回这个handled boolean handled = false; //应用安全策略来过滤一部分事件,比如View被遮挡了就会被过滤 if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // 处理初始的Down事件,只要收到Down事件,就认为是一个新的Touch事件 if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } //检查是否拦截,默认false //mFirstTouchTarget 是TouchTarget链表的头节点。 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //如果允许拦截 if (!disallowIntercept) { //这里调用了onInterceptTouchEvent方法, //该方法默认返回false,即不进行拦截 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted = false; } } else { intercepted = true; } // 检查cancel事件. 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) {//判断是否取消或者被拦截 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; //只有DOWN事件(包括多点触控)才会走这个流程, //这个流程主要是找到能接受该MotionEvent的TouchTarget //后续的其他事件则直接跳过该流程 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; //Removes the pointer ids from consideration. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //由前往后遍历所有的子View, //寻找一个能接受该事件的子view,并生成对应的TouchTarget final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(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; } //判断该view是否能接受事件且事件坐标不超过view范围 if(!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } //获取child对应的TouchTarget, //如果child还没有对应的TouchTarget,则返回null newTouchTarget = getTouchTarget(child); // 若子View已经在TouchTarget链表中 if (newTouchTarget != null) { // 将事件id加入到touchTarget中 newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //dispatchTransformedTouchEvent这个方法会把事件传递给子View, //方法内部会调用子View的dispatchTouchEvent方法。 //这个方法后面会讲到。 //并返回子View的dispatchTouchEvent的返回值。 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //为true代表子View接收了该事件 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(); //重新生成一个TouchTarget,包含了子view newTouchTarget = addTouchTarget(child, idBitsToAssign); //标记为已经将事件分发,避免后面重新分发Down事件。 alreadyDispatchedToNewTouchTarget = true; //已经找到TouchTarget了,结束遍历 break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } // 没有发现接收事件的子View if (newTouchTarget == null && mFirstTouchTarget != null) { // 把事件赋给最早的TouchTarget newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } //将事件派发给TouchTarget if (mFirstTouchTarget == null) { //没有TouchTarget,所以派发给自己 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 派发事件给TouchTarget TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; //遍历TouchTarget链表 while (target != null) { final TouchTarget next = target.next; //已经分发过 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //检查是否取消或者拦截 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //派发事件 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } //如果是UP或者Cancel事件,则清空TouchTarget链表 if (canceled || actionMasked==MotionEvent.ACTION_UP || actionMasked==MotionEvent.ACTION_HOVER_MOVE){ resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled;}
从上面的代码可以看到,对事件的派发工作都是在dispatchTransformedTouchEvent方法中。
dispatchTransformedTouchEvent会将事件的坐标转换为特定子view中的坐标,并调用子view的dispatchTouchEvent方法,最后返回dispatchTouchEvent的返回值。
/** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { //最后会返回该值 final boolean handled; final int oldAction = event.getAction(); //取消事件不需要转换坐标或者过滤,因为取消事件不需要对坐标进行处理。 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); //派发cancel事件 if (child == null) { //注意是调用super的dispatchTouchEvent方法 //即View的dispatchTouchEvent方法 handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; if (newPointerIdBits == 0) { return false; } final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { //注意是调用super的dispatchTouchEvent方法 handled = super.dispatchTouchEvent(event); } else { //坐标转换 final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); //分发给子View handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } 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()); } //派发给子view handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled;}
到这里就把事件从Activity分发到View上中。
接下来看看View的dispatchTouchEvent方法。
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */public boolean dispatchTouchEvent(MotionEvent event) { //最后会返回这个值 boolean result = false; ...... final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } //应用安全策略来过滤一部分事件,比如View被遮挡了就会被过滤 if (onFilterTouchEventForSecurity(event)) { //ListenerInfo类含有View的很多监听事件,包括OnTouchListener、OnClickListener等。 ListenerInfo li = mListenerInfo; //这里执行li.mOnTouchListener.onTouch方法,也就是执行我们通过setOnTouchListener的监听事件。 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //这里要注意,如果我们设置的TouchListener返回false,则还会执行View自身的OnTouchEvent事件。 if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { //停止滚动 stopNestedScroll(); } return result;}
TouchTarget
TouchTarget的定义和相关方法
/* Describes a touched view and the ids of the pointers that it has captured. * * This code assumes that pointer ids are always in the range 0..31 such that * it can use a bitfield to track which pointer ids are present. * As it happens, the lower layers of the input dispatch pipeline also use the * same trick so the assumption should be safe here... */private static final class TouchTarget { private static final int MAX_RECYCLED = 32; private static final Object sRecycleLock = new Object[0]; private static TouchTarget sRecycleBin; private static int sRecycledCount; public static final int ALL_POINTER_IDS = -1; // all ones // The touched child view. public View child; // The combined bit mask of pointer ids for all pointers captured by the target. public int pointerIdBits; // The next target in the target list. public TouchTarget next; private TouchTarget() { } public static TouchTarget obtain(View child, int pointerIdBits) { final TouchTarget target; synchronized (sRecycleLock) { if (sRecycleBin == null) { target = new TouchTarget(); } else { target = sRecycleBin; sRecycleBin = target.next; sRecycledCount--; target.next = null; } } target.child = child; target.pointerIdBits = pointerIdBits; return target; } public void recycle() { synchronized (sRecycleLock) { if (sRecycledCount < MAX_RECYCLED) { next = sRecycleBin; sRecycleBin = this; sRecycledCount += 1; } else { next = null; } child = null; } }}
/** * Resets all touch state in preparation for a new cycle. */private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE;}/** * Resets the cancel next up flag. Returns true if the flag was previously set. */private static boolean resetCancelNextUpFlag(View view) { if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) { view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; return true; } return false;}/** * Clears all touch targets. */private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; }}/** * Cancels and clears all touch targets. */private void cancelAndClearTouchTargets(MotionEvent event) { if (mFirstTouchTarget != null) { boolean syntheticEvent = false; if (event == null) { final long now = SystemClock.uptimeMillis(); event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); syntheticEvent = true; } for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { resetCancelNextUpFlag(target.child); dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); } clearTouchTargets(); if (syntheticEvent) { event.recycle(); } }}/** * Gets the touch target for specified child view. Returns null if not found. */private TouchTarget getTouchTarget(View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { if (target.child == child) { return target; } } return null;}/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target;}/** * Removes the pointer ids from consideration. */private void removePointersFromTouchTargets(int pointerIdBits) { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if ((target.pointerIdBits & pointerIdBits) != 0) { target.pointerIdBits &= ~pointerIdBits; if (target.pointerIdBits == 0) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; }}private void cancelTouchTarget(View view) { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (target.child == view) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); final long now = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); view.dispatchTouchEvent(event); event.recycle(); return; } predecessor = target; target = next; }}
总结:
Activity的传递
- WindowManagerService将事件传给Activity。
- Activity调用dispatchTouchEvent()方法分发事件。
- 如果是Down事件,会调用onUserInteraction方法。
- 通过PhoneWindow把事件分发给Activity最底层的ViewGroup。
- 如果最底层的ViewGroup返回false,则调用Activity的onTouchEvent。
ViewGroup的分发过程
- Activity将事件传给最底层的ViewGroup,调用ViewGroup的dispatchTouchEvent()方法。
- 如果接受到Down事件,则代表是一个新的Touch事件,会进行初始化,并清除之前的所有状态。
- ViewGroup调用onInterceptTouchEvent判断是否拦截事件分发。
- 如果没有被拦截,且为Down事件,则遍历所有的子View。
- 如果子view不能接受事件,或者事件的坐标不在子View的范围内,则跳过。
- 如果子View已经有对应TouchTarget对象,则由那个TouchTarget分发事件。
- 调用dispatchTransformedTouchEvent将事件传给子View。
- 返回true,表示该View接受了该事件,会生成对应的TouchTarget,并加入到TouchTarget链表中。
- 返回false,表示该View不接受该事件。
- 根据TouchTarget链表分发事件。
- TouchTarget链表为空,则调用super.dispatchTouchEvent(),将事件分发给自己。
- 否则遍历TouchTarget链表,并分别调用dispatchTransformedTouchEvent进行分发事件。
- 如果child的参数是空的,则代表是调用给自身的dispatchTouchEvent()。
- 如果child的参数不为空,则调用child的dispatchTouchEvent()。
View
- ViewGroup通过调用View的dispatchTouchEvent方法将事件分发给View。
- 如果View设置了OnTouchListener,则调用OnTouchListener的onTouch方法。
- onTouch返回true,则dispatchTouchEvent返回true。
- View没有设置OnTouchListener事件或者onTouch返回false,则调用View的onTouchEvent方法,如果onTouchEvent方法返回true,dispatchTouchEvent也会返回true。
- dispatchTouchEvent返回true,则表示该事件已经被接受。
- android 事件传递机制
- android 事件传递机制
- android触摸事件传递
- android事件传递机制
- android 事件传递
- Android触摸事件传递
- Android事件传递机制
- Android 事件传递机制
- android 事件的传递
- Android事件传递机制
- Android中的事件传递
- Android事件传递机制
- Android事件传递机制
- Android事件传递机制
- Android事件传递机制
- Android事件传递机制
- Android事件传递机制
- android touch事件传递
- javascript中面向对象的三大作用
- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 【LeetCode】101. Symmetric Tree 中序遍历,分支遍历,二叉树
- C#——关于属性字段中的set和get
- 给Qt程序加一个window桌面图标
- Android 事件传递
- Python GUI编程各种实现的对比
- 驾驭JAVA WEB开发环境
- android TextView设置自定义字体
- python中多进程+协程的使用以及为什么要用它
- tomcat运行web项目,不能访问动态地址,也就是controller
- Git 的使用
- document.documentElement和document.body的区别
- APK Multi-Tool(反编译工具)教程