Android:系统添加自定义鼠标样式并通过按键切换

Posted sheldon_blogs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:系统添加自定义鼠标样式并通过按键切换相关的知识,希望对你有一定的参考价值。

一、APP通过View修改鼠标样式

  app view上修改鼠标样式比较简单,通过 hover event 获取鼠标坐标并使用如下方法修改为自定义图片:

  getWindow().getDecorView().setPointerIcon(PointerIcon.load(getResources(), R.drawable.pointer_spot_touch_icon));

        imageView = (ImageView) findViewById(R.id.image_view);
        imageView.setOnHoverListener(new View.OnHoverListener() {
            @SuppressLint({"SetTextI18n", "ResourceType"})
            @Override
            public boolean onHover(View v, MotionEvent event) {
                int what = event.getAction();

                textX.setText("X : " + event.getX());
                textY.setText("Y : " + event.getY());

                switch(what){
                    case MotionEvent.ACTION_HOVER_ENTER:  //鼠标进入view
                        Log.i(TAG, "bottom ACTION_HOVER_ENTER...");
                        mOrgPI = getWindow().getDecorView().getPointerIcon();
                        getWindow().getDecorView().setPointerIcon(PointerIcon.load(getResources(), R.drawable.pointer_spot_touch_icon));
                        break;
                    case MotionEvent.ACTION_HOVER_MOVE:  //鼠标在view上
                        Log.i(TAG, "bottom ACTION_HOVER_MOVE...");
                        break;
                    case MotionEvent.ACTION_HOVER_EXIT:  //鼠标离开view
                        Log.i(TAG, "bottom ACTION_HOVER_EXIT...");
                        getWindow().getDecorView().setPointerIcon(mOrgPI);
                        break;
                }
                return false;
            }
        });
    }

  其中pointer_spot_touch_icon.xml 需要声明为 pointer-icon :

<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
    android:bitmap="@drawable/pointer_red_dot_arrow"
    android:hotSpotX="6dp"
    android:hotSpotY="6dp" />

 但是app修改鼠标样式的view关闭后,鼠标样式会恢复成默认的黑箭头,因此不依赖APP去动态切换鼠标样式需要在framework层修改系统源码实现。

 

二、framework层添加自定义鼠标样式并通过按键切换

(1)添加自定义样式资源

  系统图标资源在 frameworks/base/core/res/res/drawable-mdpi/ 目录,其中 pointer_arrow.png、pointer_arrow_large.png 是系统默认的黑色箭头,

  pointer_arrow_red_dot.png、pointer_arrow_red_dot_large.png 是自己添加的红点样式图片:

 

 然后在 frameworks/base/core/res/res/drawable/ 目录添加对应的xml:

 pointer_arrow_red_dot_icon.xml

<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
    android:bitmap="@drawable/pointer_arrow_red_dot"
    android:hotSpotX="5dp"
    android:hotSpotY="5dp" />

  pointer_arrow_red_dot_large_icon.xml

<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
    android:bitmap="@drawable/pointer_arrow_red_dot_large"
    android:hotSpotX="10dp"
    android:hotSpotY="10dp" />

修改 frameworks/base/core/res/res/values/styles.xml 添加资源配置,注意名字的匹配!

 

 

修改 frameworks/base/core/res/res/values/attrs.xml  引用资源:

 

(2)Java 层获取资源

  修改 frameworks/base/core/java/android/view/PointerIcon.java ,添加如下定义:

 

  在 getSystemIconTypeIndex(int type) 函数中返回之前配置的资源:

 

(3)c++层添加对应的id并加载资源

 修改 frameworks/base/core/jni/android_view_PointerIcon.h

* Pointer icon styles.
 * Must match the definition in android.view.PointerIcon.
 */
enum {
    POINTER_ICON_STYLE_CUSTOM = -1,
    POINTER_ICON_STYLE_NULL = 0,
    POINTER_ICON_STYLE_ARROW = 1000,
    POINTER_ICON_STYLE_CONTEXT_MENU = 1001,
    POINTER_ICON_STYLE_HAND = 1002,
    POINTER_ICON_STYLE_HELP = 1003,
    POINTER_ICON_STYLE_WAIT = 1004,
    POINTER_ICON_STYLE_CELL = 1006,
    POINTER_ICON_STYLE_CROSSHAIR = 1007,
    POINTER_ICON_STYLE_TEXT = 1008,
    POINTER_ICON_STYLE_VERTICAL_TEXT = 1009,
    POINTER_ICON_STYLE_ALIAS = 1010,
    POINTER_ICON_STYLE_COPY = 1011,
    POINTER_ICON_STYLE_NO_DROP = 1012,
    POINTER_ICON_STYLE_ALL_SCROLL = 1013,
    POINTER_ICON_STYLE_HORIZONTAL_DOUBLE_ARROW = 1014,
    POINTER_ICON_STYLE_VERTICAL_DOUBLE_ARROW = 1015,
    POINTER_ICON_STYLE_TOP_RIGHT_DOUBLE_ARROW = 1016,
    POINTER_ICON_STYLE_TOP_LEFT_DOUBLE_ARROW = 1017,
    POINTER_ICON_STYLE_ZOOM_IN = 1018,
    POINTER_ICON_STYLE_ZOOM_OUT = 1019,
    POINTER_ICON_STYLE_GRAB = 1020,
    POINTER_ICON_STYLE_GRABBING = 1021,

