gpt4 book ai didi

android - 如何使系统叠加 View 的背景成倍增加(混合模式)?

转载 作者:行者123 更新时间:2023-12-01 04:57:55 29 4
gpt4 key购买 nike

首先要解决我的问题:我正在尝试创建一个系统覆盖类型的 View (即绘制其他应用程序),该 View (目前)只是全屏纯色。我通过启动/停止服务来打开/关闭它。

到目前为止,这是我的代码(在服务内部):

public void onCreate() {
super.onCreate();
oView = new LinearLayout(this);
oView.setBackgroundColor(0x66ffffff);

PorterDuffColorFilter fil = new PorterDuffColorFilter(0xfff5961b, PorterDuff.Mode.MULTIPLY);
oView.getBackground().setColorFilter(fil);

WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
0 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.addView(oView, params);
}

这样,当我启动此服务时,我可以在其余的常规android使用情况下获得纯色。
现在,我的具体问题是:
有什么办法可以使它在常规的android用法下变为MULTIPLY(想想Photoshop混合模式)?

从您的代码可以看出,我尝试使用PorterDuff过滤器模式(它们的几种不同组合)也是徒劳的。

以下是一些屏幕快照,希望可以更好地解释这一点:

original view <-没有我的服务的原始屏幕。

current result <-与当前代码服务处于打开状态的同一屏幕。

intended result <-预期结果在同一屏幕上。请注意,较深的颜色是如何加到下面的。

如您所见,我当前的代码仅抛出一层纯色。
我感谢所有建议。提前致谢!

最佳答案

对于那些仍然想知道是否可以实现此目标的人,我决定分享我解决该问题的经验。

长话短说

Android的设计使其不允许在不同上下文的 View 之间应用颜色混合(我实际上无法证明这种说法,而这完全是根据我的个人经验),因此要实现您想要的目标,首先需要以某种方式呈现表面上的目标图像,该图像属于您的应用程序上下文之一。

0.概述

至少出于安全原因,没有办法仅在应用程序中广播android主屏幕(否则,通过使应用程序看起来和行为完全像系统主屏幕,可以通过填充用户输入来窃取个人数据和密码) 。但是,在Android API 21(Lollipop)中引入了所谓的 MediaProjection 类,该类引入了记录屏幕的可能性。我认为较低的API不能在不生根设备的情况下实现(如果可以的话,您可以使用adb shell screencap类的 exec 命令从应用程序中自由使用Runtime命令)。如果您具有主屏幕的屏幕截图,则可以从应用程序内部创建主屏幕感觉,然后应用颜色混合。不幸的是,此屏幕录制功能也会同时录制叠加层,因此仅在隐藏叠加层时才需要录制屏幕。

1.截屏

使用MediaProjection截屏并不困难,但是需要花费一些精力来执行。它还要求您具有“Activity ”,以要求用户提供屏幕录像权限。首先,您需要征得使用Media Project Service的许可:

private final static int SCREEN_RECORDING_REQUEST_CODE = 0x0002;
...
private void requestScreenCapture() {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent intent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, SCREEN_RECORDING_REQUEST_CODE);
}

然后,在 onActivityResult方法中处理请求。请注意,您还需要保留此请求返回的 Intent,以供以后使用以获得 VirtualDisplay(实际上完成了捕获屏幕的所有工作)
@Override
protected void onActivityResult(int requestCode, int resultCode, @NonNull Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case SCREEN_RECORDING_REQUEST_CODE:
if (resultCode == RESULT_OK) {
launchOverlay(data);
} else {
finishWithMessage(R.string.error_permission_screen_capture);
}
break;
}
}

最终,您可以从 MediaProjection系统服务中获得一个 MediaProjectionManager实例(使用先前返回的 Intent 数据):
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) appContext.getSystemService(MEDIA_PROJECTION_SERVICE);
// screenCastData is the Intent returned in the onActivityForResult method
mMediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, screenCastData);

为了制作 VirtualDisplay 渲染主屏幕,我们应该为其提供 Surface 。然后,如果需要该表面的图像,则需要缓存图形并将其抓取到位图中,或者要求该表面直接绘制到 Canvas 上。但是,在这种特殊情况下,无需发明轮子,为此已经有一些方便的东西称为 ImageReader 。由于我们需要模仿主屏幕,因此应使用实际设备大小实例化 ImageReader(如果它作为Android屏幕的一部分显示,则包括状态栏和导航按钮栏):
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Display defaultDisplay = mWindowManager.getDefaultDisplay();
Point displaySize = new Point();
defaultDisplay.getRealSize(displaySize);
final ImageReader imageReader = ImageReader.newInstance(displaySize.x, displaySize.y, PixelFormat.RGBA_8888, 1);

我们还需要设置传入图像缓冲区的监听器,因此在获取屏幕截图时,它将转到该监听器:
imageReader.setOnImageAvailableListener(this, null);

