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

本文主要是介绍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。
图片来源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实现照相机画面预览和照相功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/290007

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo