Android自定义相机—Camera篇

前言

这里主要简单介绍如何使用Camera+SurfaceView自定义相机拍照,如果是Camera2或者是TextureView的可以前往主页,后面会陆续推出系列文章。

自定义相机很多人都已经介绍得非常清楚了,这里个人一方面针对自己的实践起一个记录作用,另一方面也分享一下自己自定义相机的过程和总结。

相关Demo在文末有提供,有需要可自行前往获取~

Android使用相机的几种方式

  • 直接调用原生相机拍照

    • 优势:兼容性最好
    • 劣势:无法支持一些自定义场景的使用
  • 自定义相机拍照

    • 优势:可以根据需求实现一些原生相机无法支持的功能
    • 劣势:兼容性相对较差

调用原生相机

val nativeIntent = Intent()
var uri: Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    uri = FileProvider.getUriForFile(
    this@MainActivity,
    "com.n.customcamera.fileProvider",
    imageFile
    )
    nativeIntent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
} else {
    uri = Uri.fromFile(imageFile)
}
​
nativeIntent.action = MediaStore.ACTION_IMAGE_CAPTURE
nativeIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
startActivityForResult(nativeIntent, Constants.OPEN_NATIVE_CAMERA_CODE)

注:上述需要注意两个地方:

1、uri的指定Android7.0系统以上需要进行provider配置

2、imageFile则是你需要拍照完成存储的照片文件,可以根据自己的需要自定义

自定义相机

先上效果图:

image-20230104222005992.png

自定义相机我这里分为以下几步:

  • 系统权限配置
  • 沉浸式配置
  • 相机布局
  • 相机预览设置
  • 相机拍照

系统权限配置

// 相机权限
<uses-permission android:name="android.permission.CAMERA" />
// 文件读写权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
// 相机自动对焦配置
<uses-feature android:name="android.hardware.camera.autofocus" />

以上就可以满足自定义相机所需的全部权限了,只是需要注意相机权限、文件读写权限都需要动态获取。

沉浸式配置

沉浸式的配置是为了更好的获取到相机预览尺寸,从而避免相机预览画面变形。而沉浸式不同系统版本也存在差异化配置,这里为了简单我直接采用开源项目immersionbar进行,仓库地址:github.com/gyf-dev/Imm… ,具体用法也很简单,只需要在自定义相机的页面oncreate中进行初始化即可实现沉浸式效果。

ImmersionBar.with(this).init()

相机布局

这里就是最简单的布局了,一个SurfaceView用于预览相机画面、一个取消按钮、一个拍照按钮:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MyCameraActivity">
​
    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
​
    <LinearLayout
        android:id="@+id/view_bottom_operate"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:background="#FFF"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingStart="10dp"
        android:paddingEnd="10dp"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">
​
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/btn_cancle"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="取消"
            android:textColor="#000"
            android:src="@mipmap/icon_cancle"
            android:textSize="18sp" />
​
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/btn_take_picture"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="点击拍照"
            android:textColor="#000"
            android:src="@mipmap/icon_take_picture"
            android:textSize="18sp" />
​
        <androidx.appcompat.widget.AppCompatImageView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text=""
            android:textColor="#000"
            android:textSize="18sp" />
    </LinearLayout>
​
</androidx.constraintlayout.widget.ConstraintLayout>

相机预览设置

既然是相机预览,这里就一定要先获取相机以及获取相机预览尺寸,最后才是将相机预览画面通过SurfaceView显示出来。

获取相机

由于从Android2.3开始就已经支持了多摄像头了,因此获取相机可以直接通过Camera.open(id)获取,而这个id可以根据Camera.getNumberOfCameras()获取。另外由于相机预览画面默认是横屏的,如果你是做的竖屏相机,需要旋转90度。

/**
* 获取打开相机
*
* @return
*/
private Camera getCustomCamera() {
    if (null == mCamera) {
        //Camera.open()方法说明:2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,
        // 再通过 getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,
        // 假如摄像头开启成功则返回一个Camera对象
        try {
            // 这里我直接默认使用后置摄像头
            mCamera = Camera.open(0);
            //预览画面默认是横屏的,需要旋转90度
            mCamera.setDisplayOrientation(90);
        } catch (Exception e) {
        }
    }
    return mCamera;
}

