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

本文探讨了在Android中如何实现Widget在鼠标经过时自动获取焦点的问题,详细分析了WindowManagerService.java和ViewGroup.java的相关代码,并提供了测试用的Button示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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函数。

详情可参看附件的代码。



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;
    		}
        });


评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值