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 = -,
POINTER_ICON_STYLE_NULL = ,
POINTER_ICON_STYLE_ARROW = ,
POINTER_ICON_STYLE_CONTEXT_MENU = ,
POINTER_ICON_STYLE_HAND = ,
POINTER_ICON_STYLE_HELP = ,
POINTER_ICON_STYLE_WAIT = ,
POINTER_ICON_STYLE_CELL = ,
POINTER_ICON_STYLE_CROSSHAIR = ,
POINTER_ICON_STYLE_TEXT = ,
POINTER_ICON_STYLE_VERTICAL_TEXT = ,
POINTER_ICON_STYLE_ALIAS = ,
POINTER_ICON_STYLE_COPY = ,
POINTER_ICON_STYLE_NO_DROP = ,
POINTER_ICON_STYLE_ALL_SCROLL = ,
POINTER_ICON_STYLE_HORIZONTAL_DOUBLE_ARROW = ,
POINTER_ICON_STYLE_VERTICAL_DOUBLE_ARROW = ,
POINTER_ICON_STYLE_TOP_RIGHT_DOUBLE_ARROW = ,
POINTER_ICON_STYLE_TOP_LEFT_DOUBLE_ARROW = ,
POINTER_ICON_STYLE_ZOOM_IN = ,
POINTER_ICON_STYLE_ZOOM_OUT = ,
POINTER_ICON_STYLE_GRAB = ,
POINTER_ICON_STYLE_GRABBING = , POINTER_ICON_STYLE_SPOT_HOVER = ,
POINTER_ICON_STYLE_SPOT_TOUCH = ,
POINTER_ICON_STYLE_SPOT_ANCHOR = , POINTER_ICON_STYLE_REDDOT = , //增加自定义样式的枚举定义,与上面 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() + ;
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 = ; i < numFrames - ; ++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 >= ) {
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+]={}; 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,);
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 = -;
static int mCountDown = ;
static bool mThreadRun = false;
// 定时器处理函数
static void handle(union sigval v)
{
time_t t;
char p[]; 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 <= ){
mCountDown = ;
mThreadRun = false;
} return;
}
// 线程启动函数
static void* cursorTimer(void* p)
{
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
String8 sys_cmd;
int ret; memset (&evp, , sizeof (evp));
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_THREAD;
evp.sigev_notify_function = handle;
evp.sigev_value.sival_int = ; // 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 = ;
ts.it_interval.tv_nsec = ;
ts.it_value.tv_sec = ;
ts.it_value.tv_nsec = ; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL);
if(ret)
ALOGV("timer_settime"); while(mThreadRun){
usleep();
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 = -; 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 == && (iev.code == || iev.code == )) { // cursor data mCountDown = 30; // 有数据上传则重置定时器倒计时30s
// creat timer thread for sending broadcast to reset icon
if(mTimerThread < ) {
mThreadRun = true;
int ret = pthread_create(&mTimerThread, NULL, &cursorTimer, NULL); // 创建定时器线程
if(!ret)
ALOGV("Create air mouse timer thread successed");
}
} else if(iev.type== && iev.code== && iev.value == ) { // air mouse key down if(mCountDown > ) { // air mouse is enable, then disable right now
mThreadRun = false; // Let timer thread exit
mCountDown = ;
}
}
}
//lxl add for custom mouse icon end
......
以上实现方式和交互逻辑可根据实际项目需求合理设计~
Android:系统自定义鼠标样式切换的更多相关文章
- CSharp如何自定义鼠标样式
一.如何设置鼠标样式? 在CSharp的WinForm开发中,可以通过下面的API设置鼠标样式: //把鼠标样式设置为十字(系统自带的一种鼠标样式) this.Cursor = Cursors.Cro ...
- .Net应用自定义鼠标样式
(调用系统API的方法) 1.引用命名空间 using System.Runtime.InteropServices; 命名空间提供各种各样支持 COM 互操作 及平台调用服务的成员.using Sy ...
- Android中实现全屏、无标题栏的两种办法(另附Android系统自带样式的解释)
在进行UI设计时,我们经常需要将屏幕设置成无标题栏或者全屏.要实现起来也非常简单,主要有两种方法:配置xml文件和编写代码设置. 1.在xml文件中进行配置 在项目的清单文件AndroidManife ...
- C#、WPF中如何自定义鼠标样式
需求:在C#中如何自定义鼠标样式?在这里可以分两种情况,一种是在winForm,另一种是在WPF中(注意使用的Cursor对象不一样) 解决办法如下: a.首先针对WinForm中,我们可以采用图标加 ...
- Arcgis for javascript不同的状态下自定义鼠标样式
俗话说:爱美之心,人皆有之.是的,没错,即使我只是一个做地图的,我也希望自己的地图看起来好看一点.在本文,给大家讲讲在Arcgis for javascript下如何自定义鼠标样式. 首先,说几个状态 ...
- [转]Android中自定义checkbox样式
android中自定义checkbox的图片和大小 其实很简单,分三步: 1.在drawable中创建文件checkbox_selector.xml: <?xml version=" ...
- ArcGIS AddIN开发之自定义鼠标样式
如果想修改Windows默认的鼠标样式,可以这样 //设置鼠标样式 this.Cursor = System.Windows.Forms.Cursors.Cross; 可是如果想设置成一些自定义的很好 ...
- cursor url 自定义鼠标样式
cursor可以自定义鼠标,写法是cursor:url(“图片路径”),pointer; url:需使用的自定义光标的 URL.图片类型需要是.cur或.ani和jpg,png等格式的(.cur或.a ...
- 更改win系统的鼠标样式
一.找一个你心仪的鼠标样式(.cur文件),并放到 C:\Windows\Cursors 目录下 二.打开,控制面板 -> 硬件和声音 -> 鼠标 ,如下图: 三.浏览鼠标目录,找到你存放 ...
随机推荐
- MySQL面试问题
1.MySQL的复制原理以及流程 (1).复制基本原理流程 1. 主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中: 2. 从:io线程——在使用start ...
- iptables详解(2)表中规则管理(增删改查)
我们定义了四张表:raw表.mangle表.nat表.filter表,不同的表有不同的功能 filter表用来过滤,允许哪些ip.端口访问,禁止哪些ip.端口访问,表中会有很多链 ①禁止ip地址访问我 ...
- Luogu P1114 “非常男女”计划/Luogu P2697 宝石串
Luogu P1114 "非常男女"计划/Luogu P2697 宝石串 (感觉我最近很爱做双倍经验的题啊) 使$d$等于第$i$个位置男生数(绿宝石数)减女生数(红宝石数)的差值 ...
- 【动态规划】ZZNU-OJ- 2054 : 油田
2054 : 油田 (一个神奇的功能:点击上方文字进入相应页面) 时间限制:1 Sec 内存限制:32 MiB提交:49 答案正确:6 提交 状态 讨论区 题目描述 在太平洋的一片海域,发现了大量的油 ...
- P1880 [NOI1995]石子合并[环形DP]
题目来源:洛谷 题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将 ...
- idea创建springmvc项目创部署成功,但访问controller层报错404
这个问题网上有很多解决问题,检查配置文件是否正确?controller注解是否扫描?项目启动是否成功等等. 访问报错404,而且后台也没错误,归根结底还是访问路径错了. 1.如图,idea配置tomc ...
- jQuery 中的事件和动画
一.jQuery中的事件 1.加载DOM 以浏览器装载文档为例,在页面加载完毕后,浏览器会通过JavaScript为DOM元素添加事件.在常规JavaScript代码中,通常使用window.onlo ...
- Jquery检验输入值
1.检验邮件 function chkEmail(strEmail) { if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w ...
- imp
imp 重载模块功能 from imp import reload 当在shell中使用一个模块,原始代码改了,要使用新的还得退出shell重新载入一次,这样的话原来的变量就都不在了 这时候可以使用i ...
- RS-232串口通信简介
1969年,美国电子工业协会将RS-232定为串行通信接口的电器标准,该标准定义了数据终端设备DTE(Date Teriminal Equipment)与数据通信设备DCE(Data Communic ...