安卓基础(悬浮窗和摄像)

ACTION_MANAGE_OVERLAY_PERMISSION​ 的作用就是 ​​打开系统设置的「悬浮窗权限管理页面」

Intent intent = new Intent(
    Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
    Uri.parse("package:" + getPackageName())
);
startActivity(intent);

直接跳转目标应用的权限页​
Uri.parse("package:" + getPackageName()) 的作用是直接把用户带到 ​​当前应用的悬浮窗权限设置页​​,而不是让用户在系统设置中手动寻找。

Settings.canDrawOverlays(this) —— 检查有没有「贴纸许可证」​

从A页面使用startActivityForResult()跳转到B页面,B页面点击返回时将新写入的值传回到A页面。

startActivityForResult 的作用​

  1. ​启动子 Activity 并等待结果​
    当需要从另一个 Activity 获取数据(如用户选择照片、输入文本等)时,使用此方法启动子 Activity。子 Activity 关闭后,父 Activity 的 onActivityResult() 方法会被调用,接收返回的数据。

  2. ​数据双向传递​
    普通 startActivity 只能单向传递数据(父 → 子),而 startActivityForResult 支持子 Activity 将数据回传(子 → 父)。

为什么需要请求码(Request Code)?​

请求码是一个 ​​唯一整型标识符​​,用于在父 Activity 中区分不同的子 Activity 请求。它的必要性体现在以下场景:

  1. ​多个子 Activity 返回结果到同一个父 Activity​
    例如,父 Activity 同时启动“选择图片”和“拍照”两个子 Activity,两者都可能返回图片数据。通过不同的请求码,父 Activity 可以判断数据来源并做相应处理。

  2. ​同一子 Activity 多次启动​
    若多次启动同一个子 Activity(如多次选择不同文件),请求码可帮助区分是哪一次调用返回的结果。

创建悬浮窗(Floating Window)​

  • createFloatingWindow()
    • ​悬浮窗参数​​:使用 TYPE_APPLICATION_OVERLAY 类型,允许悬浮在其他应用上方。
    • ​视图布局​​:加载 R.layout.floating_window,包含“截图”和“确认”按钮。
    • ​拖动逻辑​​:通过 OnTouchListener 实现悬浮窗的拖拽移动。
    • ​按钮交互​​:
      • ​截图按钮​​:点击后显示可调整的截图框(showCaptureFrame())。
      • ​确认按钮​​:点击后执行截图(takeScreenshot()),并切换按钮状态。

设置按钮点击事件​

​截图按钮点击​
captureButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        showCaptureFrame();                // 显示截图框
        confirmButton.setVisibility(View.VISIBLE); // 显示确认按钮
        captureButton.setVisibility(View.GONE);    // 隐藏截图按钮
    }
});
  • ​功能​​:点击后显示截图框,并切换按钮状态,让用户确认截图区域。
​确认按钮点击​
confirmButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        takeScreenshot();                  // 执行截图
        confirmButton.setVisibility(View.GONE);  // 隐藏确认按钮
        captureButton.setVisibility(View.VISIBLE); // 显示截图按钮
    }
});
  • ​功能​​:触发截图操作,并在截图完成后恢复按钮初始状态。

showCaptureFrame()

创建截图框布局参数​

captureParams = new WindowManager.LayoutParams(
    800,  // 初始宽度(像素)
    600,  // 初始高度(像素)
    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 类型:系统悬浮窗
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,        // 标志:不获取焦点
    PixelFormat.TRANSLUCENT // 透明背景
);
captureParams.gravity = Gravity.TOP | Gravity.START; // 定位基准:左上角
captureParams.x = 100; // 初始X坐标(距离屏幕左边缘100像素)
captureParams.y = 100; // 初始Y坐标(距离屏幕顶部100像素)
  • ​TYPE_APPLICATION_OVERLAY​​:Android 8.0+ 的悬浮窗类型。
  • ​FLAG_NOT_FOCUSABLE​​:允许用户操作穿透截图框,避免影响其他应用。

加载截图框布局​

captureFrame = LayoutInflater.from(this).inflate(R.layout.capture_frame, null);
final View resizeHandle = captureFrame.findViewById(R.id.resizeHandle); // 调整大小的手柄视图
final FrameLayout captureArea = captureFrame.findViewById(R.id.captureArea); // 可拖动的区域
  • 布局文件​​:R.layout.capture_frame 定义截图框的UI结构(如边框、调整手柄)。
  • ​视图元素​​:
    • resizeHandle:用于拖动调整截图框大小的手柄(通常位于右下角)。
    • captureArea:用户可拖动的区域(通常是截图框的整个区域)。

代码注释原理

// 创建ImageReader实例,用于从Surface中读取图像数据
imageReader = ImageReader.newInstance(
        screenWidth,                // 图像宽度(屏幕宽度)
        screenHeight,               // 图像高度(屏幕高度)
        PixelFormat.RGBA_8888,      // 像素格式(32位RGBA,每个通道8位)
        1                           // 缓冲区数量(仅保留最新一帧)
);

// 创建VirtualDisplay虚拟显示设备,将屏幕内容投射到ImageReader的Surface
virtualDisplay = mediaProjection.createVirtualDisplay(
        "Screenshot",               // 虚拟显示名称(任意标识符)
        screenWidth,                // 显示宽度(与屏幕一致)
        screenHeight,               // 显示高度(与屏幕一致)
        screenDensity,              // 显示密度(像素密度DPI)
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // 标志位(自动镜像显示方向)
        imageReader.getSurface(),   // 输出目标Surface(绑定到ImageReader)
        null,                       // 回调接口(此处未使用)
        handler                     // 事件处理器(指定操作线程)
);

