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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【代码思路】2023mathorcup 大数据数学建模B题 电商零售商家需求预测及库存优化问题

各位同学们好&#xff0c;我们之前已经发布了第一问的思路视频&#xff0c;然后我们现在会详细的进行代码和结果的一个讲解&#xff0c;然后同时我们之后还会录制其他小问更详细的思路以及代码的手把手教学。 大家我们先看一下代码这一部分&#xff0c;我们采用的软件是Jupyte…

tftp服务的搭建

TFTP服务的搭建 1 先更新一下apt包 sudo apt-get update2 服务器端(虚拟机上)安装 TFTP相关软件 sudo apt-get install xinetd tftp tftpd -y3 创建TFTP共享目录 mkdir tftp_sharetftp_shaer的路径是/home/cwz/tftp_share 3.1 修改共享目录的权限 sudo chmod -R 777 tftp…

python操作MySQL、SQL注入问题、视图、触发器、事务、存储过程、函数、流程控制、索引(重点)

python操作MySQL(重要) SQL的由来&#xff1a; MySQL本身就是一款C/S架构&#xff0c;有服务端、有客户端&#xff0c;自身带了有客户端&#xff1a;mysql.exe python这门语言成为了MySQL的客户端(对于一个服务端来说&#xff0c;客户端可以有很多) 操作步骤&#xff1a; …

是谁在造谣杭州取消直播带货?

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 这个世道&#xff0c;谣言的传播成本很低&#xff1a;比如“杭州禁止直播带货”这件事。 就在今天若水跟我说&#xff1a;“杭州禁止直播是谣言了&#xff0c;辟谣了”让我也赶紧隐藏或删除内容&…

【触想智能】工控一体机与5G物联网技术结合是未来发展趋势

工控一体机也叫工业电脑一体机&#xff0c;是工业应用非常重要的一种产品。目前&#xff0c;工控一体机在工业领域的应用已经非常普及&#xff0c;在繁忙的生产车间、数字化机床、自助服务终端设备等场景中&#xff0c;我们都有看到它的身影。 工控一体机应用的普及已经潜移默化…

InstructionGPT