我们将很快返回此监听器的实现。现在让我们创建一个 VirtualDisplay,以便它最终可以完成我们的工作以截取屏幕截图:
final Display defaultDisplay = mWindowManager.getDefaultDisplay();
final DisplayMetrics displayMetrics = new DisplayMetrics();
defaultDisplay.getMetrics(displayMetrics);
mScreenDpi = displayMetrics.densityDpi;
mVirtualDisplay = mMediaProjection.createVirtualDisplay("virtual_display",
imageReader.getWidth(),
imageReader.getHeight(),
mScreenDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
imageReader.getSurface(),
null, null);

现在,每当 ImageRender发出 VirtualDisplay时,它将向其监听器发送新图像。监听器仅包含一种方法,其外观如下:
@Override
public void onImageAvailable(@NonNull ImageReader reader) {
final Image image = reader.acquireLatestImage();
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * image.getWidth();
final Bitmap bitmap = Bitmap.createBitmap(image.getWidth() + rowPadding / pixelStride,
image.getHeight(), Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
image.close();
reader.close();
onScreenshotTaken(bitmap);
}

图像处理完成后,应调用 close() ImageReader 方法。它释放了它分配的所有资源,同时使 ImageReder不可用。因此,当您需要获取下一个屏幕截图时,您将不得不实例化另一个 ImageReader。 (由于我们的实例是使用1张图片的缓冲区制作的,因此无论如何都无法使用)

2.将屏幕截图放在主屏幕上

现在,当我们获得主屏幕的确切屏幕截图时,应该放置它,以便最终用户不会注意到差异。我选择了具有类似布局参数的 LinearLayout,而不是您在问题中使用的 ImageView。由于它已经覆盖了整个屏幕,因此唯一要做的就是调整屏幕快照的位置,因此它不包含状态栏和导航栏按钮(这是因为屏幕记录实际上捕获了整个设备屏幕,而我们的屏幕叠加 View 只能放置在“可绘制”区域内)。为此,我们可以使用简单的矩阵转换:
private void showDesktopScreenshot(Bitmap screenshot, ImageView imageView) {
// The goal is to position the bitmap such it is attached to top of the screen display, by
// moving it under status bar and/or navigation buttons bar
Rect displayFrame = new Rect();
imageView.getWindowVisibleDisplayFrame(displayFrame);
final int statusBarHeight = displayFrame.top;
imageView.setScaleType(ImageView.ScaleType.MATRIX);
Matrix imageMatrix = new Matrix();
imageMatrix.setTranslate(-displayFrame.left, -statusBarHeight);
imageView.setImageMatrix(imageMatrix);
imageView.setImageBitmap(screenshot);
}

3.应用颜色混合

由于我们已经有了一个包含主屏幕的 View ,因此我发现可以方便地对其进行扩展,因此可以在应用了混合模式的情况下绘制叠加层。让我们扩展`ImageView类,并引入几个空方法:
class OverlayImageView extends ImageView {
@NonNull
private final Paint mOverlayPaint;

public OverlayImageView(Context context) {
super(context);
}

void setOverlayColor(int color) {
}

void setOverlayPorterDuffMode(PorterDuff.Mode mode) {
}
}

如您所见,我添加了一个 Paint 类型的字段。这将帮助我们绘制叠加层并反射(reflect)更改。实际上,我并不认为自己是计算机图形学的专家,但为了避免造成任何混淆,我想强调一下,您在问题中使用的方法有些错误-您将 View 的背景可绘制为背景(实际上不依赖于 View 背后的内容),而仅在其上应用PorterDuff模式。因此,背景用作目标颜色,而您在 PorterDuffColorFilter构造函数中指定的颜色用作源颜色。只有这两件事融合了,其他都没有受到影响。但是,当您在 Paint上应用PorterDuff模式并将其绘制在 Canvas 上时,此 Canvas 后面的所有 View (属于同一 Context )将被混合。让我们首先覆盖 onDraw方法,然后在 ImageView中画图:
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
canvas.drawPaint(mOverlayPaint);
}

现在,我们只需要为 setter 添加相应的更改,并通过调用 invalidate() 方法要求 View 重绘即可:
void setOverlayColor(@SuppressWarnings("SameParameterValue") int color) {
mOverlayPaint.setColor(color);
invalidate();
}

void setOverlayPorterDuffMode(@SuppressWarnings("SameParameterValue") PorterDuff.Mode mode) {
mOverlayPaint.setXfermode(new PorterDuffXfermode(mode));
invalidate();
}

就是这样!这是没有混合乘法模式并且应用了它的相同效果的示例:



代替结论

当然,这种解决方案远非完美-覆盖层完全覆盖主屏幕,并且要使其可行,您应该想出很多解决方法来解决一些极端情况,例如常见的交互,键盘,滚动,调用,系统对话框和很多其他的。我进行了尝试,并制作了一个快速的应用程序,该应用程序可以在用户触摸屏幕时隐藏覆盖层。它仍然有很多问题,但是对于您来说应该是一个很好的起点。主要问题是使应用程序意识到周围正在发生的事情。我没有检查 Accessibility Service,但它应该非常合适,因为与普通服务相比,它具有更多有关用户操作的信息。您可以在此处查看我的解决方案- https://github.com/AlexandrSMed/stack-35590953

关于android - 如何使系统叠加 View 的背景成倍增加(混合模式)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35590953/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com