Android笔记(八):基于CameraX库结合Compose和传统视图组件PreviewView实现照相机画面预览和照相功能

news2024/11/13 23:54:09

CameraX是JetPack库之一,通过CameraX可以向应用增加相机的功能。在下列内容中,将介绍一个结合CameraX实现一个简单的拍照应用。本应用必须采用Android SDK 34。并通过该简单示例,了解传统View层次组件的UI组件如何与Compose组件结合实现移动应用界面的定制。

首先,新建一个项目,选择Empty Activity。
在这里插入图片描述

一、增加依赖使用CameraX

在新建项目的模块build.gradle.kt中增加依赖如下所示:

	//CameraX
    val camerax_version = "1.3.0-beta04"
    implementation("androidx.camera:camera-core:$camerax_version")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    implementation("androidx.camera:camera-video:${camerax_version}")

    implementation("androidx.camera:camera-view:${camerax_version}" )
    implementation("androidx.camera:camera-extensions:${camerax_version}")
    
    //accompanist处理权限依赖库
    val accompanist_version = "0.31.2-alpha"
    implementation("com.google.accompanist:accompanist-permissions:$accompanist_version")

二、申请权限

1.AndroidManifest.xml配置使用照相机特性和权限


<uses-feature android:name="android.hardware.camera"   
              android:required="false" />     
<uses-permission   
              android:name="android.permission.CAMERA" />

2.申请使用拍照的权限

使用rememberPermissionState函数申请权限,该函数的参数为permission,表示需要申请的权限。rememberPermissionState函数的返回值为PermissionState类型,表示获取权限的状态。

@OptIn(ExperimentalPermissionsApi::class) 
@Preview 
@Composable 
fun CameraScreen() {    
	val cameraPermissionState = rememberPermissionState(permission = android.Manifest.permission.CAMERA)    
	LaunchedEffect(key1 = Unit){
       if(!cameraPermissionState.status.isGranted && !cameraPermissionState.status.shouldShowRationale){
           cameraPermissionState.launchPermissionRequest()
       }    
	}

   if(cameraPermissionState.status.isGranted){
       //接受拍照的授权
       }
 
  else{
       //未授权,显示未授权的界面    
       } 
 }

在上述代码中,因为通过cameraPermissionState.launchPermissionRequest()请求申请照相机权限是一个异步过程,因此,将这个请求的处理放置在LaunchedEffect代码段内。

三、定义未授权的界面

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun NoPermissionScreen(cameraPermissionState:  PermissionState) {
    Column(horizontalAlignment = Alignment.CenterHorizontally){
        val message = if(cameraPermissionState.status.shouldShowRationale){
            "未获取照相机权限导致无法使用照相机功能"
        }else{
            "请授权照相机的权限"
        }
        Text(message)
        Spacer(modifier = Modifier.height(8.dp))
        Button(onClick={
            cameraPermissionState.launchPermissionRequest()
        }){
            Text("请求授权")
        }
    }
}

通过定义按钮的点击动作,请求调用cameraPermissionState.launchPermissionRequest()再次申请权限。运行界面如下图所示:
在这里插入图片描述

四、结合PreviewView实现预览相机画面

PreviewView,这是一种可以剪裁、缩放和旋转以确保正确显示的 传统的视图组件。
在这里插入图片描述
可以结合Preview将处于活动状态的相机中的图片预览会流式传输到 PreviewView 中的 Surface。因为是传统的View系统的组件,并不是Compose组件,因此需要在Compose界面中利用AndroidView来使用View系统的组件。

PreviewView中有些属性需要注意:

(1)缩放类型

相机预览视频分辨率常常与PreviewView 的尺寸不同,需要通过设置缩放类型scaleType来剪裁或添加遮幅式黑边使得预览画面来适应视图(保持原始宽高比)。

FIT_CENTER、FIT_START 和 FIT_END,用于添加遮幅式黑边。整个视频内容会调整(放大或缩小)为可在目标 PreviewView 中显示的最大尺寸。预览的内容会被完整显示。但是可能出现空白部分,每一帧的画面会与FIT_CENTER目标视图的中心、FIT_START起始或FIT_END结束位置对齐。CameraX 使用的默认缩放类型是 FILL_CENTER。
三种设置的实现效果如下图所示,图片来源https://developer.android.google.cn/static/images/training/camera/camerax/camera-preview/camera_preview_view_scale_type_fit.png?hl=zh-cn。
图片来源https://developer.android.google.cn/static/images/training/camera/camerax/camera-preview/camera_preview_view_scale_type_fit.png?hl=zh-cn