// 延迟300毫秒执行截图操作,等待屏幕内容稳定
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        Image image = null;
        try {
            // 从ImageReader获取最新的图像对象
            image = imageReader.acquireLatestImage();
            if (image != null) {
                // 获取图像平面数据(RGBA格式只有一个平面)
                Image.Plane[] planes = image.getPlanes();
                // 获取第一个平面的像素缓冲区
                ByteBuffer buffer = planes[0].getBuffer();
                // 单个像素的字节跨度(RGBA_8888格式为4字节)
                int pixelStride = planes[0].getPixelStride();
                // 每行的字节跨度(可能包含填充字节)
                int rowStride = planes[0].getRowStride();
                // 计算行填充字节数(实际数据宽度与声明的宽度差异)
                int rowPadding = rowStride - pixelStride * screenWidth;

                // 创建全屏位图(考虑行填充调整实际宽度)
                Bitmap fullScreenBitmap = Bitmap.createBitmap(
                        screenWidth + rowPadding / pixelStride, // 调整后的宽度
                        screenHeight,                           // 高度不变
                        Bitmap.Config.ARGB_8888                 // 配置为ARGB_8888格式
                );
                // 将缓冲区数据复制到位图中
                fullScreenBitmap.copyPixelsFromBuffer(buffer);
                
                // 保存全屏截图到公开相册目录
                saveScreenshot(fullScreenBitmap, "Fullscreen_" + System.currentTimeMillis() + ".jpg", true);
                
                // 创建调试用的全屏截图副本
                Bitmap debugCopy = fullScreenBitmap.copy(Bitmap.Config.ARGB_8888, true);
                saveDebugScreenshot(debugCopy, "fullscreen_" + System.currentTimeMillis() + ".jpg");
                
                // 在调试截图上绘制用户选择的红色选框
                debugCopy = fullScreenBitmap.copy(Bitmap.Config.ARGB_8888, true);
                Canvas canvas = new Canvas(debugCopy);          // 创建画布
                Paint paint = new Paint();                      // 创建画笔
                paint.setColor(Color.RED);                      // 设置红色
                paint.setStyle(Paint.Style.STROKE);             // 描边样式
                paint.setStrokeWidth(10);                       // 线宽10像素
                canvas.drawRect(captureX, captureY,            // 绘制矩形框
                        captureX + captureWidth, 
                        captureY + captureHeight, 
                        paint);
                saveDebugScreenshot(debugCopy, "marked_" + System.currentTimeMillis() + ".jpg");

                // 计算合法的裁剪区域(防止超出屏幕边界)
                int x = Math.max(0, captureX);                   // X起点不小于0
                int y = Math.max(0, captureY);                   // Y起点不小于0
                int width = Math.min(captureWidth, screenWidth - x); // 宽度不超过屏幕右侧
                int height = Math.min(captureHeight, screenHeight - y); // 高度不超过屏幕底部
                
                // 尝试裁剪指定区域
                Bitmap croppedBitmap = null;
                try {
                    croppedBitmap = Bitmap.createBitmap(
                            fullScreenBitmap,    // 源位图
                            x, y,                // 起始坐标
                            width, height        // 裁剪尺寸
                    );
                    Log.d(TAG, "成功裁剪: x=" + x + ", y=" + y 
                            + ", width=" + width + ", height=" + height);
                } catch (IllegalArgumentException e) {
                    // 处理非法参数异常(如负坐标或超界)
                    Log.e(TAG, "裁剪失败: " + e.getMessage() + ", 尝试默认值");
                    // 使用默认居中区域(800x600)
                    width = Math.min(800, screenWidth);
                    height = Math.min(600, screenHeight);
                    x = (screenWidth - width) / 2;  // 水平居中
                    y = (screenHeight - height) / 2;// 垂直居中
                    croppedBitmap = Bitmap.createBitmap(
                            fullScreenBitmap, x, y, width, height);
                }

                // 释放全屏位图内存
                fullScreenBitmap.recycle();
                
                if (croppedBitmap != null) {
                    // 保存裁剪后的截图到相册
                    saveScreenshot(croppedBitmap, "Cropped_" + System.currentTimeMillis() + ".jpg", false);
                    croppedBitmap.recycle();  // 释放裁剪位图内存
                } else {
                    Toast.makeText(FloatingWindowService.this, 
                            "截图失败:无法裁剪图像", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(FloatingWindowService.this, 
                        "截图失败:无法获取图像", Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            // 捕获并记录所有异常
            Log.e(TAG, "Error capturing screen", e);
            Toast.makeText(FloatingWindowService.this, 
                    "截图失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        } finally {
            // 确保资源释放
            if (image != null) {
                image.close();  // 关闭Image对象
            }
            // 恢复截图框可见
            if (captureFrame != null) {
                captureFrame.setVisibility(View.VISIBLE);
            }
            // 恢复确认按钮可见
            Button confirmButton = floatingView.findViewById(R.id.confirmButton);
            if (confirmButton != null) {
                confirmButton.setVisibility(View.VISIBLE);
            }
            // 释放MediaProjection相关资源
            releaseMediaProjection();
            isCapturing = false;  // 重置截图状态
        }
    }
}, 300);  // 延迟300毫秒执行

123

启动截图 → 初始化MediaProjection → 创建VirtualDisplay → 延迟捕获图像 → 处理像素数据 → 裁剪区域 → 保存结果 → 清理资源

123

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奶龙牛牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值