zoukankan      html  css  js  c++  java
  • 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 ", 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
        ......

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

  • 相关阅读:
    我再说一遍-微软官方文档查询技巧分享
    你听我说-HandyControl多语言包处理
    太阳当空照-Windows服务化方式脚本封装sc指令
    你听我说-HandyControl源码编译
    太阳当空照-知识分享
    Mac多屏dock切换
    [转]浅析线性表(链表)的头插法和尾插法的区别及优缺点
    点击按钮,在textarea光标位置插入值
    优秀学习笔记汇总>o<
    解决excel文件上传时更改选中的文件出现错误net::ERR_UPLOAD_FILE_CHANGED
  • 原文地址:https://www.cnblogs.com/blogs-of-lxl/p/11946399.html
Copyright © 2011-2022 走看看