手机链接蓝牙鼠标后,可以用鼠标操作手机,当鼠标移动到某控件后,它的形状可能从箭头变为小手(文本链接等)。
辣么google,如何实现的呢?
一、先来看看图片:
有好多鼠标的图片。目录随意一个存储图片的目录即可,例如:8trunk/frameworks/base/core/res/res/drawable-mdpi
二、主题和属性控制:
一步一步来,不急知道google如何做到鼠标移动,指针形状发生变化的逻辑。
先来了解下,这些图片如何被加载的。
一般的,google不会直接在code各种使用图片,一般的都在style里写好,在code中进行解析加载。
1.style中:
/frameworks/base/core/res/res/values/styles.xml
<style name="Pointer">
1351 <item name="pointerIconArrow">@drawable/pointer_arrow_icon</item>
1352 <item name="pointerIconSpotHover">@drawable/pointer_spot_hover_icon</item>
1353 <item name="pointerIconSpotTouch">@drawable/pointer_spot_touch_icon</item>
1354 <item name="pointerIconSpotAnchor">@drawable/pointer_spot_anchor_icon</item>
1355 <item name="pointerIconHand">@drawable/pointer_hand_icon</item>
1356 <item name="pointerIconContextMenu">@drawable/pointer_context_menu_icon</item>
1357 <item name="pointerIconHelp">@drawable/pointer_help_icon</item>
1358 <item name="pointerIconWait">@drawable/pointer_wait_icon</item>
1359 <item name="pointerIconCell">@drawable/pointer_cell_icon</item>
1360 <item name="pointerIconCrosshair">@drawable/pointer_crosshair_icon</item>
1361 <item name="pointerIconText">@drawable/pointer_text_icon</item>
1362 <item name="pointerIconVerticalText">@drawable/pointer_vertical_text_icon</item>
1363 <item name="pointerIconAlias">@drawable/pointer_alias_icon</item>
1364 <item name="pointerIconCopy">@drawable/pointer_copy_icon</item>
1365 <item name="pointerIconAllScroll">@drawable/pointer_all_scroll_icon</item>
1366 <item name="pointerIconNodrop">@drawable/pointer_nodrop_icon</item>
1367 <item name="pointerIconHorizontalDoubleArrow">
1368 @drawable/pointer_horizontal_double_arrow_icon
1369 </item>
1370 <item name="pointerIconVerticalDoubleArrow">
1371 @drawable/pointer_vertical_double_arrow_icon
1372 </item>
1373 <item name="pointerIconTopRightDiagonalDoubleArrow">
1374 @drawable/pointer_top_right_diagonal_double_arrow_icon
1375 </item>
1376 <item name="pointerIconTopLeftDiagonalDoubleArrow">
1377 @drawable/pointer_top_left_diagonal_double_arrow_icon
1378 </item>
1379 <item name="pointerIconZoomIn">@drawable/pointer_zoom_in_icon</item>
1380 <item name="pointerIconZoomOut">@drawable/pointer_zoom_out_icon</item>
1381 <item name="pointerIconGrab">@drawable/pointer_grab_icon</item>
1382 <item name="pointerIconGrabbing">@drawable/pointer_grabbing_icon</item>
1383 </style>
1384
1385 <style name="LargePointer">
1386 <item name="pointerIconArrow">@drawable/pointer_arrow_large_icon</item>
1387 <item name="pointerIconSpotHover">@drawable/pointer_spot_hover_icon</item>
1388 <item name="pointerIconSpotTouch">@drawable/pointer_spot_touch_icon</item>
1389 <item name="pointerIconSpotAnchor">@drawable/pointer_spot_anchor_icon</item>
1390 <item name="pointerIconHand">@drawable/pointer_hand_large_icon</item>
1391 <item name="pointerIconContextMenu">@drawable/pointer_context_menu_large_icon</item>
1392 <item name="pointerIconHelp">@drawable/pointer_help_large_icon</item>
1393 <!-- TODO: create large wait icon. -->
1394 <item name="pointerIconWait">@drawable/pointer_wait_icon</item>
1395 <item name="pointerIconCell">@drawable/pointer_cell_large_icon</item>
1396 <item name="pointerIconCrosshair">@drawable/pointer_crosshair_large_icon</item>
1397 <item name="pointerIconText">@drawable/pointer_text_large_icon</item>
1398 <item name="pointerIconVerticalText">@drawable/pointer_vertical_text_large_icon</item>
1399 <item name="pointerIconAlias">@drawable/pointer_alias_large_icon</item>
1400 <item name="pointerIconCopy">@drawable/pointer_copy_large_icon</item>
1401 <item name="pointerIconAllScroll">@drawable/pointer_all_scroll_large_icon</item>
1402 <item name="pointerIconNodrop">@drawable/pointer_nodrop_large_icon</item>
1403 <item name="pointerIconHorizontalDoubleArrow">
1404 @drawable/pointer_horizontal_double_arrow_large_icon
1405 </item>
1406 <item name="pointerIconVerticalDoubleArrow">
1407 @drawable/pointer_vertical_double_arrow_large_icon
1408 </item>
1409 <item name="pointerIconTopRightDiagonalDoubleArrow">
1410 @drawable/pointer_top_right_diagonal_double_arrow_large_icon
1411 </item>
1412 <item name="pointerIconTopLeftDiagonalDoubleArrow">
1413 @drawable/pointer_top_left_diagonal_double_arrow_large_icon
1414 </item>
1415 <item name="pointerIconZoomIn">@drawable/pointer_zoom_in_large_icon</item>
1416 <item name="pointerIconZoomOut">@drawable/pointer_zoom_out_large_icon</item>
1417 <item name="pointerIconGrab">@drawable/pointer_grab_large_icon</item>
1418 <item name="pointerIconGrabbing">@drawable/pointer_grabbing_large_icon</item>
1419 </style>
1420
2.code中:
xml中定义好了,必然会在code进行加载处理。
关注一个类:PointerIcon.java,可以看到,不同的type,会加载不同的图片。
现在大概有点想法了吧,不同控件设置了相应的type,根据这些type显示相应的鼠标形状。
private static int getSystemIconTypeIndex(int type) {
switch (type) {
case TYPE_ARROW:
return com.android.internal.R.styleable.Pointer_pointerIconArrow;
case TYPE_SPOT_HOVER:
return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
case TYPE_SPOT_TOUCH:
return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
case TYPE_SPOT_ANCHOR:
return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
case TYPE_HAND:
return com.android.internal.R.styleable.Pointer_pointerIconHand;
case TYPE_CONTEXT_MENU:
return com.android.internal.R.styleable.Pointer_pointerIconContextMenu;
case TYPE_HELP:
return com.android.internal.R.styleable.Pointer_pointerIconHelp;
case TYPE_WAIT:
return com.android.internal.R.styleable.Pointer_pointerIconWait;
case TYPE_CELL:
return com.android.internal.R.styleable.Pointer_pointerIconCell;
case TYPE_CROSSHAIR:
return com.android.internal.R.styleable.Pointer_pointerIconCrosshair;
case TYPE_TEXT:
return com.android.internal.R.styleable.Pointer_pointerIconText;
case TYPE_VERTICAL_TEXT:
return com.android.internal.R.styleable.Pointer_pointerIconVerticalText;
case TYPE_ALIAS:
return com.android.internal.R.styleable.Pointer_pointerIconAlias;
case TYPE_COPY:
return com.android.internal.R.styleable.Pointer_pointerIconCopy;
case TYPE_ALL_SCROLL:
return com.android.internal.R.styleable.Pointer_pointerIconAllScroll;
case TYPE_NO_DROP:
return com.android.internal.R.styleable.Pointer_pointerIconNodrop;
case TYPE_HORIZONTAL_DOUBLE_ARROW:
return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow;
case TYPE_VERTICAL_DOUBLE_ARROW:
return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow;
case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW:
return com.android.internal.R.styleable.
Pointer_pointerIconTopRightDiagonalDoubleArrow;
case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW:
return com.android.internal.R.styleable.
Pointer_pointerIconTopLeftDiagonalDoubleArrow;
case TYPE_ZOOM_IN:
return com.android.internal.R.styleable.Pointer_pointerIconZoomIn;
case TYPE_ZOOM_OUT:
return com.android.internal.R.styleable.Pointer_pointerIconZoomOut;
case TYPE_GRAB:
return com.android.internal.R.styleable.Pointer_pointerIconGrab;
case TYPE_GRABBING:
return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
default:
return 0;
}
}
三、如何做到移动的时候,改变鼠标的形状:
从1-2,基本知道了,google是根据type加载不同的图片来显示的。
那么它如何知道什么时候加载那张图片?比如:超链接要显示”小手“,文本编辑要显示”竖线“的呢?
下面介绍一下相关逻辑,不过前提是需要对android 输入事件处理机制,有个基本的了解,不然可能看起来有点蒙。
1.整体逻辑概述:
当输入事件过来的时候(鼠标移动),Input处理到ViewRootImpl的时候,ViewRootImpl是领导,它以巡视的角度进行处理(顺序描述处理逻辑):1.当前到啥控件上了;2.这个控件有没有鼠标的图片类型返回来啊?YES显示这个类型的图片(可能是手,竖线等):NO显示默认的图片(箭头)。
简单概括就是这么一句话。好屌有木有,这么复杂的逻辑,一句话就搞定了^$^。
2.code梳理:
从1,大概知道它怎么处理了。下面看看,代码上如何完成这些的。
需要稍微了解下ViewGroup,ViewRootImpl。
A.ViewRootImpl(涉及很多input派发相关知识,不在展开描述,若不了解,可能会感觉蒙)
根据input事件进行处理,那么肯定是ViewRootImpl进行的第一步了。
输入事件处理7阶段的第四阶段:ViewPostImeInputStage
其中函数:中的maybeUpdatePointerIcon(event);就是开始做事情了。
private int processPointerEvent(QueuedInputEvent q) {
maybeUpdatePointerIcon(event);
调用到关键函数:
private boolean updatePointerIcon(MotionEvent event) {
final int pointerIndex = 0;
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
if (mView == null) {
// E.g. click outside a popup to dismiss it
Slog.d(mTag, "updatePointerIcon called after view was removed");
return false;
}
if (x < 0 || x >= mView.getWidth() || y < 0 || y >= mView.getHeight()) {
// E.g. when moving window divider with mouse
Slog.d(mTag, "updatePointerIcon called with position out of bounds");
return false;
}
final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);//这里,会进行派发到当前控件,询问是否有鼠标图片,没有肯定默认了,有就会用你的。
final int pointerType = (pointerIcon != null) ?
pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;//拿到图片后,获得对应的type,一对一的关系;TYPE_DEFAULT就是箭头,默认就是这个
if (mPointerIconType != pointerType) {
mPointerIconType = pointerType;//更新成员变量,保存这个图片的type,就是保存当前使用的图片,后面判断的时候,没有变化,就不用重新设置了
mCustomPointerIcon = null;
if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
InputManager.getInstance().setPointerIconType(pointerType);//没有自定义,就设置下去;自定义也设置下,调用的接口不一样而已
return true;
}
}
if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
!pointerIcon.equals(mCustomPointerIcon)) {
mCustomPointerIcon = pointerIcon;
InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon);//自定义的鼠标图片
}
return true;
}
B.ViewGroup中:
从A中,看到要进行派发,查找当前鼠标悬浮的控件,然后看这个控件有么有鼠标图片要设置:mView.onResolvePointerIcon(event, pointerIndex);
其实ViewGroup从code角度是比较复杂的,从思路上又是简单的,为什么这么说:不负责任的说,它就是各种递归。。。就是政府机构一球样(踢皮球),各种遍历,返回调来调去,父子各种调用。
我们简而言之,下面两个函数:
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
... ...
final PointerIcon pointerIcon =
dispatchResolvePointerIcon(event, pointerIndex, child);
if (pointerIcon != null) {
if (preorderedList != null) preorderedList.clear();
return pointerIcon;
}
... ...
}
private PointerIcon dispatchResolvePointerIcon(MotionEvent event, int pointerIndex,
View child) {
final PointerIcon pointerIcon;
... ...
pointerIcon = child.onResolvePointerIcon(event, pointerIndex);
... ...
return pointerIcon;
}
这两个就调来调去的,父掉子,结果子还有子,所以子又是父... ...(很形象,viewgroup的遍历,就是这么弄的)
最后,找到了没儿子的儿子,那么就是它了,child.onResolvePointerIcon(event, pointerIndex);
我们举例,这个child就是Button。
Button.java中:返回的是TYPE_HAND。到此,就完事了。
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
if (getPointerIcon() == null && isClickable() && isEnabled()) {
return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
}
return super.onResolvePointerIcon(event, pointerIndex);
}
总结一下:
移动鼠标,遍历当前的控件,控件返回鼠标图片。没移动一下,都会有input事件,所以鼠标图片是”实时“(或者说是及时)更新的。
拿到图片以后,通过InputManager.getInstance().setPointerIconType(pointerType)就设置成功了。
具体的setPointerIconType就不在展开了。
很多没有展开讲,太过详细,反而很难看懂,基本框架了解之后,顺着code,摸摸细节也就逐渐清晰了。