Android实战开发:自定义照相机

2024-06-04 22:18

本文主要是介绍Android实战开发:自定义照相机,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

参考资料:

SurfaceView:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html
android.hardware.Camera2:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0428/2811.html
Camera默认捕获画面横向: http://blog.sina.com.cn/s/blog_777c69930100y7nv.html

感谢以上大神们的的无私分享!


之前在公司写了一个自定义CameraView,年代久远,回头看代码时居然有点看不懂了。。。
真是好记性不如烂笔头啊~

趁着年底不忙有时间,再次重写下Camera,话不多说,开始撸代码。


1.权限


首先需要在AndroidManifest文件中配置权限:

    <!-- 权限 --><!-- 摄像头权限 --><uses-permission android:name="android.permission.CAMERA" /><!-- 闪光灯权限 --><uses-permission android:name="android.permission.FLASHLIGHT" /><!-- 在SDCard中创建与删除文件权限 --><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /><!-- 写入SD卡权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 功能 --><!-- 摄像头功能 --><uses-feature android:name="android.hardware.camera" /><!-- 摄像头自动对焦功能 --><uses-feature android:name="android.hardware.camera.autofocus" />

2.预览图像


2.1 SurfaceView和SurfaceHolder


关于SurfaceView的详细介绍可以点这里:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html


2.1.1 SurfaceView


因为我们需要预览照相机中的图像,而这个图像又是动态变化的,所以必须用到这个SurfaceView。
SurfaceView继承自View,能够在非UI线程中在屏幕上绘图,所以我们可以在预览图像的同时进行一些别的操作。
我们把它写在布局文件中,使用findViewById获得它的实例即可。

    mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);

2.1.2 SurfaceHolder


SurfaceHolder相当于是SurfaceView的控制器,用来操纵surface。处理它的Canvas上画的效果和动画,控制表面,大小,像素等。

    mSurfaceHolder = mSurfaceView.getHolder();//通过getHolder方法获取实例

实现SurfaceView需要实现SurfaceHolder.Callback接口,可以自定义一个类继承SurfaceView并实现这个接口,也可以让Activity直接实现这个接口,我们这里使用第二种。

