android的widget在鼠标划过时自动聚焦

来源:互联网 发布:游戏编程之从零开始 编辑:程序博客网 时间:2024/04/29 23:58
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.csdn.net/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;    }        });


原创粉丝点击