(2)渲染画面的实现模式

PreviewView 的属性implementationMode用来设置画面渲染的实现模式,实现模式主要有两种:

PERFORMANCE 是默认模式。PreviewView 会使用 SurfaceView 显示视频串流,但在某些情况下会回退为使用 TextureView。SurfaceView 具有专用的绘图界面,该对象更有可能通过内部硬件合成器实现硬件叠加层,尤其是当预览视频上面没有其他界面元素(如按钮)时。通过使用硬件叠加层进行渲染,视频帧会避开 GPU 路径,从而能降低平台功耗并缩短延迟时间。

COMPATIBLE 模式。在此模式下,PreviewView 会使用 TextureView;不同于 SurfaceView,该对象没有专用的绘图表面。因此,视频要通过混合渲染,才能显示。在这个额外的步骤中,应用可以执行额外的处理工作,例如不受限制地缩放和旋转视频。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CameraContent() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val cameraController = remember{LifecycleCameraController(context)}

    Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues: PaddingValues ->
        Box(modifier = Modifier.fillMaxSize()) {
            //在Compose中使用View系统中的PreviewView
            AndroidView(modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
                factory = { context ->
                    PreviewView(context).apply {
                        //设置布局宽度和高度占据全屏
                        layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
                        //设置背景颜色
                        setBackgroundColor(Color.BLACK)
                        //设置渲染的实现模式
                        implementationMode = PreviewView.ImplementationMode.COMPATIBLE
                        //设置缩放方式
                        scaleType = PreviewView.ScaleType.FILL_START
                    }.also{
                        it.controller = cameraController
                        cameraController.bindToLifecycle(lifecycleOwner)
                    }
                },
                onReset = {},
                onRelease = {
                    cameraController.unbind()
                }
            )
        }
    }
}

这时,重新修改CameraScreen,代码如下:

@OptIn(ExperimentalPermissionsApi::class) 
@Preview 
@Composable 
fun CameraScreen() {    
	val cameraPermissionState = rememberPermissionState(permission = android.Manifest.permission.CAMERA)    
	LaunchedEffect(key1 = Unit){
       if(!cameraPermissionState.status.isGranted && !cameraPermissionState.status.shouldShowRationale){
           cameraPermissionState.launchPermissionRequest()
       }    
	}

   if(cameraPermissionState.status.isGranted){
       //接受拍照的授权
        CameraContent()    
       }
 
  else{
       //未授权,显示未授权的界面    
       NoPermissionScreen(cameraPermissionState)
       } 
 }

测试一下上述的代码,模拟器运行效果如下图所示:
在这里插入图片描述
但是,从这个运行效果可以发现,它仅仅是显示相机预览的画面,功能有限。因此,需要增加其他的功能。

四、拍照的处理

CameraX提供了拍照的功能,通过调用takePicture来实现拍照。takePicture函数有两种形式:

  • takePicture(Executor, OnImageCapturedCallback):此方法为拍摄的图片提供内存缓冲区。

  • takePicture(OutputFileOptions, Executor,OnImageSavedCallback):此方法将拍摄的图片保存到提供的文件位置。 运行
    ImageCapture可自定义执行程序有两种类型:回调执行程序和 IO 执行程序。