之前是写在[Instruction-tuning&#xff08;指令微调&#xff09;]里的&#xff0c;抽出来单独讲一下。 基本原理 在做下游的任务时&#xff0c;我们发现GPT-3有很强大的能力&#xff0c;但是只要人类说的话不属于GPT-3的范式&#xff0c;他几乎无法理解。例如&#xff0c;我们…

华为---DHCP中继代理简介及示例配置

DHCP中继代理简介 IP动态获取过程中&#xff0c;客户端&#xff08;DHCP Client&#xff09;总是以广播&#xff08;广播帧及广播IP报文&#xff09;方式来发送DHCPDISCOVER和DHCPREQUEST消息的。如果服务器&#xff08;DHCP Server&#xff09;和 客户端不在同一个二层网络(二…

通过el-tree 懒加载树,创建国家地区四级树

全国四级行政地区树数据库sql下载路径&#xff1a;【免费】全国四级地区(省市县)数据表sql资源-CSDN文库https://download.csdn.net/download/weixin_51722520/88469807?spm1001.2014.3001.5503 我在后台获取地区信息添加了限制&#xff0c;只获取parentid为当前的地…

Gloss优化

Gloss优化&#xff0c;Route – Gloss – Parameters .清除不必要的线和过孔&#xff0c;圆滑线&#xff0c;焊盘中间的线&#xff0c;把转角变成圆弧&#xff0c;自动布线总会产生一些布线效果不好、多余过孔等问题。此时可以利用allegro提供的Gloss命令对设计进行优化和调整&…

ES6新增循环对象的四种方法(通俗易懂)

在我们ES6之前&#xff0c;我们一般都是用for…in来循环对象&#xff0c;现在我们ES6为我们新增了几种方法&#xff0c;让我为大家介绍一下吧&#xff01; 1.Object.keys() 静态方法返回一个由给定对象自身的可枚举的字符串键属性名组成的数组 const obj {name:"zs&quo…

项目部署Linux步骤

1、最小化安装centos7-环境准备 安装epel-release 安装epel-release&#xff0c;因为有些rpm包在官方库中找不到。前提是保证可以联网 yum install -y epel-release 修改IP net-tools net-tool&#xff1a;工具包集合&#xff0c;包含ifconfig等命令 yum install -y net-…

Games104现代游戏引擎笔记 网络游戏进阶架构

Character Movement Replication 角色位移同步 玩家2的视角看玩家1的移动是起伏一截一截&#xff0c;并且滞后的 interpolation&#xff1a;内插值&#xff0c;在两个旧的但已知的状态计算 extrapolation&#xff1a;外插值&#xff0c;本质是预测 内插值&#xff1a;但网络随着…

零基础Linux_22(多线程)线程控制和和C++的多线程和笔试选择题

目录 1. 线程控制 1.1 线程创建(pthread_create) 1.2 线程结束(pthread_exit) 1.3 线程等待(pthread_join) 1.4 线程取消(pthread_cancel结束) 1.5 线程tid(pthread_self()) 1.6 线程局部存储(__thread) 1.7 线程分离(pthread_detach) 2. C的多线程 3. 笔试选择题 答…

双十一某宝、某东活动脚本

一、前言 双十一马上就快开始了&#xff0c;各大网购平台的优惠活动开展的如火如荼&#xff0c;羊毛党们也是摩拳擦掌&#xff0c;蠢蠢欲动。为了提高效率&#xff0c;自动化脚本应运而生&#xff0c;今天&#xff0c;小编为大家带来的就是这么三款自动化点击软件。主要是针对…

软考系统架构师知识点集锦五:系统可靠性分析与设计

一、考情分析 二、考点精讲 2.1相关基本概念 可靠性:可靠性是软件系统在应用或系统错误面前&#xff0c;在意外或错误使用的情况下维持软件系统的功能特性的基本能力。 可用性:可用性是系统能够正常运行的时间比例。 软件可靠性 ≠ 硬件可靠性 软硬件对比 复杂性:软件复杂性比…

[已解决]安装的明明是pytorch-gpu,但是condalist却显示cpu版本,而且torch.cuda.is_available 也是flase

问题; 安装了gpu版本的pytorch&#xff0c;但是显示的torch.cuda.is_available(&#xff09;却是flase。 conda list查看 版本显示只有cpuonly 在网上找了半天&#xff0c;也没有解决办法。 仔细看了一下&#xff0c;发现&#xff0c;有个单独的包叫cpuonly&#xff0c;不知道…

JAVAEE初阶相关内容第十六弹--网络编程

写在前 这一节的内容首先是对十五弹&#xff08;UDP回显服务器&#xff09;进行简单的改进&#xff0c;在这基础上开始介绍TCP流套接字编程。 目录 写在前 1.改进回显服务器 1.1完整代码实现 1.2运行输出结果 2.TCP流套接字编程 2.1ServerSocketAPI 2.2SocketAPI 3.TC…

小知识(6) el-table表格选中行和回显行(vue3)

el-table表格选中行和回显行 官方文档说明 https://element-plus.org/zh-CN/component/table.html#table-%E6%96%B9%E6%B3%95 环境&#xff1a;vue3element-plus 选中行selection <el-table ref"baseTableRef" row-key"id" border :selection"tr…

【OpenCV实现平滑图像形态学变化】

文章目录 概要目标腐蚀膨胀开运算结构元素&#xff08;内核&#xff09;小结 概要 形态学变化是一组简单的图像操作&#xff0c;主要用于处理二值图像&#xff0c;即只包含黑和白两种颜色的图像。这些操作通常需要两个输入&#xff0c;原始图像和一个内核&#xff08;kernel&a…

第11期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…