android系统现在被移植到了各式各样的设备上,但它毕竟是为手机设计的操作系统,一些功能不是很完善。比如一些launcher模仿苹果做的dock,在使用鼠标的设备上,当鼠标放到dock里的图标上时,没有任何反应,只能用键盘的方向键激活它的动画。这篇文章就是尝试解决这个问题的笔记。
首先要明确一下android对鼠标是怎样支持的。据说原生的android是不支持鼠标的,后来在hacker们的努力下诞生了x86版本的系统并支持鼠标。
这里只大概描述一下framework层对鼠标的支持。第一次接收到鼠标事件后,WindowManagerService开始绘制鼠标光标,并处理鼠标事件。
RawInputEvent.CLASS_MOUSE为鼠标事件,属于Pointer事件,使用dispatchPointer函数分发事件。左键按下映射为ACTION_DOWN,左键释放映射为ACTION_UP,滑动映射为ACTION_MOVE。
参考:http://hi.baidu.com/haijiaoshu/blog/item/b65591084da31f24e824884e.html
捕获事件并传递的代码:
WindowManagerService.java:
case RawInputEvent.CLASS_MOUSE:
MotionEvent mmev = (MotionEvent)ev.event;
int mcx = (int)mmev.getX();
int mcy = (int)mmev.getY();
if (mMlx != mcx || mMly != mcy) {
mMlx = mcx;
mMly = mcy;
if (!mMouseDisplayed)
mMouseDisplayed = true;
requestAnimationLocked(0);
}
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
break;
对于ACTION_DOWN,WindowManagerService会为它查找target window,同时把mKeyWaiter.mMotionTarget设置为找到的窗口。
WindowManagerService.java:dispatchPointer函数:
Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, ev, true, false, pid, uid);
mKeyWaiter.waitForNextEventTarget函数:
Object target = findTargetWindow(nextKey, qev, nextMotion, isPointerEvent, callingPid, callingUid);
对于ACTION_UP,会直接使用mKeyWaiter.mMotionTarget的值,并把mKeyWaiter.mMotionTarget重新设置为NULL——因为ACTION_UP之前肯定有ACTION_DOWN,并且在UP事件后就应该释放对象处理其他事件了。
WindowManagerService.java:dispatchPointer函数:
if (action == MotionEvent.ACTION_UP) {
// let go of our target
mKeyWaiter.mMotionTarget = null;
mPowerManager.logPointerUpEvent();
} else if (action == MotionEvent.ACTION_DOWN) {
mPowerManager.logPointerDownEvent();
}
对于ACTION_MOVE,如果mKeyWaiter.mMotionTarget不为NULL则传递事件,否则直接丢弃事件。因此平时鼠标的滑动不应该产生任何效果,但按下左键后滑动鼠标意味着要拖动对象。
最后,向窗口传递事件。
WindowManagerService.java:dispatchPointer函数:
target.mClient.dispatchPointer(ev, eventTime, true);
事件由窗口里的ViewRoot承接,并传递给窗口中的View部件。ViewRoot是一个处理消息的handler,具体的请参考其他android资料。
ViewRoot.java:handleMessage函数:
handled = mView.dispatchTouchEvent(event);
第一个接收到消息的是ViewGroup——在android里,各个View部件是附着在ViewGroup里的,ViewGroup互相嵌套成为View层次树。ViewGroup根据事件类型和坐标,传递到子部件上,直到被处理。具体可参看ViewGroup.java的代码。
参考:http://blog.youkuaiyun.com/ddna/article/details/5473293
最后是View部件处理按键事件。如果注册了回调函数则回调,否则就用默认的方法处理。
View.java:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
了解以上过程后,就知道怎么做了。首先要修改的是WindowManagerService.java的dispatchPointer方法,为ACTION_MOVE寻找target window——可以把处理ACTION_DOWN的部分代码拷过来,注意不要去设置mKeyWaiter.mMotionTarget的值。为了避免影响其他事件的传递,我把事件码改成了12,并把代码放到return前执行。
然后修改ViewGroup.java,为这个事件寻找View子部件并传递事件。这样事件便可以传递到app上了。
最后修改app,检测到这个事件就requestFocus——重写OnTouch函数,不过为了不影响其他事件的处理,需要先调用父类的OnTouch函数。
首先要明确一下android对鼠标是怎样支持的。据说原生的android是不支持鼠标的,后来在hacker们的努力下诞生了x86版本的系统并支持鼠标。
这里只大概描述一下framework层对鼠标的支持。第一次接收到鼠标事件后,WindowManagerService开始绘制鼠标光标,并处理鼠标事件。
RawInputEvent.CLASS_MOUSE为鼠标事件,属于Pointer事件,使用dispatchPointer函数分发事件。左键按下映射为ACTION_DOWN,左键释放映射为ACTION_UP,滑动映射为ACTION_MOVE。
参考:http://hi.baidu.com/haijiaoshu/blog/item/b65591084da31f24e824884e.html
捕获事件并传递的代码:
WindowManagerService.java:
case RawInputEvent.CLASS_MOUSE:
MotionEvent mmev = (MotionEvent)ev.event;
int mcx = (int)mmev.getX();
int mcy = (int)mmev.getY();
if (mMlx != mcx || mMly != mcy) {
mMlx = mcx;
mMly = mcy;
if (!mMouseDisplayed)
mMouseDisplayed = true;
requestAnimationLocked(0);
}
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
break;
对于ACTION_DOWN,WindowManagerService会为它查找target window,同时把mKeyWaiter.mMotionTarget设置为找到的窗口。
WindowManagerService.java:dispatchPointer函数:
Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, ev, true, false, pid, uid);
mKeyWaiter.waitForNextEventTarget函数:
Object target = findTargetWindow(nextKey, qev, nextMotion, isPointerEvent, callingPid, callingUid);
对于ACTION_UP,会直接使用mKeyWaiter.mMotionTarget的值,并把mKeyWaiter.mMotionTarget重新设置为NULL——因为ACTION_UP之前肯定有ACTION_DOWN,并且在UP事件后就应该释放对象处理其他事件了。
WindowManagerService.java:dispatchPointer函数:
if (action == MotionEvent.ACTION_UP) {
// let go of our target
mKeyWaiter.mMotionTarget = null;
mPowerManager.logPointerUpEvent();
} else if (action == MotionEvent.ACTION_DOWN) {
mPowerManager.logPointerDownEvent();
}
对于ACTION_MOVE,如果mKeyWaiter.mMotionTarget不为NULL则传递事件,否则直接丢弃事件。因此平时鼠标的滑动不应该产生任何效果,但按下左键后滑动鼠标意味着要拖动对象。
最后,向窗口传递事件。
WindowManagerService.java:dispatchPointer函数:
target.mClient.dispatchPointer(ev, eventTime, true);
事件由窗口里的ViewRoot承接,并传递给窗口中的View部件。ViewRoot是一个处理消息的handler,具体的请参考其他android资料。
ViewRoot.java:handleMessage函数:
handled = mView.dispatchTouchEvent(event);
第一个接收到消息的是ViewGroup——在android里,各个View部件是附着在ViewGroup里的,ViewGroup互相嵌套成为View层次树。ViewGroup根据事件类型和坐标,传递到子部件上,直到被处理。具体可参看ViewGroup.java的代码。
参考:http://blog.youkuaiyun.com/ddna/article/details/5473293
最后是View部件处理按键事件。如果注册了回调函数则回调,否则就用默认的方法处理。
View.java:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
了解以上过程后,就知道怎么做了。首先要修改的是WindowManagerService.java的dispatchPointer方法,为ACTION_MOVE寻找target window——可以把处理ACTION_DOWN的部分代码拷过来,注意不要去设置mKeyWaiter.mMotionTarget的值。为了避免影响其他事件的传递,我把事件码改成了12,并把代码放到return前执行。
然后修改ViewGroup.java,为这个事件寻找View子部件并传递事件。这样事件便可以传递到app上了。
最后修改app,检测到这个事件就requestFocus——重写OnTouch函数,不过为了不影响其他事件的处理,需要先调用父类的OnTouch函数。
详情可参看附件的代码。
WindowManagerService.java
private int dispatchPointer(QueuedEvent qev, MotionEvent ev, int pid, int uid) {
if (DEBUG_INPUT || WindowManagerPolicy.WATCH_POINTER) Slog.v(TAG,
"dispatchPointer " + ev);
if (MEASURE_LATENCY) {
lt.sample("3 Wait for last dispatch ", System.nanoTime() - qev.whenNano);
}
Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev,
ev, true, false, pid, uid);
if (MEASURE_LATENCY) {
lt.sample("3 Last dispatch finished ", System.nanoTime() - qev.whenNano);
}
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP) {
// let go of our target
mKeyWaiter.mMotionTarget = null;
mPowerManager.logPointerUpEvent();
} else if (action == MotionEvent.ACTION_DOWN) {
mPowerManager.logPointerDownEvent();
}
if (targetObj == null) {
// In this case we are either dropping the event, or have received
// a move or up without a down. It is common to receive move
// events in such a way, since this means the user is moving the
// pointer without actually pressing down. All other cases should
// be atypical, so let's log them.
if (action != MotionEvent.ACTION_MOVE) {
Slog.w(TAG, "No window to dispatch pointer action " + ev.getAction());
}
if (action == MotionEvent.ACTION_MOVE) {
long nextEventTime = mLastTouchEventTime + mMinWaitTimeBetweenTouchEvents;
long now = SystemClock.uptimeMillis();
if (now < nextEventTime) {
if (qev != null) {
mQueue.recycleEvent(qev);
}
ev.recycle();
return INJECT_FAILED;
} else {
mLastTouchEventTime = now;
}
}
synchronized (mWindowMap) {
dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), true);
}
///////////////////////////////bingbowan
action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final long eventTime = ev.getEventTime();
final int x = (int)xf;
final int y = (int)yf;
final ArrayList windows = mWindows;
final int N = windows.size();
WindowState topErrWindow = null;
final Rect tmpRect = mTempRect;
for (int i=N-1; i>=0; i--) {
WindowState child = (WindowState)windows.get(i);
//Slog.i(TAG, "Checking dispatch to: " + child);
final int flags = child.mAttrs.flags;
if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) {
if (topErrWindow == null) {
topErrWindow = child;
}
}
if (!child.isVisibleLw()) {
//Slog.i(TAG, "Not visible!");
continue;
}
tmpRect.set(child.mFrame);
if (child.mTouchableInsets == ViewTreeObserver
.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
// The touch is inside of the window if it is
// inside the frame, AND the content part of that
// frame that was given by the application.
tmpRect.left += child.mGivenContentInsets.left;
tmpRect.top += child.mGivenContentInsets.top;
tmpRect.right -= child.mGivenContentInsets.right;
tmpRect.bottom -= child.mGivenContentInsets.bottom;
} else if (child.mTouchableInsets == ViewTreeObserver
.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
// The touch is inside of the window if it is
// inside the frame, AND the visible part of that
// frame that was given by the application.
tmpRect.left += child.mGivenVisibleInsets.left;
tmpRect.top += child.mGivenVisibleInsets.top;
tmpRect.right -= child.mGivenVisibleInsets.right;
tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
}
final int touchFlags = flags &
(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
if (tmpRect.contains(x, y) || touchFlags == 0) {
//Slog.i(TAG, "Using this target!");
final boolean screenOff = qev != null && (qev.flags&WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0;
if (!screenOff || (flags & WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) {
targetObj = child;
}
break;
}
if ((flags & WindowManager.LayoutParams
.FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
child.mNextOutsideTouch = mKeyWaiter.mOutsideTouchTargets;
targetObj = child;
//Slog.i(TAG, "Adding to outside target list: " + child);
}
}
// if there's an error window but it's not accepting
// focus (typically because it is not yet visible) just
// wait for it -- any other focused window may in fact
// be in ANR state.
if (topErrWindow != null && targetObj != topErrWindow) {
targetObj = null;
}
if (action == MotionEvent.ACTION_MOVE) {
long nextEventTime = mLastTouchEventTime + mMinWaitTimeBetweenTouchEvents;
long now = SystemClock.uptimeMillis();
if (now < nextEventTime) {
try {
Thread.sleep(nextEventTime - now);
} catch (InterruptedException e) {
}
mLastTouchEventTime = nextEventTime;
} else {
mLastTouchEventTime = now;
}
}
ev.setAction(12);
try {
if (DEBUG_INPUT || DEBUG_FOCUS || WindowManagerPolicy.WATCH_POINTER) {
Slog.v(TAG, "Delivering pointer " + qev + " Ev " + ev + " to " + (WindowState)targetObj);
}
((WindowState)targetObj).mClient.dispatchPointer(ev, eventTime, true);
} catch (android.os.RemoteException e) {
Slog.i(TAG, "WINDOW DIED during motion dispatch: " + (WindowState)targetObj);
mKeyWaiter.mMotionTarget = null;
try {
removeWindow(((WindowState)targetObj).mSession, ((WindowState)targetObj).mClient);
} catch (java.util.NoSuchElementException ex) {
// This will happen if the window has already been
// removed.
}
}
if (qev != null) {
mQueue.recycleEvent(qev);
}
ev.recycle();
//////////////////bingbowan
return INJECT_FAILED;
}
if (targetObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) {
synchronized (mWindowMap) {
dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), true);
}
if (qev != null) {
mQueue.recycleEvent(qev);
}
ev.recycle();
return INJECT_SUCCEEDED;
}
WindowState target = (WindowState)targetObj;
final long eventTime = ev.getEventTime();
final long eventTimeNano = ev.getEventTimeNano();
//Slog.i(TAG, "Sending " + ev + " to " + target);
if (uid != 0 && uid != target.mSession.mUid) {
if (mContext.checkPermission(
android.Manifest.permission.INJECT_EVENTS, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission denied: injecting pointer event from pid "
+ pid + " uid " + uid + " to window " + target
+ " owned by uid " + target.mSession.mUid);
if (qev != null) {
mQueue.recycleEvent(qev);
}
ev.recycle();
return INJECT_NO_PERMISSION;
}
}
if (MEASURE_LATENCY) {
lt.sample("4 in dispatchPointer ", System.nanoTime() - eventTimeNano);
}
if ((target.mAttrs.flags &
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) {
//target wants to ignore fat touch events
boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(ev);
//explicit flag to return without processing event further
boolean returnFlag = false;
if((action == MotionEvent.ACTION_DOWN)) {
mFatTouch = false;
if(cheekPress) {
mFatTouch = true;
returnFlag = true;
}
} else {
if(action == MotionEvent.ACTION_UP) {
if(mFatTouch) {
//earlier even was invalid doesnt matter if current up is cheekpress or not
mFatTouch = false;
returnFlag = true;
} else if(cheekPress) {
//cancel the earlier event
ev.setAction(MotionEvent.ACTION_CANCEL);
action = MotionEvent.ACTION_CANCEL;
}
} else if(action == MotionEvent.ACTION_MOVE) {
if(mFatTouch) {
//two cases here
//an invalid down followed by 0 or moves(valid or invalid)
//a valid down, invalid move, more moves. want to ignore till up
returnFlag = true;
} else if(cheekPress) {
//valid down followed by invalid moves
//an invalid move have to cancel earlier action
ev.setAction(MotionEvent.ACTION_CANCEL);
action = MotionEvent.ACTION_CANCEL;
if (DEBUG_INPUT) Slog.v(TAG, "Sending cancel for invalid ACTION_MOVE");
//note that the subsequent invalid moves will not get here
mFatTouch = true;
}
}
} //else if action
if(returnFlag) {
//recycle que, ev
if (qev != null) {
mQueue.recycleEvent(qev);
}
ev.recycle();
return INJECT_FAILED;
}
} //end if target
// Enable this for testing the "right" value
if (false && action == MotionEvent.ACTION_DOWN) {
int max_events_per_sec = 35;
try {
max_events_per_sec = Integer.parseInt(SystemProperties
.get("windowsmgr.max_events_per_sec"));
if (max_events_per_sec < 1) {
max_events_per_sec = 35;
}
} catch (NumberFormatException e) {
}
mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec;
}
/*
* Throttle events to minimize CPU usage when there's a flood of events
* e.g. constant contact with the screen
*/
if (action == MotionEvent.ACTION_MOVE) {
long nextEventTime = mLastTouchEventTime + mMinWaitTimeBetweenTouchEvents;
long now = SystemClock.uptimeMillis();
if (now < nextEventTime) {
try {
Thread.sleep(nextEventTime - now);
} catch (InterruptedException e) {
}
mLastTouchEventTime = nextEventTime;
} else {
mLastTouchEventTime = now;
}
}
if (MEASURE_LATENCY) {
lt.sample("5 in dispatchPointer ", System.nanoTime() - eventTimeNano);
}
synchronized(mWindowMap) {
if (!target.isVisibleLw()) {
// During this motion dispatch, the target window has become
// invisible.
dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), false);
if (qev != null) {
mQueue.recycleEvent(qev);
}
ev.recycle();
return INJECT_SUCCEEDED;
}
if (qev != null && action == MotionEvent.ACTION_MOVE) {
mKeyWaiter.bindTargetWindowLocked(target,
KeyWaiter.RETURN_PENDING_POINTER, qev);
ev = null;
} else {
if (action == MotionEvent.ACTION_DOWN) {
WindowState out = mKeyWaiter.mOutsideTouchTargets;
if (out != null) {
MotionEvent oev = MotionEvent.obtain(ev);
oev.setAction(MotionEvent.ACTION_OUTSIDE);
do {
final Rect frame = out.mFrame;
oev.offsetLocation(-(float)frame.left, -(float)frame.top);
try {
out.mClient.dispatchPointer(oev, eventTime, false);
} catch (android.os.RemoteException e) {
Slog.i(TAG, "WINDOW DIED during outside motion dispatch: " + out);
}
oev.offsetLocation((float)frame.left, (float)frame.top);
out = out.mNextOutsideTouch;
} while (out != null);
mKeyWaiter.mOutsideTouchTargets = null;
}
}
dispatchPointerElsewhereLocked(target, null, ev, ev.getEventTime(), false);
final Rect frame = target.mFrame;
ev.offsetLocation(-(float)frame.left, -(float)frame.top);
mKeyWaiter.bindTargetWindowLocked(target);
}
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
try {
if (DEBUG_INPUT || DEBUG_FOCUS || WindowManagerPolicy.WATCH_POINTER) {
Slog.v(TAG, "Delivering pointer " + qev + " Ev " + ev + " to " + target);
}
if (MEASURE_LATENCY) {
lt.sample("6 before svr->client ipc ", System.nanoTime() - eventTimeNano);
}
target.mClient.dispatchPointer(ev, eventTime, true);
if (MEASURE_LATENCY) {
lt.sample("7 after svr->client ipc ", System.nanoTime() - eventTimeNano);
}
return INJECT_SUCCEEDED;
} catch (android.os.RemoteException e) {
Slog.i(TAG, "WINDOW DIED during motion dispatch: " + target);
mKeyWaiter.mMotionTarget = null;
try {
removeWindow(target.mSession, target.mClient);
} catch (java.util.NoSuchElementException ex) {
// This will happen if the window has already been
// removed.
}
}
return INJECT_FAILED;
}
ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("bingbowan", "action"+ev.getAction()+"reach viewgroup");
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
////////////////bingbowan
if (action == 12) {
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 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;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
if (child.dispatchTouchEvent(ev)) {
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
/////////////////bingbowan
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// 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.
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;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
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) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
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) {
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
// if have a target, see if we're allowed to and want to intercept its
// events
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = 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;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}
测试用的button
button1.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
Log.i("bingbowan", "button1 in app");
v.onTouchEvent(event);
if (event.getAction() == 12)
{
// button1.setFocusableInTouchMode(true);
if (button1.requestFocus()==false)
Log.i("bingbowan", "button1 focus fail");
}
return true;
}
});