Android 系统鼠标

2 篇文章 0 订阅
1 篇文章 0 订阅

Base on AOSP Android 11.

1. 自定义鼠标样式

定义了鼠标样式对应事件下的显示样式.

  1. 系统资源路径: frameworks/base/core/res/res/
  2. 样式定义路径: frameworks/base/core/res/res/values/styles.xml

2. 控制类:

1. 代表类: PointerIcon.java

frameworks/base/core/java/android/view/PointerIcon.java

PointerIcon代表了系统的鼠标样式, 在方法 中定义了几乎所有需要用到的样式:

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;
        }
    }

2. 控制类: PointerController

工程路径: frameworks/base/libs/input/

1. PointerController.cpp:
控制当前鼠标的坐标位置, 更新和动画等(横竖屏切换的坐标转换也在这里).
Input系统中对于鼠标坐标的上报也是从这里获取(getPosition接口)的.

PointerController中对应于时间更新的接口是:PointerControllerInterface
frameworks/native/services/inputflinger/include/PointerControllerInterface.h
这个接口由InputFlinger使用(更新, 获取等)

PointerController是PointerControllerInterface的具体实现类.

_1.1. 鼠标的控制接口

//设置窗口大小,鼠标会在这个范围内可用
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
    AutoMutex _l(mLock);
    if (viewport == mLocked.viewport) {
        return;
    }

    const DisplayViewport oldViewport = mLocked.viewport;
    mLocked.viewport = viewport;

    int32_t oldDisplayWidth, oldDisplayHeight;
    getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
    int32_t newDisplayWidth, newDisplayHeight;
    getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);

    // Reset cursor position to center if size or display changed.
    if (oldViewport.displayId != viewport.displayId
            || oldDisplayWidth != newDisplayWidth
            || oldDisplayHeight != newDisplayHeight) {

        float minX, minY, maxX, maxY;
        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
            mLocked.pointerX = (minX + maxX) * 0.5f;
            mLocked.pointerY = (minY + maxY) * 0.5f;
            // Reload icon resources for density may be changed.
            loadResourcesLocked();
        } else {
            mLocked.pointerX = 0;
            mLocked.pointerY = 0;
        }

        fadeOutAndReleaseAllSpotsLocked();
    } else if (oldViewport.orientation != viewport.orientation) {
    //横竖屏切换的时候,会转换鼠标在屏幕上的位置计算参照
        // Apply offsets to convert from the pixel top-left corner position to the pixel center.
        // This creates an invariant frame of reference that we can easily rotate when
        // taking into account that the pointer may be located at fractional pixel offsets.
        float x = mLocked.pointerX + 0.5f;
        float y = mLocked.pointerY + 0.5f;
        float temp;

        // Undo the previous rotation.
        switch (oldViewport.orientation) {
        case DISPLAY_ORIENTATION_90:
            temp = x;
            x =  oldViewport.deviceHeight - y;
            y = temp;
            break;
        case DISPLAY_ORIENTATION_180:
            x = oldViewport.deviceWidth - x;
            y = oldViewport.deviceHeight - y;
            break;
        case DISPLAY_ORIENTATION_270:
            temp = x;
            x = y;
            y = oldViewport.deviceWidth - temp;
            break;
        }

        // Perform the new rotation.
        switch (viewport.orientation) {
        case DISPLAY_ORIENTATION_90:
            temp = x;
            x = y;
            y = viewport.deviceHeight - temp;
            break;
        case DISPLAY_ORIENTATION_180:
            x = viewport.deviceWidth - x;
            y = viewport.deviceHeight - y;
            break;
        case DISPLAY_ORIENTATION_270:
            temp = x;
            x = viewport.deviceWidth - y;
            y = temp;
            break;
        }

        // Apply offsets to convert from the pixel center to the pixel top-left corner position
        // and save the results.
        mLocked.pointerX = x - 0.5f;
        mLocked.pointerY = y - 0.5f;
    }

    updatePointerLocked();
}

//鼠标移动更新, 这些都是由对应device的Mapper来处理的.
void PointerController::move(float deltaX, float deltaY) {
#if DEBUG_POINTER_UPDATES
    ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
#endif
    if (deltaX == 0.0f && deltaY == 0.0f) {
        return;
    }

    AutoMutex _l(mLock);

    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
}