实现SurfaceHolder.Callback接口需要实现三个方法:

    public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback {...mSurfaceHolder.addCallback(this);...@Overridepublic void surfaceCreated(SurfaceHolder holder) {//创建时触发,surfaceView生命周期的开始,在这里打开相机}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//surface的大小发生改变时触发,在这里预览图像}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {//销毁时触发,surfaceView生命周期的结束,在这里关闭相机}}

有了预览图像的容器,下面就真正开始使用Camera了。


2.2 Camera


2.2.1 import注意事项


导入包的时候注意是android.hardware.Camera,而不是android.graphics.Camera,不要搞错了;
hardware中的Camera是控制设备摄像头的,graphics中的Camera是对图像进行处理的。

PS:在android5.0以上,android.hardware.Camera已过时,推荐使用的是android.hardware.Camera2类;但由于Camera2类不向下兼容,而且目前安卓手机5.0以上的不多,所以我们还是使用过时的android.hardware.Camera。

关于Camera2类的详情,点这里:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0428/2811.html
感谢 泡在网上的日子 的无私分享~


2.2.2 生成预览图像


在重写的surfaceCreated方法中初始化camera,并开启预览

    @Overridepublic void surfaceCreated(SurfaceHolder holder) {mCamera = Camera.open();//使用静态方法open初始化camera对象,默认打开的是后置摄像头try {mCamera.setPreviewDisplay(mSurfaceHolder);//设置在surfaceView上显示预览mCamera.startPreview();//开始预览} catch (IOException e) {//在异常处理里释放camera并置为nullmCamera.release();mCamera = null;e.printStackTrace();}}

可以直接把预览写在surfaceCreated里,也可以写在surfaceChanged里

    @Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//也可以把预览写在这里}

在surfaceView被销毁时,停止预览并释放camera对象并置为null

    @Overridepublic void surfaceDestroyed(SurfaceHolder holder) {//停止预览并释放camera对象并置为nullmCamera.stopPreview();mCamera.release();mCamera = null;}

这时候运行程序,点击同意调用摄像头,我们会发现有图像了,
可是,为什么是歪的?
。。。whta the hell。。。


2.2.3 设置预览方向


之所以是歪的,是因为摄像头默认捕获的画面byte[]是根据横向来的,而我们的应用是竖向的,
解决办法是调用setDisplayOrientation来设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示。
详情点这里:http://blog.sina.com.cn/s/blog_777c69930100y7nv.html

所以我们只要在预览前调用下setDisplayOrientation这个方法就好了

    @Overridepublic void surfaceCreated(SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(mSurfaceHolder);//设置预览偏移90度,一般的设备都是90,但某些设备会偏移180mCamera.setDisplayOrientation(90);mCamera.startPreview();} catch (IOException e) {mCamera.release();mCamera = null;e.printStackTrace();}}

2.2.4 Camera.Parameters 相机参数类


Camera.Parameters是相机参数类,在这里可以给camera对象设置分辨率,图片方向,闪光灯模式等等一些参数,用以实现更丰富的功能。
使用方式如下:

    @Overridepublic void surfaceCreated(SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(mSurfaceHolder);mCamera.setDisplayOrientation(90);/**Camera.Parameters**/Camera.Parameters parameters = mCamera.getParameters();//得到一个已有的(默认的)参数parameters.setPreviewSize(1920, 1080);//设置分辨率,后面有详细说明parameters.setRotation(90);//设置照相生成的图片的方向,后面有详细说明parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);//设置闪光灯模式为关mCamera.setParameters(parameters);//将参数赋给cameramCamera.startPreview();} catch (IOException e) {mCamera.release();mCamera = null;e.printStackTrace();}}

注意:这里设置分辨率是不能随便设置的,因为每个设备所支持的分辨率不一样,如果设置了设备不支持的分辨率程序就会崩溃,所以上面我把分辨率写死是非常不可取的
可以通过getSupportedPreviewSizes()获得设备支持的分辨率list。

    List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//获得设备所支持的分辨率列表for (int i = 0; i < sizeList.size(); i++) {//这里的TAG是一个常量字符串,用来标识logLog.i(TAG, "width:" + sizeList.get(i).width + ",height:" + sizeList.get(i).height);}

运行这行代码,我们可以看到如下log

01-09 09:23:09.540 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:176,height:144
01-09 09:23:09.540 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:320,height:240
01-09 09:23:09.540 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:352,height:288
01-09 09:23:09.540 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:480,height:320
01-09 09:23:09.540 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:480,height:368
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:640,height:480
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:720,height:480
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:800,height:480
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:800,height:600
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:864,height:480
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:960,height:540
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:1280,height:720
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:1088,height:1088
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:1440,height:1080
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:1920,height:1080
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:1920,height:1088
01-09 09:23:09.541 11014-11014/com.waka.workspace.wakacamera I/CameraActivity: width:1680,height:1248

在这里面我们可以获得所有的支持的分辨率;
但是注意,这里面的width都比height大,这是因为系统默认的图像捕捉是横屏的,而非我们所熟悉的竖屏。


2.2.5 获得最佳分辨率


我们发现,sizeList的分辨率大小虽然有一定的规律,但是并不是最后一个就是屏幕的分辨率;
比如我测试的手机屏幕分辨率为1920x1080,但是sizeList的最后一个是1680x1248,
所以我们还要对这些数据进行一下筛选,找到最适合屏幕的分辨率。

代码如下:

    /*** 获得最佳分辨率* 注意:因为相机默认是横屏的,所以传参的时候要注意,width和height都是横屏下的** @param parameters 相机参数对象* @param width      期望宽度* @param height     期望高度* @return*/private int[] getBestResolution(Camera.Parameters parameters, int width, int height) {int[] bestResolution = new int[2];//int数组,用来存储最佳宽度和最佳高度int bestResolutionWidth = -1;//最佳宽度int bestResolutionHeight = -1;//最佳高度List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//获得设备所支持的分辨率列表int difference = 99999;//最小差值,初始化市需要设置成一个很大的数//遍历sizeList,找出与期望分辨率差值最小的分辨率for (int i = 0; i < sizeList.size(); i++) {int differenceWidth = Math.abs(width - sizeList.get(i).width);//求出宽的差值int differenceHeight = Math.abs(height - sizeList.get(i).height);//求出高的差值//如果它们两的和,小于最小差值if ((differenceWidth + differenceHeight) < difference) {difference = (differenceWidth + differenceHeight);//更新最小差值bestResolutionWidth = sizeList.get(i).width;//赋值给最佳宽度bestResolutionHeight = sizeList.get(i).height;//赋值给最佳高度}}//最后将最佳宽度和最佳高度添加到数组中bestResolution[0] = bestResolutionWidth;bestResolution[1] = bestResolutionHeight;return bestResolution;//返回最佳分辨率数组}

注意:这里期望宽度和期望高度是为了扩展功能,
一般来说照相机都全屏,直接传入手机分辨率就可以了
但是如果不全屏呢?

所以就可以在这里传入希望的高度和宽度,保证预览图像不变形。

修改surfaceCreate方法中的代码:

    @Overridepublic void surfaceCreated(SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(mSurfaceHolder);mCamera.setDisplayOrientation(90);Camera.Parameters parameters = mCamera.getParameters();/**获得屏幕分辨率**/Display display = this.getWindowManager().getDefaultDisplay();Point size = new Point();display.getSize(size);int screenWidth = size.x;int screenHeight = size.y;/**获得最佳分辨率,注意此时要传的width和height是指横屏时的,所以要颠倒一下**/int[] bestResolution = Utils.getBestResolution(parameters, screenHeight, screenWidth);//Utils是一个工具类,我习惯把操作的方法放在一个工具类中,作为静态方法使用parameters.setPreviewSize(bestResolution[0], bestResolution[1]);parameters.setRotation(90);parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);mCamera.setParameters(parameters);mCamera.startPreview();} catch (IOException e) {mCamera.release();mCamera = null;e.printStackTrace();}}

好了,现在应该就可以看到不变形的图像了~
如果还变形,请隐藏通知栏,ActionBar和底部的虚拟导航键栏,等等等等等。。。

所以这个时候期望宽度和期望高度就有用了,我们可以计算出除去这些系统栏的高度,然后传入我们算好的值,就可以自动匹配出最佳分辨率~


2.2.6 设置自动对焦


现在预览出来的图像终于是正常大小了,可还是模模糊糊的,这咋拍?这坑定不能拍啊
所以我们需要实现相机的自动对焦功能,在这里Android已经给我们封装的很好了,我们只需要简单的调用一下方法就行。
好多手机里自带的那种停下来就自动进行对焦的方式我还不太会,就先写了个触摸自动对焦

自动对焦需要配置相关权限,和实现Camera.AutoFocusCallback接口

代码如下:

    //重写onTouchEvent方法@Overridepublic boolean onTouchEvent(MotionEvent event) {mCamera.autoFocus(this);//让Activity实现接口return true;}//实现接口中的方法,自动对焦完成时的回调@Overridepublic void onAutoFocus(boolean success, Camera camera) {//在这里可以判断对焦是否成功,进行一些操作}

自动对焦已经完成,调用Google的东西真是很简单,我们可以在这里加个提示框啊什么的,后面有时间再说。


2.2.7 照相


终于要照相了,T^T
照相调用camera.takePicture方法,
代码如下:

“`
private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {//照相动作回调用的pictureCallback

    //在这里可以获得拍照后的图片数据@Overridepublic void onPictureTaken(byte[] data, Camera camera) {//byte[]数组data就是图片数据,可以在这里对图片进行处理mCamera.startPreview();//恢复预览}
};//点击事件
@Override
public void onClick(View v) {switch (v.getId()) {case R.id.imgbtnTakePhoto:mCamera.takePicture(null, null, pictureCallback);//拍照会停止预览break;default:break;}
}

就先写到这里吧,有精力再补充。。

这篇关于Android实战开发:自定义照相机的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

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

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

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount