在 ViewRoot 中:
- 有这几个数据成员:
InputChannel mInputChannel; InputQueue.Callback mInputQueueCallback; InputQueue mInputQueue; private final InputHandler mInputHandler = new InputHandler() { public void handleKey(KeyEvent event, Runnable finishedCallback) { startInputEvent(finishedCallback); dispatchKey(event, true); } public void handleMotion(MotionEvent event, Runnable finishedCallback) { startInputEvent(finishedCallback); dispatchMotion(event, true); } };
- 这个 mInputHandler 是在 setView 中注册的:
/** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mWindowAttributes.copyFrom(attrs); attrs = mWindowAttributes; if (view instanceof RootViewSurfaceTaker) { mSurfaceHolderCallback = ((RootViewSurfaceTaker)view).willYouTakeTheSurface(); if (mSurfaceHolderCallback != null) { mSurfaceHolder = new TakenSurfaceHolder(); mSurfaceHolder.setFormat(PixelFormat.UNKNOWN); } } Resources resources = mView.getContext().getResources(); CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); if (mTranslator != null || !compatibilityInfo.supportsScreen()) { mSurface.setCompatibleDisplayMetrics(resources.getDisplayMetrics(), mTranslator); } boolean restore = false; if (mTranslator != null) { restore = true; attrs.backup(); mTranslator.translateWindowLayout(attrs); } if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs); if (!compatibilityInfo.supportsScreen()) { attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; } mSoftInputMode = attrs.softInputMode; mWindowAttributesChanged = true; mAttachInfo.mRootView = view; mAttachInfo.mScalingRequired = mTranslator != null; mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; if (panelParentView != null) { mAttachInfo.mPanelParentWindowToken = panelParentView.getApplicationWindowToken(); } mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); mInputChannel = new InputChannel(); try { res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; unscheduleTraversals(); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingVisibleInsets.set(0, 0, 0, 0); if (Config.LOGV) Log.v(TAG, "Added window " + mWindow); if (res < WindowManagerImpl.ADD_OKAY) { mView = null; mAttachInfo.mRootView = null; mAdded = false; unscheduleTraversals(); switch (res) { case WindowManagerImpl.ADD_BAD_APP_TOKEN: case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManagerImpl.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerImpl.ADD_NOT_APP_TOKEN: throw new WindowManagerImpl.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerImpl.ADD_APP_EXITING: throw new WindowManagerImpl.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerImpl.ADD_DUPLICATE_ADD: throw new WindowManagerImpl.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerImpl.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerImpl.ADD_MULTIPLE_SINGLETON: throw new WindowManagerImpl.BadTokenException( "Unable to add window " + mWindow + " -- another window of this type already exists"); case WindowManagerImpl.ADD_PERMISSION_DENIED: throw new WindowManagerImpl.BadTokenException( "Unable to add window " + mWindow + " -- permission denied for this window type"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } if (view instanceof RootViewSurfaceTaker) { mInputQueueCallback = ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); } if (mInputQueueCallback != null) { mInputQueue = new InputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); } else { InputQueue.registerInputChannel(mInputChannel, mInputHandler, //这个地方注意一下. Looper.myQueue()); } view.assignParent(this); mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; } } }
- 这里最主要的是 mInputHandler 中的 handleMotion 中调用到了 dispatchMotion 方法:
public void handleMotion(MotionEvent event, Runnable finishedCallback) { startInputEvent(finishedCallback); dispatchMotion(event, true); }
见 ViewRoot 中的 dispatchMotion 方法:
private void dispatchMotion(MotionEvent event, boolean sendDone) { int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { dispatchPointer(event, sendDone); //这个地方! } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { dispatchTrackball(event, sendDone); } else { // TODO Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event); if (sendDone) { finishInputEvent(); } } } private void dispatchPointer(MotionEvent event, boolean sendDone) { Message msg = obtainMessage(DISPATCH_POINTER); //发出这样的消息. msg.obj = event; msg.arg1 = sendDone ? 1 : 0; sendMessageAtTime(msg, event.getEventTime()); }
- 处理这个消息:
case DISPATCH_POINTER: {//触摸事件消息的处理 MotionEvent event = (MotionEvent) msg.obj; try { deliverPointerEvent(event);//走入到这里.----这个地方 } finally { event.recycle(); //处理完这个事件后, 把这个event回收掉. if (msg.arg1 != 0) { finishInputEvent();//向发出消息模块发一个回执, 以便进行下一次的消息派发. } if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); } } break; private void deliverPointerEvent(MotionEvent event) { if (mTranslator != null) { mTranslator.translateEventInScreenToAppWindow(event);//物理坐标向逻辑坐标的转换. } boolean handled; if (mView != null && mAdded) { // enter touch mode on the down boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; if (isDown) { ensureTouchMode(true);//进入触摸模式.----这个方法见下面 } if(Config.LOGV) { captureMotionLog("captureDispatchPointer", event); } if (mCurScrollY != 0) { event.offsetLocation(0, mCurScrollY); } if (MEASURE_LATENCY) { lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); } //进行事件的派发, 对view和activity系统产生影响. 见 DecorView 和 ViewGroup中的方法. handled = mView.dispatchTouchEvent(event); //--------------------------这句话是最重要的. if (MEASURE_LATENCY) { lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano()); } if (!handled && isDown) {//对于上面没有处理的事件, 进行屏幕边界偏移.屏幕偏移用(edge slop)进行表示. //它的作用是当用户正好触摸到屏幕边界时,系统自动对原始消息进行一定的偏移, //然后在新的偏移后的位置上寻找是否有匹配的视图, //为什么要有"屏幕偏移"呢? 因为对于触摸屏而言, 尤其是电容触摸屏, 人类手指尖有一定的大小, //当触摸到边界时, 力量会被自动吸附到屏幕边界, //所以, 此处根据上下左右不同的边界对象消息原始位置进行一定的偏移. int edgeSlop = mViewConfiguration.getScaledEdgeSlop(); final int edgeFlags = event.getEdgeFlags(); int direction = View.FOCUS_UP; int x = (int)event.getX(); int y = (int)event.getY(); final int[] deltas = new int[2]; if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) { direction = View.FOCUS_DOWN; if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { deltas[0] = edgeSlop; x += edgeSlop; } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { deltas[0] = -edgeSlop; x -= edgeSlop; } } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) { direction = View.FOCUS_UP; if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { deltas[0] = edgeSlop; x += edgeSlop; } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { deltas[0] = -edgeSlop; x -= edgeSlop; } } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { direction = View.FOCUS_RIGHT; } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { direction = View.FOCUS_LEFT; } if (edgeFlags != 0 && mView instanceof ViewGroup) { View nearest = FocusFinder.getInstance().findNearestTouchable( ((ViewGroup) mView), x, y, direction, deltas); if (nearest != null) { event.offsetLocation(deltas[0], deltas[1]); event.setEdgeFlags(0); mView.dispatchTouchEvent(event); } } } } } 其中 ensureTouchMode 如下所示: boolean ensureTouchMode(boolean inTouchMode) {//进否进入触摸模式.---即 非触摸模式 与 触摸模式 之间的切换. if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current " + "touch mode is " + mAttachInfo.mInTouchMode); //如果当前触摸 与 原来的触摸模式 相同, 则没有改变, 所以返回false. if (mAttachInfo.mInTouchMode == inTouchMode) return false; // tell the window manager----即通知window----因为 wms在布局窗口时, 会根据不同的touch模式进行不同的处理 try { //通知窗口, WmS在进行客户窗口布局时, 需要根据客户窗口的Touch模式进行不同的处理. sWindowSession.setInTouchMode(inTouchMode); } catch (RemoteException e) { throw new RuntimeException(e); } // handle the change ----view自身的改变.----如清除焦点, 或者requestFocus 之类的 可能涉及 界面更新的操作. return ensureTouchModeLocally(inTouchMode);//---点进去去看下. 这个方法 其实就在这下面. }
- 如果这个 mView是 DecorView 而言, 执行这个:
public boolean dispatchTouchEvent(MotionEvent ev) { //注意, activity实现了 Window.CallBack接口, 这里获得的cb, 就是这个activity. final Callback cb = getCallback(); return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super .dispatchTouchEvent(ev); }
- 如果 cb为空, 则直接执行 ViewGroup中的 dispatchTouchEvent方法.—-下面会讲到.
- 如果 cb不为空, 则执行 activity中的 dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { //如果是down事件的话, activity有机会在事件响应之前做点事情. onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { //调的是PhoneWindow中的superDispatchTouchEvent //--->DecorView中的superDispatchTouchEvent //--->ViewGroup中的dispatchTouchEvent方法. return true; } return onTouchEvent(ev); //如果view系统不处理, 则调用 activity中的 onTouchEvent. }
- 如果这个 mView直接就是 ViewGroup的话, 那直接调到 ViewGroup中的 dispatchTouchEvent.
- 反正先 处理 viewgroup的 dispatchTouchEvent, 如果没有消化掉, 才去处理 activity中的 onTouchEvent方法.
- 至于 ViewGroup中的 dispatchTouchEvent(event)方法:
/** * {@inheritDoc} */ @Override public boolean dispatchTouchEvent(MotionEvent ev) {//touch到时, 会从 ViewRoot那里调到 ViewGroup的这个方法. if (!onFilterTouchEventForSecurity(ev)) { return false; } final int action = ev.getAction(); //当前ViewGroup布局坐标系的坐标. 当前ViewGroup视图坐标原点在布局坐标系中的位置为(-mScrollX, -mScrollY) final float xf = ev.getX(); final float yf = ev.getY(); //坐标系 转换成 当前ViewGroup视图坐标系的坐标. 这个混算要整明白.----不要误以为是child什么的. //因为当前这个viewgroup可能会在scroll的, //所以要算上(mScrollX, mScrollY)来得到这个触摸点相对于当前这个Viewgroup视图坐标原点的坐标. final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//true表示不允许拦截 if (action == MotionEvent.ACTION_DOWN) { //先处理 action_down 情况 if (mMotionTarget != null) {//这个mMotionTarget是指这个viewgroup中的捕获事件的child. // this is weird, we got a pen down, but we thought it was // already down! // XXX: We should probably send an ACTION_UP to the current // target. //当action_down时, 通常情况下, 这个mMotionTarget当然应为null. 不为空则可能是出错的. mMotionTarget = null; } // If we‘re disallowing intercept or if we‘re allowing and we didn‘t // intercept if (disallowIntercept || !onInterceptTouchEvent(ev)) {//不允许拦截 或者 没有拦截 // reset this event‘s action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN);//重置 // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. final int scrolledXInt = (int) scrolledXFloat;//视图坐标 final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren;//要对孩子进行遍历, 这个孩子可能是相邻, 也可能是相互前后叠加. final int count = mChildrenCount; //被触摸的点处, 可能会叠加多个孩子. //让序号最后面的child先拿事件试试, 如果不要的话, 再让序号前面的孩子拿事件. for (int i = count - 1; i >= 0; i--) {//遍历孩子, 确定孩子要不要这个down事件. final View child = children[i]; //只有当child是可见或者动画时, 才可以响应这个down. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame);//获得该child的布局区域----父view视图坐标系中的. if (frame.contains(scrolledXInt, scrolledYInt)) {//判断点击的位置是否在这个child上. // offset the event to the view‘s coordinate system //坐标系切到孩子的布局坐标系统上. 这个要理解好. final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; //点击事件在child上, 则现在坐标转换到以child的原点为基准,---但非child显示区域坐标啊. ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) {//交给child来分发了. // Event handled, we have a target now. //如果孩子消费了这个down事件, 则这个mMotionTarget就记录这个孩子, 然后返回. mMotionTarget = child; return true; } // The event didn‘t get handled, try the next view. // Don‘t reset the event‘s location, it‘s not // necessary here. } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||//指是不是up或cancel事件, true表示是. (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { //如果现在的事件是up或者cancel掉了, 那么应当允许拦截. 因为在按下还没有释放时, 要拦截消息的. // Note, we‘ve already copied the previous state to our local // variable, so this takes effect on the next event //现在允许拦截.----因为 这一系列的(down/move/up/cacel)事件 已经结束了! //----所以没有是否允许拦截的意义了. //----即 设一个不允许拦截, 其有效期仅这么一套down/move/up/cancel周期而已. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // The event wasn‘t an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; if (target == null) { //如果child没有消耗这个down事件的话, 说明move和up也不会响应. 所以, 应由这个viewgroup自己响应. // We don‘t have a target, this means we‘re handling the // event as a regular view. ev.setLocation(xf, yf);//坐标移回viewgroup自己的坐标体系, 即布局坐标. if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {//这个CANCEL_NEXT_UP_EVENT通常是不存在的 ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev);//调用viewgroup的view部分的dispatchTouchEvent方法. } //如果有孩子响应了down事件, 那么往下走.----上面刚处理的是 没有孩子响应的事情, 现在处理有孩子响应的情况. // if have a target, see if we‘re allowed to and want to intercept its // events if (!disallowIntercept && onInterceptTouchEvent(ev)) { //如果允许viewgroup截获, 并且确实被viewgroup截获了, //那么child应当放弃down,move,up事件, 所以下面用cancel来取消child. //这个target是指捕获down事件的child //即, 将获得点击位置---即以child的布局坐标系统来算的. final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL);//事件改为action_cancel事件. ev.setLocation(xc, yc);//坐标换到child的布局坐标来. if (!target.dispatchTouchEvent(ev)) { //用于让child处理cancel事件 //----因为原来的事件被父viewgroup给拦截了,所以用cancel来逐个逐级通知child处理cancel. // target didn‘t handle ACTION_CANCEL. not much we can do //这个cancel事件可以通知child去取消之前对事件的追踪, 如长按, 特定手势之类. // but they should have. } // clear the target mMotionTarget = null;//把其置为null // Don‘t dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true;//返回true, 表示事件被消耗了.----这里是被viewgroup消耗了, 而不是child. } // if (isUpOrCancel) {//true表示当前事件是 up或cancel事件, //表示 事件处理 处于 尾声了. //mMotionTarget置回空, 不过target仍在, 以便下面调用 target.dispatchTouchEvent. //对于 move事件, 因为事件 后面还会有, 所以 mMotionTarget不能为空的. mMotionTarget = null; } //下面这些, 都是指 由child来响应 move, up事件! // finally offset the event to the target‘s coordinate system and // dispatch the event. final float xc = scrolledXFloat - (float) target.mLeft;//转到child的布局坐标方式. final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc);//更改坐标系统为child的布局坐标方式. if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { //通常不走进来.这个CANCEL_NEXT_UP_EVENT表示 取消 随后的up事件. ev.setAction(MotionEvent.ACTION_CANCEL); //走进来的话, 表示要取消随后的up事件, 所以事件改为cancel事件. target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; //然后 也把 这个置为null } return target.dispatchTouchEvent(ev);//由child来处理这个move和up事件.----以及可能的cancel事件. }
- View 中的 dispatchTouchEvent 方法:
//这里注意的是: 这里先处理 外界设置的 OnTouchEventListener // 如果返回 true, 说明外界要 抢占 这个事件, 所以不执行 控件自身的 onTouchEvent. // 如果返回 false, 说明外界 认为可以 把这个事件 分发给 控件自身的 onTouchEvent处理. //主要是这点: //(1) 提供了一个接口给外界设置, 即通过 setOnTouchEventListener 设置一个监听器. //(2) 自身处理的方法: onTouchEvent ----在自定义一个view时写的. //优先执行 外界的要求(即监听器中的方法), 如果返回 false, 才去执行 控件自身的onTouchEvent. public boolean dispatchTouchEvent(MotionEvent event) { if (!onFilterTouchEventForSecurity(event)) { //处理当窗口处于模糊状态下的事件.---返回true表示, 事件应当处理; 为false时, 表示事件不处理. return false; } if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { //如果 ENABLE, 并且该view注册了OnTouchListener监听器, 则执行这个监听器的onTouch, 处理完直接返回true return true; } return onTouchEvent(event); //如果没有设置监听器, 则执行 onTouchEvent方法. }
- View 中的 onTouchEvent 方法:
这个呢, 在其它的笔记中已做了说明, 这里就不列出来了.
时间: 2024-10-09 23:22:30