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

与天地兮比寿,与日月兮齐光。这篇文章主要讲述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; } }); }

【Android(系统添加自定义鼠标样式并通过按键切换)】   其中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 是自己添加的红点样式图片:
Android(系统添加自定义鼠标样式并通过按键切换)

文章图片

 
 然后在 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 添加资源配置,注意名字的匹配!
Android(系统添加自定义鼠标样式并通过按键切换)

文章图片

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

文章图片

 
修改  frameworks/base/core/res/res/values/attrs.xml  引用资源:
Android(系统添加自定义鼠标样式并通过按键切换)

文章图片

 
(2)Java 层获取资源
修改 frameworks/base/core/java/android/view/PointerIcon.java ,添加如下定义:
Android(系统添加自定义鼠标样式并通过按键切换)

文章图片

 
在 getSystemIconTypeIndex(int type) 函数中返回之前配置的资源:
Android(系统添加自定义鼠标样式并通过按键切换)

文章图片

 
(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 = https://www.songbingjia.com/android/(*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 endif (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() argsret = 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 datamCountDown = 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 =https://www.songbingjia.com/android/= 1) { // air mouse key downif(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
......

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

    推荐阅读