下列代码展示了一个简单的拍照功能,通过调用takePicture(Executor, OnImageCapturedCallback)函数来实现。但是在下列代码中,拍下的图片并没有保存,仅仅是生成了一个Bitmap对象

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CameraContent() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val cameraController = remember{LifecycleCameraController(context)}
    //请求关联context的主线程的Excutor
    val cameraExecutor = ContextCompat.getMainExecutor(context)

    Scaffold(modifier = Modifier.fillMaxSize(),
        floatingActionButton = {
            FloatingActionButton(onClick = {
                cameraController.takePicture(cameraExecutor,object : ImageCapture.OnImageCapturedCallback(){
                    override fun onCaptureSuccess(image: ImageProxy) {
                        super.onCaptureSuccess(image)
                        //拍照得到图片
                        val cameraBitmap = image.toBitmap()
                    }
                    override fun onError(exception: ImageCaptureException) {
            			//拍照失败的处理
        			}
                })
            }) {
                Icon(modifier = Modifier.clip(CircleShape), 
                     painter = painterResource(id = R.mipmap.len),
                     contentDescription = "照相机")
            }
        },
        floatingActionButtonPosition = FabPosition.Center) { paddingValues: PaddingValues ->
        Box(modifier = Modifier.fillMaxSize()) {
            //在Compose中使用View系统中的PreviewView
            AndroidView(modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
                factory = { context ->
                    PreviewView(context).apply {
                        layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
                        //设置背景颜色
                        setBackgroundColor(Color.BLACK)
                        //设置渲染模式
                        implementationMode = PreviewView.ImplementationMode.COMPATIBLE
                        //设置缩放方式
                        scaleType = PreviewView.ScaleType.FILL_START
                    }.also{
                        it.controller = cameraController
                        cameraController.bindToLifecycle(lifecycleOwner)
                    }
                },
                onReset = {},
                onRelease = {
                    cameraController.unbind()
                }
            )
        }
    }
}

运行效果在模拟器中如下图所示:
在这里插入图片描述
可以注意到,上述代码虽然可以将拍下的图片生成一个Bitmap对象,但是并没有将这个图片保存到手机中。因此可以考虑通过调用 takePicture(OutputFileOptions, Executor,OnImageSavedCallback)函数来实现拍照并保存图片的功能。
为此,做如下的处理:

(1)自定义一个函数capturePicture:执行拍照功能,并将拍下的图片保存到文件中。
(2)修改CameraContent组合函数,调用capturePicture函数实现拍照并保存照片的功能。

fun capturePicture(cameraController:LifecycleCameraController,cameraExecutor:Executor){
	//创建文件名以img为开始,扩展名为jpg的临时文件
    val file = File.createTempFile("img",".jpg")
    //配置最新拍下照片文件的位置和元数据
    val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
    //定义拍照,cameraExecutor处理拍照后的回调 object:ImageCapture.OnImageSavedCallback创建匿名对象对捕获图片保存
    cameraController.takePicture(outputFileOptions,cameraExecutor,object:ImageCapture.OnImageSavedCallback{
        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
            //拍照的图片成功保存的处理
            Log.d("CAMERA_APP","成功保存照片在:${outputFileResults.savedUri}")
        }
     
        override fun onError(exception: ImageCaptureException) {
            //拍照的图片保存失败的处理
            Log.d("CAMERA_APP","保存照片失败")
        }

    })
}

修改CameraContent函数,修改拍照的处理:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CameraContent() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val cameraController = remember{LifecycleCameraController(context)}
    //请求关联context的主线程的Excutor
    val cameraExecutor = ContextCompat.getMainExecutor(context)

    Scaffold(modifier = Modifier.fillMaxSize(),
        floatingActionButton = {
            FloatingActionButton(onClick = {
                //处理拍照
                capturePicture(cameraController,cameraExecutor)
            }) {
                Icon(modifier = Modifier.clip(CircleShape).size(24.dp,24.dp),
                    painter = painterResource(id = R.mipmap.len),contentDescription = "照相机")
            }
        },
        floatingActionButtonPosition = FabPosition.Center) { paddingValues: PaddingValues ->
        Box(modifier = Modifier.fillMaxSize()) {
            //在Compose中使用View系统中的PreviewView
            AndroidView(modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
                factory = { context ->
                    PreviewView(context).apply {
                        layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
                        //设置背景颜色
                        setBackgroundColor(Color.BLACK)
                        //设置渲染模式
                        implementationMode = PreviewView.ImplementationMode.COMPATIBLE
                        //设置缩放方式
                        scaleType = PreviewView.ScaleType.FILL_START
                    }.also{
                        it.controller = cameraController
                        cameraController.bindToLifecycle(lifecycleOwner)
                    }
                },
                onReset = {},
                onRelease = {
                    cameraController.unbind()
                }
            )
        }
    }
}

运行界面如上图所示。执行多次拍照后,启动Android Studio的Device Explorer,在data/data目录中找到创建的项目模块包名:
在这里插入图片描述

