天天看點

滑鼠如何在移動的時候根據目前控件變換形狀一、先來看看圖檔:二、主題和屬性控制:三、如何做到移動的時候,改變滑鼠的形狀:總結一下:

手機連結藍牙滑鼠後,可以用滑鼠操作手機,當滑鼠移動到某控件後,它的形狀可能從箭頭變為小手(文本連結等)。

辣麼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,摸摸細節也就逐漸清晰了。

PS:自定義的View,可以複寫onResolvePointerIcon,就可以實作讓滑鼠顯示自己期望的樣式了。