获取最佳预览尺寸

这一步如果不动态调整surfaceView的大小其实很难兼容所有设备,不过好在目前大多数相机都是可以通过获取屏幕分辨率以及相机支持的分辨率,通过计算获取到一个最佳的预览分辨率的。

1、获取相机支持的所有预览分辨率

// 获取相机支持的所有预览尺寸,一般是从大到小倒序排列的
mCamera?.parameters.supportedPictureSizes

2、计算合适的分辨率进行预览

其实这里的核心就是获取一个和SurfaceView同等分辨率比例的一个最大分辨率尺寸,这样在预览和拍照的时候相片才不会变形,由于我是将SurfaceView的尺寸设置为屏幕的大小(这也是做沉浸式的原因),所以只需要获取和屏幕一样比例的最大预览尺寸即可。以下是我的具体方式:

/**
 * 根据屏幕尺寸以及相机支持的分辨率获取最佳预览分辨率
 *
 * @param parameters 相机参数
 */
private fun getPreviewSize(parameters: Camera.Parameters) {
    // 选择合适的图片尺寸,必须是手机支持的尺寸
    val sizeList = parameters.supportedPictureSizes
    // 如果sizeList只有一个我们也没有必要做什么了,因为就他一个别无选择
    if (sizeList.size > 1) {
        for (size: Camera.Size in sizeList) {
            Log.i("分辨率>>>", "Width=" + size.height + "__Height=" + size.width)
            // 获取当前分辨率的最大公约数
            val sizeGY = getGY(size.height, size.width)
            // 校验是否和屏幕的分辨率比例一致,如果一致则是最大
            if (screenWidth / screenGY == size.height / sizeGY && screenHeight / screenGY == size.width / sizeGY) {
                previewWidth = size.height
                previewHeight = size.width
                return
            }
        }
    } else {
        previewWidth = sizeList[0].height
        previewHeight = sizeList[0].width
    }
    Log.i("分辨率", "previewWidth=" + previewWidth + "__previewHeight=" + previewHeight)
}

/**
 * 获取两个数的公约数
 *
 * @param a 数字1
 * @param b 数字2
 * 
 * @return 公约数
 */
private fun getGY(a: Int, b: Int): Int {
    var localA = a
    var localB = b
    while (localA % localB != 0) {
        val temp = localA % localB
        localA = localB
        localB = temp
    }
    return localB
}

进行相机预览

预览的核心分为以下几步:

  • 实现surfaceViewHolder的各项回调
  • 将surfaceViewHolder设置为Camera的显示控件
  • 开启相机预览
mSurfaceHolder = binding.surfaceView.holder
mSurfaceHolder.addCallback(object : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder) {
        mCamera = getCustomCamera()
    }
​
    override fun surfaceChanged(holder: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
        mCamera?.let {
            val parameters = it.parameters
            getPreviewSize(parameters)
            parameters.setPictureSize(previewWidth, previewHeight)
            // 设置自动对焦模式
            parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
            parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
            it.parameters = parameters
            try {
                it.setPreviewDisplay(mSurfaceHolder)
            } catch (e: Exception) {
                e.printStackTrace()
                Toast.makeText(this@MyCameraActivity, e.message, Toast.LENGTH_SHORT).show()
            }
            it.startPreview()
        }
    }
​
    override fun surfaceDestroyed(p0: SurfaceHolder) {
        if (null != mCamera) {
            mSurfaceHolder.removeCallback(this);
            mCamera?.setPreviewCallback(null);
            //停止预览
            mCamera?.stopPreview()
            mCamera?.lock()
            //释放相机资源
            mCamera?.release()
            mCamera = null
        }
    }
})

拍照

拍照只需要调用Camera的takePicture方法即可:

binding.btnTakePicture.setOnClickListener {
    mCamera?.takePicture({}, null
    ) { bytearray, camera ->
            // bytearray 就是照片数据
        }
    }
}