在包名的下级目录cache中可以发现多次拍照的图片,也可以根据跟踪日志发现保存图片文件的路径,如下图所示。
在这里插入图片描述

参考文献

(1) ImageCapture.Builder https://developer.android.google.cn/reference/androidx/camera/core/ImageCapture.Builder#setIoExecutor(java.util.concurrent.Executor)
(2)CameraX概览
https://developer.android.google.cn/training/camerax?hl=zh-cn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1141625.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【pwn入门】使用python打二进制

声明 本文是B站你想有多PWN学习的笔记&#xff0c;包含一些视频外的扩展知识。 程序网络交互初体验 将程序部署成可以远程访问的 socat tcp-l:8877,fork exec:./question_1_plus_x64,reuseaddr通过网络访问程序 nc 127.0.0.1 8877攻击脚本 import socket import telnetli…

PHP简单实现预定义钩子和自定义钩子

在PHP中&#xff0c;钩子&#xff08;Hooks&#xff09;是一种机制&#xff0c;允许开发人员在特定的时机插入自定义代码。通过使用钩子&#xff0c;开发人员可以在应用程序的特定事件发生时执行自定义的功能或逻辑 钩子有两种类型&#xff1a;预定义钩子和自定义钩子。 预定…

shell的执行流控制

目录 1.for语句 2.条件语句 while...do语句 until...do 语句 if...then...elif...then...else...fi 语句 3.case语句 4.expect 5.break,continue,exit 1.for语句 作用&#xff1a;为循环执行动作 for语句结构 for //定义变量 do //使用变量&#xff0…

系列十八、请描述下bean的生命周期

一、概述 bean的生命周期是指bean从创建到销毁的整个过程。 二、生命周期 bean的生命周期是指bean从创建到销毁的整个过程&#xff0c;大致可以分为如下四个过程&#xff1a; 2.1、实例化 实例化可以通过如下几种方式完成&#xff1a;&#xff08;参考系列十五&#xff09…

区块链物联网中基于属性的私有数据共享与脚本驱动的可编程密文和分散密钥管理

Attribute-Based Private Data Sharing With Script-Driven Programmable Ciphertext and Decentralized Key Management in Blockchain Internet of Things 密钥生成算法 第 1 步&#xff1a;对于属性集A 的用户IDk&#xff0c;他首先将属性集A发送给Pi并且计算 &#xff0c…

MySQL大揭秘:深入探索Linux数据库管理

1 数据库的基本介绍 1.1 数据库的基本介绍 数据库的本质就是高级的excle表格。常见的数据库有&#xff1a;MySQL、Oracle 、sqlservermariadb就是MySQL的一种。数据库常用的名词 1.字段 &#xff1a;表格中的表头,每一列就是一个字段 2.表 &#xff1a;表格 3.库 &#xff1a…

Corel Products Keygen-X-FORCE 2023(Corel会声会影2023注册机)

Corel All Products Universal Keygens通用注册机是一款非常实用的激活工具&#xff0c;专门用于激活Corel全系列产品。尤其是被广泛使用的CorelDRAW作图软件和Corel VideoStudio会声会影视频编辑处理软件。小编也是一直关注由X-Force团队制作的注册机&#xff0c;目前已更新至…

fl studio21水果新手要不要购买?

FL Studio&#xff0c;一款深受音乐制作人和作曲家欢迎的数字音频工作站&#xff08;DAW&#xff09;&#xff0c;不仅因其强大的功能&#xff0c;也因其在行业内的长久盛名而成为许多专业人士的首选。作为一个全方位的音乐制作软件&#xff0c;它能够满足用户在录音、编曲、混…

p5.js画布操作实战:创建,绑定指定元素,动态调整大小,隐藏滚动条,删除画布

文章简介 之前在 《p5.js 光速入门》 里粗略讲过一下如何使用 p5.js 创建画布。 这次要介绍几个 p5.js 提供的画布相关的方法。 创建画布时的相关配置。让画布绑定指定元素。重置画布大小。删除画布。 学习本文前你需要具备一点 p5.js 的知识&#xff0c;想了解的请查看 《p…

Android---StartActivity启动过程

在手机桌面应用中点击某一个 icon 之后&#xff0c;最终是通过 startActivity 去打开某一个 Activity 页面。我们知道&#xff0c;Android 中的一个 APP 就相当于一个进程。所以&#xff0c;startActivity 操作中还需要判断&#xff0c;目标 Activity 的进程是否已经创建。如果…

