手機連結藍牙滑鼠後,可以用滑鼠操作手機,當滑鼠移動到某控件後,它的形狀可能從箭頭變為小手(文本連結等)。
辣麼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,摸摸細節也就逐漸清晰了。