注意事项:如果你在获取相机的时候对相机预览画面做了旋转,这里获取到照片数据需要将它旋转回来。

可以使用如下方法旋转图片:

/**
 * 旋转图片
 *
 * @param bitmap 目标图片
 * @param rotation 旋转角度
 * @Return 旋转后的图片
 */
public static Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) {
    Matrix matrix = new Matrix();
    matrix.postRotate(rotation);
    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
            bitmap.getHeight(), matrix, false);
}

至此,关于使用Camera进行自定义相机开发就介绍到这里,有兴趣的可以去下载源码查看。 源码地址:gitee.com/No.N/custom…

如果有任何问题欢迎随时评论或者私信交流,如果我的分享能够帮助到你也希望能给我点赞关注~

如果有任何问题欢迎随时评论或者私信交流,如果我的分享能够帮助到你也希望能给我点赞关注~

作者:一念三千_
原文链接:https://juejin.cn/post/7185335137603682341

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Android自定义相机Camera2是一种基于Android Camera2 API的相机应用程序开发技术。相比于旧版Camera API,Camera2 API提供了更多的控制权和更高的性能。使用Camera2 API,开发者可以自定义相机的各种参数,如曝光时间、ISO、焦距等,从而实现更加精细的相机控制。同时,Camera2 API还支持RAW格式的图像输出,使得开发者可以更加灵活地处理相机输出的图像数据。 ### 回答2: Android自定义相机Camera2是基于Android 5.0之后提供的新一代相机API,相比较Camera1,它提供了更加丰富和灵活的功能。在使用Camera2 API时,需要使用一些异步的回调接口,通过监听器来处理相机的各种状态和数据。 Camera2 API 的使用分为三个阶段:预览、拍照和保存图片。其中,预览是最基本的功能,也是所有相机功能的基础。可以通过创建CameraDevice,设置相机的参数和预览界面,再通过ImageReader获取相机捕获的数据,最后进行图像显示。 在拍照功能中,需要使用相机的Capture请求和CaptureSession会话来进行捕获相片的操作。首先需要创建相机的CaptureRequest.Builder对象,设置拍照相关的参数。然后,需要使用CaptureRequest.Builder构建捕获请求CaptureRequest,再将捕获请求加入到CaptureSession,通过光圈控制、曝光时间、ISO值、白平衡等参数控制相机的拍摄效果。 最后,在保存图片时,需要使用ImageReader对象从相机捕获的数据中获取图片数据,然后将其保存到文件中或者显示到界面上。 总之,使用Camera2 API自定义相机可以在相机的预览、拍照和保存等各个环节上实现更加灵活的控制,能够充分发挥相机的功能,达到更好的拍照效果。 ### 回答3: Android自定义相机成为了越来越多开发者关注和学习的领域,其中相机API2(Camera2)是我们不可忽视的一部分。Android Lollipop时代,Google引入了Camera2 API,它是原来的相机API(Camera)的替代品,提供了很多强大的功能和灵活的控制权,包括更高的FPS、更低的延迟和更好的控制,让我们可以更精确地控制相机硬件,实现更好的相机+应用程序的体验。 使用Camera2 API, 我们可以: 1. 使用更高质量的图像传感器. 2. 以流的方式更容易访问图像预览数据. 3. 支持高帧率的视频录制,甚至支持高达120fps. 4. 可以在更广泛的控制选项(如焦距,曝光等)中进行定制,以实现更具创意的摄影模式。 5. 显着提高了拍摄速度和唤醒速度。 然而,相较于Camera API, Camera2 API一个更复杂和庞大的API,需要更多的配置和使用,刚开始可能会让些开发者望而却步,然而掌握Camera2 API后,它将为您带来更多的好处。开始使用Camera2 API不仅需要对Android摄像头架构有很好的理解,还需要一定的Java编程经验和计算机图形知识。 总之,Android自定义相机Camera2是非常复杂和庞大的API,需要开发人员掌握许多技能。但是,一旦掌握,它将为开发人员带来更高的图像质量,更精确的控制和更多的相机配置选项,这将为开发人员提高用户体验的基础,满足客户的各种摄影需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值