鼠标如何在移动的时候根据当前控件变换形状

手机链接蓝牙鼠标后,可以用鼠标操作手机,当鼠标移动到某控件后,它的形状可能从箭头变为小手(文本链接等)。

辣么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进行加载处理。

扫描二维码关注公众号,回复: 2514479 查看本文章

关注一个类: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,摸摸细节也就逐渐清晰了。


PS:自定义的View,可以复写onResolvePointerIcon,就可以实现让鼠标显示自己期望的样式了。

猜你喜欢

转载自blog.csdn.net/shi_xin/article/details/78677019
今日推荐