    POINTER_ICON_STYLE_SPOT_HOVER = 2000,
    POINTER_ICON_STYLE_SPOT_TOUCH = 2001,
    POINTER_ICON_STYLE_SPOT_ANCHOR = 2002,
    
    POINTER_ICON_STYLE_REDDOT = 10001, //增加自定义样式的枚举定义,与上面 PointerIcon.java 中的变量对应
};

 修改 frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp ,加载到自定义枚举变量对应的图片资源:

void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
        std::map<int32_t, PointerAnimation>* outAnimationResources) {
    JNIEnv* env = jniEnv();

    for (int iconId = POINTER_ICON_STYLE_CONTEXT_MENU; iconId <= POINTER_ICON_STYLE_REDDOT;
             ++iconId) {
        PointerIcon pointerIcon;
        loadSystemIconAsSpriteWithPointerIcon(
                env, mContextObj, iconId, &pointerIcon, &((*outResources)[iconId]));
        if (!pointerIcon.bitmapFrames.empty()) {
            PointerAnimation& animationData = (*outAnimationResources)[iconId];
            size_t numFrames = pointerIcon.bitmapFrames.size() + 1;
            animationData.durationPerFrame =
                    milliseconds_to_nanoseconds(pointerIcon.durationPerFrame);
            animationData.animationFrames.reserve(numFrames);
            animationData.animationFrames.push_back(SpriteIcon(
                    pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY));
            for (size_t i = 0; i < numFrames - 1; ++i) {
              animationData.animationFrames.push_back(SpriteIcon(
                      pointerIcon.bitmapFrames[i], pointerIcon.hotSpotX, pointerIcon.hotSpotY));
            }
        }
    }
    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_NULL,
            &((*outResources)[POINTER_ICON_STYLE_NULL]));
}

 

(4)按键切换鼠标样式

   按键事件处理在 frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java 的 interceptKeyBeforeDispatching 函数中

......
            String keytype = event.getDevice().getName();
            int vendorId  = event.getDevice().getVendorId();
            int productId = event.getDevice().getProductId();
            Log.d(TAG, "onKeyDown send keycode : " + keyCode +
                       ", keytype : " + keytype +
                       ", vendorId : " + vendorId +
                       ", productId : " + productId); 

            if (event.getAction() == KeyEvent.ACTION_UP &&
                vendorId == 6421 && productId == 4146) {  //过滤指定VID PID 的HID设备按键值
                
                /* @id:
                 *TYPE_NULL = 0;
                 *TYPE_ARROW = 1000;
                 *TYPE_OEM_FIRST = 10000;
                 *TYPE_ARROW_REDDOT = 10001;
                 */

                switch (keyCode) {

                    case 139: // air mouse
                        InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_ARROW_REDDOT); //设置指定id的鼠标样式
                        SystemProperties.set("persist.sys.lxl.mouse_id", "10001"); //通过属性记录当前样式ID,目的是防止鼠标样式被其他界面更新,后面会介绍到
                        break;
                        
                    default:
                        break;
                } 
            }
......

 其中 setPointerIconType() 方法实现在  frameworks/base/services/core/java/com/android/server/input/InputManagerService.java 

    // Binder call
    @Override
    public void setPointerIconType(int iconId) {
        nativeSetPointerIconType(mPtr, iconId);
    }

 接着调用到 native层的方法,实现在 frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static void nativeSetPointerIconType(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    im->setPointerIconType(iconId);
}
void NativeInputManager::setPointerIconType(int32_t iconId) {
    AutoMutex _l(mLock);
    sp<PointerController> controller = mLocked.pointerController.promote();
    if (controller != NULL) {
        controller->updatePointerIcon(iconId);
    }
}

 updatePointerIcon()方法实现在 frameworks/base/libs/input/PointerController.cpp 中:

void PointerController::updatePointerIcon(int32_t iconId) {
    AutoMutex _l(mLock);

    //lxl add for custom mouse icon start
    const int32_t customIconId = mPolicy->getCustomPointerIconId();
    if(customIconId >= 0) {
        iconId = customIconId;
    }
    //lxl add for custom mouse icon end

    if (mLocked.requestedPointerType != iconId) {
        mLocked.requestedPointerType = iconId;
        mLocked.presentationChanged = true;
        updatePointerLocked();
    }
}

 其中就是通过 getCustomPointerIconId() 去获取当前属性id,强制更新为自定义的样式:

int32_t NativeInputManager::getCustomPointerIconId() {

     //lxl add for custom mouse icon start
     int mVaule;
     char mgetVal[PROPERTY_VALUE_MAX+1]={0};
    
     property_get("persist.sys.lxl.mouse_id",mgetVal,""); //例如鼠标悬浮在按钮上时会变成小手样式,需要使用到前面按键时设定的属性值,强制更新为自定义鼠标样式。
     mVaule = atoi(mgetVal);
     switch (mVaule)
     {
         case POINTER_ICON_STYLE_REDDOT:
         case POINTER_ICON_STYLE_NULL:
             return mVaule;
         default:
             break;
     }
     //lxl add for custom mouse icon end
     return POINTER_ICON_STYLE_CUSTOM;
}

 (5)隐藏鼠标样式接口

   同样在frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp 中添加一个JNI方法:

//lxl add for custom mouse icon start
static void android_server_InputManager_nativefadeMouse(JNIEnv* env,
jclass clazz,jlong ptr){
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

   int mID;
   char *mgetID=new char[PROPERTY_VALUE_MAX];
 
   property_get("sys.ID.mID",mgetID,0);
   mID=atoi(mgetID);

   static sp<PointerControllerInterface>mPointerController=im->obtainPointerController(mID);

   ALOGI("Fade mouse by user");
 
   //start to dispatchMouse
    mPointerController->setPresentation(
                    PointerControllerInterface::PRESENTATION_POINTER);
    mPointerController->fade(PointerControllerInterface::TRANSITION_IMMEDIATE);
}
//lxl add for custom mouse icon end

  并添加到 JNINativeMethod gInputManagerMethods[] 数组中去:

//lxl add for custom mouse icon start
    { "nativefadeMouse", "(J)V",
                    (void*) android_server_InputManager_nativefadeMouse},
//lxl add for custom mouse icon end

  然后在 frameworks/base/services/core/java/com/android/server/input/InputManagerService.java 即可声明使用:

//lxl add for custom mouse icon start
private static native void nativefadeMouse(long ptr);
public void fadeMouse(){
  nativefadeMouse(mPtr);
}
//lxl add for custom mouse icon end

  例如在 systemRunning() 中注册一个广播监听 USB 设备的插拔,在指定VID PID的HID设备拔除时调用fade接口隐藏鼠标:

        //lxl add for custom mouse icon start
        filter = new IntentFilter();
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Bundle bundle = intent.getExtras();  
                String action = intent.getAction();
                String idStr  = bundle.getString("id");
                Slog.i(TAG, "onReceive : " + idStr);
                
                if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { // USB设备接入
                   Log.d(TAG, "onReceive: " + "ACTION_USB_DEVICE_ATTACHED");
                  
                   UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                   if (null == device) {
                       Log.w(TAG, "null usb device");
                       return;
                   }

                   Log.d(TAG, "--->getDeviceName:"+device.getDeviceName() + 
                              ",getVendorId:"+device.getVendorId() +
                              ",getProductId:"+device.getProductId());
                   
                   int count = device.getConfigurationCount();
                   for (int i = 0; i < count; i++) {
                       UsbConfiguration configuration = device.getConfiguration(i);
                       if (null == configuration) {
                           Log.w(TAG, "null usb configuration");
                           return;
                       }
                       int interfaceCount = configuration.getInterfaceCount();
                       for (int j = 0; j < interfaceCount; j++) {
                           UsbInterface usbInterface = configuration.getInterface(j);
                           if (null != usbInterface && usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {

                                Log.d(TAG, "onReceive: " + "A hid device connected, mId: "+usbInterface.getId()
                                            + ", mName: " + usbInterface.getName());
                           }
                       }
                   }

               } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { // USB设备移除
                   Log.d(TAG, "onReceive: " + "ACTION_USB_DEVICE_DETACHED");
            
                   UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                   if (null == device) {
                       Log.w(TAG, "null usb device");
                       return;
                   }                   

                   Log.d(TAG, "--->getDeviceName:"+device.getDeviceName() + 
                              ",getVendorId:"+device.getVendorId() +
                              ",getProductId:"+device.getProductId());
                   
                   int count = device.getConfigurationCount();
                   for (int i = 0; i < count; i++) {
                       UsbConfiguration configuration = device.getConfiguration(i);
                       if (null == configuration) {
                           Log.w(TAG, "null usb configuration");
                           return;
                       }
                       int interfaceCount = configuration.getInterfaceCount();
                       for (int j = 0; j < interfaceCount; j++) {
                           UsbInterface usbInterface = configuration.getInterface(j);
                           if (null != usbInterface && usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {

                                Log.d(TAG, "onReceive: " + "A hid device disconnected, mId: "+usbInterface.getId()
                                            + ", mName: " + usbInterface.getName());
                                            
                                if(device.getVendorId() == 6421 && device.getProductId() == 4146){
                                    fadeMouse(); // 隐藏鼠标
                                }
                           }
                       }
                   }
               }
            }
        }, filter, null, mHandler);
        //lxl add for custom mouse icon end

 

 (6)HID设备数据监听

     实际项目中,需要监测HID设备数据上报状态,所以在 frameworks/native/services/inputflinger/EventHub.cpp 中添加了一个线程+定时器进行实时监测,超时则认为设备移除,恢复默认鼠标样式。

     定时器中通过am发送广播给framework层更新鼠标样式(可参考InputManagerService.java 中 USB 设备插拔广播添加),定时器方法如下:

//lxl add for custom mouse icon start
static pthread_t mTimerThread = -1;
static int mCountDown  = 0;
static bool mThreadRun = false;

// 定时器处理函数
static void handle(union sigval v) { time_t t; char p[32]; time(&t); strftime(p, sizeof(p), "%T", localtime(&t)); ALOGV("%s thread %lu, val = %d, mCountDown = %d , mThreadRun = %d\\n", p, pthread_self(), v.sival_int, mCountDown, mThreadRun); mCountDown--; if(mCountDown <= 0){ mCountDown = 0; mThreadRun = false; } return; }
// 线程启动函数
static void* cursorTimer(void* p) { struct sigevent evp; struct itimerspec ts; timer_t timer; String8 sys_cmd; int ret; memset (&evp, 0, sizeof (evp)); evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_THREAD; evp.sigev_notify_function = handle; evp.sigev_value.sival_int = 3; // handle() args ret = timer_create(CLOCK_REALTIME, &evp, &timer); // 创建定时器 if(ret) ALOGV("cursorTimer Create");// send broadcast to update icon when get air mouse data sys_cmd = "am broadcast -a com.lxl.action.mouseicon --es id \\"ENABLE\\" -f 0x01000000"; system(sys_cmd); ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL); if(ret) ALOGV("timer_settime"); while(mThreadRun){ usleep(500000); ALOGV("cursorTimer is running..."); } ret = timer_delete (timer); if(!ret) ALOGV("cursorTimer Delete");// send broadcast to update icon when get air mouse data sys_cmd = "am broadcast -a com.lxl.action.mouseicon --es id \\"DISABLE\\" -f 0x01000000"; system(sys_cmd); mTimerThread = -1; ALOGV("cursorTimer Exit"); return NULL; } //lxl add for custom mouse icon end

 在EventHub::getEvents() 方法中创建该线程:

         ......
     //lxl add for custom mouse icon start if(device->identifier.vendor == 0x1915 && device->identifier.product == 0x1032) { if(iev.type == 2 && (iev.code == 1 || iev.code == 0)) { // cursor data mCountDown = 30; // 有数据上传则重置定时器倒计时30s // creat timer thread for sending broadcast to reset icon if(mTimerThread < 0) { mThreadRun = true; int ret = pthread_create(&mTimerThread, NULL, &cursorTimer, NULL); // 创建定时器线程 if(!ret) ALOGV("Create air mouse timer thread successed"); } } else if(iev.type==1 && iev.code==67 && iev.value == 1) { // air mouse key down if(mCountDown > 0) { // air mouse is enable, then disable right now mThreadRun = false; // Let timer thread exit mCountDown = 0; } } } //lxl add for custom mouse icon end
    ......

 

  以上实现方式和交互逻辑可根据实际项目需求合理设计~

 

以上是关于Android:系统添加自定义鼠标样式并通过按键切换的主要内容,如果未能解决你的问题,请参考以下文章

把鼠标移动到按钮并点击时,一次除了那几个时间

unityevent与持续按键触发

CSS-自定义鼠标图标

QT Windows平台屏蔽按键事件 自定义拦截按键输入

Android系统中自定义按键的短按双击长按事件

android布局文件里的ProgressBar长形进度条怎么自定义样式