标签:
在 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不为空, 则执行 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 方法:
这个呢, 在其它的笔记中已做了说明, 这里就不列出来了.
从 ViewRoot 来分析 TouchEvent 触摸事件
标签:
原文地址:http://blog.csdn.net/hamiton/article/details/51351070