//获取当前鼠标的位置
//注意: 这个会被InputReader当作原始做坐标数据上报,界面上的鼠标事件的坐标都是来自于这里, 在move之后调用
void PointerController::getPosition(float* outX, float* outY) const {
    AutoMutex _l(mLock);

    *outX = mLocked.pointerX;
    *outY = mLocked.pointerY;
}

2. SpriteController.cpp:
管理鼠标的图层, 显示图标样式和显示的样式变换等.
!!鼠标的图层是单独的, 一般的设备都是从硬件对鼠标的图层进行支持(也是谷歌的官方要求),在所有内容的最上层.

_2.1. 鼠标图层的初始化:

sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
    ensureSurfaceComposerClient();

    sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
            String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
            ISurfaceComposerClient::eHidden |
            ISurfaceComposerClient::eCursorWindow); //Flag eCursorWindow是必须的,
                                                   //标记是鼠标图层,硬件就会将该图层特别处理.
    if (surfaceControl == NULL || !surfaceControl->isValid()) {
        ALOGE("Error creating sprite surface.");
        return NULL;
    }
    return surfaceControl;
}

3. 鼠标样式更新: ViewRootImpl

鼠标样式具体更新的操作是在View系统的根节点ViewRootImpl中:

android.view.ViewRootImpl.ViewPostImeInputStage#onProcess

    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                //Pointer事件包括鼠标点击和触摸点击
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }

    //processPointerEvent->maybeUpdatePointerIcon

    private void maybeUpdatePointerIcon(MotionEvent event) {
        if (event.getPointerCount() == 1 && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
            if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
                    || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
                // Other apps or the window manager may change the icon type outside of
                // this app, therefore the icon type has to be reset on enter/exit event.
                mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
            }

            if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
                //针对鼠标事件, 更新POinterIcon
                if (!updatePointerIcon(event) &&
                        event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
                    mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
                }
            }
        }
    }

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;
    }

    ///
    // 这里开始调用View Tree的onResolvePointerIcon方法, 确定在每个view下鼠标应该显示的样式图片.
    // 通过返回PointerIcon对象给系统用, 可以自定义样式图片, 也可以获取系统的样式.
    // 默认是黑色白边的箭头样式;
    ///
    final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
    final int pointerType = (pointerIcon != null) ?
            pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;

    if (mPointerIconType != pointerType) {
        mPointerIconType = pointerType;
        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;
}

// 默认是箭头样式的
// android.view.View#onResolvePointerIcon
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
    final float x = event.getX(pointerIndex);
    final float y = event.getY(pointerIndex);
    if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) {
        return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
    }
    return mPointerIcon;
}

4. 自定义鼠标样式案例

1. 通过重写onResolvePointerIcon

自定义箭头样式, 只需要在View中重写 onResolvePointerIcon, 闭关返回需要自定义的PointerIcon对象即可.
我们看看系统中的几个View的样式设置:

1. TextView

    @Override
    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
        if (mSpannable != null && mLinksClickable) {
            final float x = event.getX(pointerIndex);
            final float y = event.getY(pointerIndex);
            final int offset = getOffsetForPosition(x, y);
            final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
                    ClickableSpan.class);
            if (clickables.length > 0) {
            //对于Spannable的类型, 链接位置会编程"小手"样式
                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
            }
        }
        if (isTextSelectable() || isTextEditable()) {
            // 对于文本的,会变成"文本"样式(就是竖线样子)
            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
        }
        return super.onResolvePointerIcon(event, pointerIndex);
    }

2. Button

    @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);
    }

2. 在xml中直接设置

如: android:pointerIcon="hand"

代码案例来自源码 development/samples/ApiDemos/

<!-- development/samples/ApiDemos/res/layout/pointer_types.xml -->
<Button
    android:id="@+id/pointer_type_view_hand"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/pointer_type_hand"
    android:pointerIcon="hand" /> <!--显示hand -->

<Button
    android:id="@+id/pointer_type_view_context_menu"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/pointer_type_context_menu"
    android:pointerIcon="context_menu" /> <!--显示context_menu -->

<Button
    android:id="@+id/pointer_type_view_custom"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/pointer_type_custom_resource"
    android:pointerIcon="@drawable/custom_pointer_icon" />  <!--显示自定义样式 -->
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值