在Android 5及以上版本中如何在导航栏(和其他应用程序)上绘制内容

14

我想在屏幕上从一个服务中绘制一个(鼠标指针)图标,覆盖其他应用程序。我已经实现了这个功能,我可以在屏幕上画图,除了导航栏。我研究了这里这里以及这里的一些其他问题,并尝试使用TYPE_SYSTEM_OVERLAYTYPE_TOASTTYPE_SYSTEM_ERROR和一些其他窗口类型,但没有成功。

我只是想在屏幕上画一个鼠标指针,不需要捕获焦点,仅持续一两秒钟。当我试图画导航栏时,它只会被覆盖在下方(实际上,RelativeLayout 结束于与导航栏的边界处,即使我手动指定高度)。下面的截图显示了屏幕右下角的手形指针,那是我能够将其放置的最低位置。请注意,我不想在我的应用程序中隐藏导航栏,而是想在其他应用程序上绘制它。

overlay-pointer-on-screen

我甚至尝试设置 WindowManager.LayoutParams 实例中的 xposypos 偏移量设置,但这只是偏移了布局,仍然在导航栏下面。

我正在使用以下布局参数显示此窗口:

        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.TRANSPARENT);

然后我将带有这些参数的RelativeLayout添加到WindowManager中:windowManager.addView(relativeLayout, params);


你在清单文件中声明了 绘制在其他应用程序上层 的权限吗? - mr.icetea
@mr.icetea 当然可以。否则我就无法绘制了。我可以很好地绘制,甚至可以在状态栏上绘制,只是不能在导航栏(底部,返回、主页和最近使用的软键所在的位置)上绘制。 - miha
2个回答

11

经过许多尝试,我成功地找到了正确的标志来使它工作。它几乎可以(对于大多数应用程序)。以下是实现的代码(一个按钮单击处理程序示例):

@Override
public void onClick(View view) {
    if (_windowManager == null) {
        _windowManager = (WindowManager) MainActivity.this.getSystemService(Context.WINDOW_SERVICE);
    }

    WindowManager.LayoutParams params;
    // create the view on the first try
    if (_overlayView == null) {
        ImageView hand;

        _overlayView = new FrameLayout(MainActivity.this);
        hand = new ImageView(MainActivity.this);
        hand.setImageResource(R.drawable.icon_hand_pointer);
        int w = hand.getDrawable().getIntrinsicWidth();
        int h = hand.getDrawable().getIntrinsicHeight();
        _overlayView.addView(hand);

        params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                WindowManager.LayoutParams.FLAG_FULLSCREEN |
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                PixelFormat.TRANSLUCENT);

        params.width = w;
        params.height = h;
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.x = _xOffset;
        params.y = _yOffset;
        _windowManager.addView(_overlayView, params);
    } else {
        // move the view
        params = (WindowManager.LayoutParams) _overlayView.getLayoutParams();
        params.x = _xOffset;
        params.y = _yOffset;
        _windowManager.removeView(_overlayView);
        _overlayView.setLayoutParams(params);
        _windowManager.addView(_overlayView, params);
    }

    _xOffset += 40;
    _yOffset += 100;
}

这是在应用程序中运行该代码的屏幕截图(您可以看到手部叠加层;这是在安装有Android 6.0.1的Nexus 5上):

输入图片说明

还可以参考以下关于无法在导航栏上绘制的评论:comment - miha
你有没有找到一种方法让它在所有应用程序中都能正常工作?我无法让我的应用在某些应用程序中(如Chrome和Tinder的个人资料查看模式)绘制在导航栏上方!它只是在导航栏下面绘制。 - Flyview
@Flyview,没有,我们最终采用了这个解决方案。 - miha
@miha 这个有效吗?你能贴一下截图吗? - Karun Shrestha
@KarunShrestha,它可以工作。您实际上可以在“ISL Light”应用程序中进行测试(可在Play上获得)。我还将尝试在上面的答案中添加屏幕截图。 - miha

3
底部的导航栏不能被覆盖,因为上面绘制的内容可能会影响到主页按钮、返回按钮等的使用。但是你可以将你的活动设置为全屏模式,这意味着它会隐藏导航按钮,直到你从底部向上滑动,这样你就可以自由地在屏幕上绘制任何东西了。
编辑:我发现有一些类似的问题,这个问题已经有了答案:在Android中在导航栏顶部绘制位图 的答案是添加一个纵向偏移量,该偏移量等于导航栏的大小。

TeamViewer QS应用程序可以做到这一点。其想法是在其他应用程序上绘制,而不是在自己的活动上绘制。 - miha
@miha 看起来它会快速截屏,然后让技术人员在屏幕上绘制,然后再让用户重新掌控。 - LonelyIdiot
@user3911725 我刚刚重新测试了TeamViewer应用程序 - 我不相信它正在使用屏幕截图,因为我可以在TV覆盖指针的同时与启动器或应用程序进行交互。我之前找到了你提供的答案,但不幸的是那并没有起作用(或者我缺少关键的组成部分)。 - miha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接