【洛谷 P3654】First Step (ファーストステップ) 题解(模拟+循环枚举)

First Step (ファーストステップ) 题目背景 知らないことばかりなにもかもが&#xff08;どうしたらいいの&#xff1f;&#xff09; 一切的一切 尽是充满了未知数&#xff08;该如何是好&#xff09; それでも期待で足が軽いよ&#xff08;ジャンプだ&#xff01;&#xff09…

好的FPGA编码风格(2)--多参考设计软件的语言模板(Language Templates)

什么是语言模板&#xff1f; 不论是Xilinx的Vivado&#xff0c;还是Altera的Quartus II&#xff0c;都为开发者提供了一系列Verilog、SystemVerilog、VHDL、TCL、原语、XDC约束等相关的语言模板&#xff08;Language Templates&#xff09;。 在Vivado软件中&#xff0c;按顺序…

C# Socket通信从入门到精通(4)——多个异步TCP客户端C#代码实现

前言: 在之前的文章C# Socket通信从入门到精通(3)——单个异步TCP客户端C#代码实现我介绍了单个异步Tcp客户端的c#代码实现,但是有的时候,我们需要连接多个服务器,并且对于每个服务器,我们都有一些比如异步连接、异步发送、异步接收的操作,那么这时候我们使用之前单个…

文件权限详解

一、文件类型 ll指令查看文件详细信息中&#xff0c;第一列就是文件类型。 常见的文件类型有&#xff1a; 1、 - &#xff1a;普通文件 &#xff08;文本、源代码、图片、视频、可执行&#xff09; 2、 d &#xff1a;目录文件 3、b &#xff1a;块设备 4、c &#xff1…

CAS 机制的实现原理分析

在 synchronized 中很多地方都用到了CAS机制&#xff0c;它的叫法有很多&#xff0c;比如CompareAndSwap、CompareAndExchange、CompareAndSet&#xff0c;它是一个能够进行比较和替换的方法&#xff0c;这个方法能够在多线程环境下保证对一个共享变量进行修改时的原子性不变。…

【MyBatis Plus】深入探索 MyBatis Plus 的条件构造器,自定义 SQL语句,Service 接口的实现

文章目录 前言一、条件构造器1.1 什么是条件构造器1.2 QueryWrapper1.3 UpdateWrapper1.4 LambdaWrapper 二、自定义 SQL 语句2.1 自定义 SQL 的基本用法2.2 自定义 SQL 实现多表查询 三、Service 接口3.1 对 Service 接口的认识3.2 实现 Service 接口3.3 实现增删改查功能3.4 …

20231027 基于STM32mp157a 的内核与应用层通过子系统控制led灯,以及计时器功能

1.基于GPIO子系统编写LED驱动&#xff0c;编写应用程序进行测试 stm32mp157a-fsmp1a.dts 内核程序&#xff1a;ledk.c #include <linux/init.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/de…

C++进阶语法——OOP(面向对象)【学习笔记(四)】

文章目录 1、C OOP⾯向对象开发1.1 类&#xff08;classes&#xff09;和对象&#xff08;objects&#xff09;1.2 public、private、protected访问权限1.3 实现成员⽅法1.4 构造函数&#xff08;constructor&#xff09;和 析构函数&#xff08;destructor&#xff09;1.4.1 构…

kubectl资源管理命令-陈述式

目录 一、陈述式对象管理 1、基本概念 2、基础命令使用 3、基本信息查看&#xff08;kubectl get&#xff09; 4、增删等操作 5、登录pod中的容器 6、扩容缩容pod控制器的pod 7、删除副本控制器 二、创建项目实例 1、创建 kubectl create命令 2、发布 kubectl …

Spring Cloud Gateway + Knife4j 4.3 实现微服务网关聚合接口文档

目录 前言Spring Cloud 整合 Knife4jpom.xmlapplication.ymlSwaggerConfig.java访问单服务接口文档 Spring Cloud Gateway 网关聚合pom.xmlapplication.yml访问网关聚合接口文档 接口测试**登录认证**获取登录用户信息 结语源码 前言 youlai-mall 开源微服务商城新版本基于 Sp…