前言

这儿主要简略介绍如何运用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则是你需求摄影完结存储的照片文件,可以依据自己的需求自定义

自定义相机

先上效果图:

Android自定义相机—Camera篇

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

  • 体系权限装备
  • 沉溺式装备
  • 相机布局
  • 相机预览设置
  • 相机摄影

体系权限装备

// 相机权限
<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…

假设有任何问题欢迎随时谈论或许私信沟通,假设我的共享可以帮助到你也希望能给我点赞重视~

假设有任何问题欢迎随时谈论或许私信沟通,假设我的共享可以帮助到你也希望能给我点赞重视~