Android笔记(八):基于CameraX库结合Compose和传统视图组件PreviewView实现照相机画面预览和照相功能
CameraX是JetPack库之一,通过CameraX可以向应用增加相机的功能。在下列内容中,将介绍一个结合CameraX实现一个简单的拍照应用。本应用必须采用Android SDK 34。并通过该简单示例,了解传统View层次组件的UI组件如何与Compose组件结合实现移动应用界面的定制。
首先,新建一个项目,选择Empty Activity。
一、增加依赖使用CameraX
在新建项目的模块build.gradle.kt中增加依赖如下所示:
//CameraXval 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。
(2)渲染画面的实现模式
PreviewView 的属性implementationMode用来设置画面渲染的实现模式,实现模式主要有两种:
PERFORMANCE 是默认模式。PreviewView 会使用 SurfaceView 显示视频串流,但在某些情况下会回退为使用 TextureView。SurfaceView 具有专用的绘图界面,该对象更有可能通过内部硬件合成器实现硬件叠加层,尤其是当预览视频上面没有其他界面元素(如按钮)时。通过使用硬件叠加层进行渲染,视频帧会避开 GPU 路径,从而能降低平台功耗并缩短延迟时间。
COMPATIBLE 模式。在此模式下,PreviewView 会使用 TextureView;不同于 SurfaceView,该对象没有专用的绘图表面。因此,视频要通过混合渲染,才能显示。在这个额外的步骤中,应用可以执行额外的处理工作,例如不受限制地缩放和旋转视频。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CameraContent() {val context = LocalContext.currentval lifecycleOwner = LocalLifecycleOwner.currentval cameraController = remember{LifecycleCameraController(context)}Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues: PaddingValues ->Box(modifier = Modifier.fillMaxSize()) {//在Compose中使用View系统中的PreviewViewAndroidView(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 = cameraControllercameraController.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.currentval lifecycleOwner = LocalLifecycleOwner.currentval cameraController = remember{LifecycleCameraController(context)}//请求关联context的主线程的Excutorval 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系统中的PreviewViewAndroidView(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 = cameraControllercameraController.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.currentval lifecycleOwner = LocalLifecycleOwner.currentval cameraController = remember{LifecycleCameraController(context)}//请求关联context的主线程的Excutorval 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系统中的PreviewViewAndroidView(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 = cameraControllercameraController.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
相关文章:

Android笔记(八):基于CameraX库结合Compose和传统视图组件PreviewView实现照相机画面预览和照相功能
CameraX是JetPack库之一,通过CameraX可以向应用增加相机的功能。在下列内容中,将介绍一个结合CameraX实现一个简单的拍照应用。本应用必须采用Android SDK 34。并通过该简单示例,了解传统View层次组件的UI组件如何与Compose组件结合实现移动应…...

【每日一题Day361】LC2558从数量最多的堆取走礼物 | 大顶堆
从数量最多的堆取走礼物【LC2558】 给你一个整数数组 gifts ,表示各堆礼物的数量。每一秒,你需要执行以下操作: 选择礼物数量最多的那一堆。如果不止一堆都符合礼物数量最多,从中选择任一堆即可。选中的那一堆留下平方根数量的礼物…...

【psychopy】【脑与认知科学】认知过程中的面孔识别加工
目录 实验描述 实验思路 python实现 实验描述 现有的文献认为,人们对倒置的面孔、模糊的面孔等可能会出现加工时长增加、准确率下降的问题,现请你设计一个相关实验,判断不同的面孔是否会出现上述现象。请按照认知科学要求,画…...

File类的常用API
判断文件类型 public boolean isDirectory() public boolean isFile() 获取文件信息 public boolean exists() public String getAbsolutePath() public String getPath() 返回创建文件对象时传入的抽象路径的字符串形式 public String getName() public long lastModi…...

02【Git分支的使用、Git回退、还原】
上一篇:01【Git的基本命令、底层命令、命令原理】 下一篇:03【Git的协同开发、TortoiseGit、IDEA的操作Git】 文章目录 02【Git分支的使用、Git回退、还原】一、分支1.1 分支概述1.1.1 Git分支简介1.1.2 Git分支原理 1.2 创建分支1.2.1 创建普通分支1.…...

Qt文件 I/O 操作
一.QFile 文件读取 QIODevice::ReadOnly QString filePath"/home/chenlang/RepUtils/1.txt"; QFile file(filePath); 1.逐行读取 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {QTextStream in(&file);while (!in.atEnd()) {QString line i…...

Springboot 使用JavaMailSender发送邮件 + Excel附件
目录 1.生成Excel表格 1.依赖设置 2.代码: 2.邮件发送 1.邮件发送功能实现-带附件 2.踩过的坑 1.附件名中文乱码问题 3.参考文章: 需求描述:项目审批完毕后,需要发送邮件通知相关人员,并且要附带数据库表生成的…...

软件工程——期末复习知识点汇总
本帖的资料来源于某国内顶流高校的期末考试资料,仅包含核心的简答题,大家结合个人情况,按需复习~ 总的来说,大层面重点包括如下几个方面: 软件过程需求工程 设计工程软件测试软件项目管理软件过程管理 1.掌握软件生命…...

postgresSQL 数据库本地创建表空间读取本地备份tar文件与SQL文件
使用pgAdmin4,你安装PG得文件夹****/16/paAdmin 4 /runtime/pgAdmin4.exe 第一步:找到Tablespaces 第二步:创建表空间名称 第三步:指向数据文件 第四步:找到Databases,创建表空间 第五步:输入数…...

Elasticsearch跨集群检索配置
跨集群检索字面意思,同一个检索语句,可以检索到多个ES集群中的数据,ES集群默认是支持跨集群检索的,只需要动态的增加入节点即可,下面跟我一起来体验下ES的跨集群检索的魅力。 Elasticsearch 跨集群检索推荐的是不同集群…...

第九章 软件BUG和管理
一、学习目的与要求 软件测试的目的就是为了发现软件BUG。通过本章的学习,应了解软件BUG的产生和影响,掌握软件开发过程中产生的BUG种类,掌握使BUG重现的技术,了解软件BUG报告单应该包括的主要内容及软件BUG的管理流程。 二、考核…...

大厂面试题-Java并发编程基础篇(二)
目录 一、wait和notify这个为什么要在synchronized代码块中? 二、ThreadLocal是什么?它的实现原理呢? 三、基于数组的阻塞队列ArrayBlockingQueue原理 四、怎么理解线程安全? 五、请简述一下伪共享的概念以及如何避免 六、什…...

测绘屠夫报表系统V1.0.0-beta
1. 简介 测绘屠夫报表系统,能够根据变形监测数据:水准、平面、轴力、倾斜等数据,生成对应的报表,生成报表如下图。如需进一步了解,可以加QQ:3339745885。视频教程可以在bilibili观看。 2. 软件主界面 3. …...

『力扣刷题本』:移除链表元素
一、题目 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5]示例 2: 输入&a…...

图像特征Vol.1:计算机视觉特征度量|第一弹:【纹理区域特征】
目录 一、前言二、纹理区域度量2.1:边缘特征度量2.2:互相关和自相关特征2.3:频谱方法—傅里叶谱2.4:灰度共生矩阵(GLCM)2.5:Laws纹理特征2.6:局部二值模式(LBP) 一、前言 …...

day01:数据库DDL
一:基础概念 数据库:存储数据的仓库,数据是有组织的进行存储 数据库管理系统:操纵和管理数据库的大型软件 SQL:操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 关系图 二:数据模型 关系型数据库:建…...

9、定义错误页
在layouts目录下新建error.vue,可以通过layout函数使用布局文件,通过props: [“error”]能拿到错误信息对象。 <template><div>{{ error.statusCode }}: {{ error.message }}</div> </template><script> export default {…...

有关多线程环境下的Volatile、lock、Interlocked和Synchronized们
📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅…...

spring boot利用redis作为缓存
一、缓存介绍 在 Spring Boot 中,可以使用 Spring Cache abstraction 来实现缓存功能。Spring Cache abstraction 是 Spring 框架提供的一个抽象层,它对底层缓存实现(如 Redis、Ehcache、Caffeine 等)进行了封装,使得在…...

Android Studio 查看Framework源码
1、背景 安卓系统源码量很庞大,选择好的开发工具和方式去开发可以提升开发效率,常用的开发工具有Source Insight 、Visual Studio Code、Android Studio,vscode适合C和C代码开发,java层代码无法跳转和提示,因此&#…...

FileInputStream文件字节输入流
一.概念 以内存为基准,把磁盘文件中的数据以字节形式读入内存中 二.构造器 public FileInputStream(File file) public FileInputStream(String pathname) 这两个都是创建字节输入流管道与源文件接通 三.方法 public int read() :每次读取一个字节返回,如…...

【Qt】窗口和对话框区别、主窗口和二级窗口区别、QMainWindow和QDialog区别
窗口和对话框(Window and Dialog Widgets) 未嵌入在父界面中的界面称为窗口。(通常,窗口具有边框和标题栏,尽管也可以使用合适的窗口标志创建没有此类标志的窗口)。 在Qt中,QMainWindow和QDial…...

Python参数种类介绍
Python参数种类介绍 相比于一些其他编程语言,Python提供了更多的参数种类选项。这是Python的一大特点,使用不同的参数类型,可以提高函数的可读性和可维护性。例如,使用关键字参数可以使函数调用更加清晰,不需要记住参数…...

react事件机制
React 事件机制 React的事件机制是React框架中非常重要的一部分,用于处理用户交互和用户界面上的事件。React的事件机制在底层使用了虚拟DOM以及合成事件来提高性能和跨浏览器兼容性。以下是关于React事件机制的详细信息: 合成事件(Syntheti…...

JAVA删除excel指定列
首先POI没有提供删除列的API,所以就需要用其他的方式实现。 在 java - Apache POI xls column Remove - Stack Overflow 这里找到了实现方式: 先将该列所有值都清空,然后将该列之后的所有列往前移动。 下面的工具类中 deleteColumns(Inpu…...

Netty编码器和解码器
文章目录 一、Decoder原理与实践1、ByteToMessageDecoder解码器2、自定义整数解码器1)常规方式2)ReplayingDecoder解码器 3、分包解码器3、MessageToMessageDecoder解码器 二、Netty内置的Decoder1、LineBasedFrameDecoder解码器2、DelimiterBasedFrameD…...

大语言模型(LLM)综述(三):大语言模型预训练的进展
A Survey of Large Language Models 前言4. PRE-TRAINING4.1数据收集4.1.1 数据源4.1.2 数据预处理4.1.3 预训练数据对LLM的影响 4.2 模型架构4.2.1 典型架构4.2.2 详细配置4.2.3 预训练任务4.2.4 解码策略4.2.5 总结和讨论 4.3 模型训练4.3.1 优化设置4.3.2 可扩展的训练技术 …...

如何在Node.js中使用环境变量或命令行参数来设置HTTP爬虫ip?
首先,定义问题:在 Node.js 应用程序中,我们可以通过环境变量或命令行参数来设置HTTP爬虫ip,以便在发送请求时使用这些HTTP爬虫ip。 亲身经验:我曾经需要为一个项目设置HTTP爬虫ip,以便在发送请求时使用这些…...

VMware打开共享虚拟机后找不到/mnt/hgfs/文件夹,以及不能拖拽/复制粘贴等操作,ubuntu不能安装VMware tools
问题原因 我的问题出现原因是,安装ubuntn虚拟机的时候VMware tools没有安装好,需要重新安装,但安装选项是暗的,不能操作。 类似这种情况,虚拟机开启时也是,因为我虚拟机已经装好了,开启时是亮…...

pytorch 入门 (五)案例三:乳腺癌识别识别-VGG16实现
本文为🔗小白入门Pytorch内部限免文章 🍨 本文为🔗小白入门Pytorch中的学习记录博客🍦 参考文章:【小白入门Pytorch】乳腺癌识别🍖 原作者:K同学啊 在本案例中,我将带大家探索一下深…...