Android?Camera+SurfaceView自動聚焦防止變形拉伸

前言
這里主要簡單介紹如何使用Camera+SurfaceView自定義相機(jī)拍照,如果是Camera2或者是TextureView的可以前往主頁,后面會陸續(xù)推出系列文章。
自定義相機(jī)很多人都已經(jīng)介紹得非常清楚了,這里個人一方面針對自己的實(shí)踐起一個記錄作用,另一方面也分享一下自己自定義相機(jī)的過程和總結(jié)。
相關(guān)Demo在文末有提供,有需要可自行前往獲取~
Android使用相機(jī)的幾種方式
直接調(diào)用原生相機(jī)拍照
- 優(yōu)勢:兼容性最好
- 劣勢:無法支持一些自定義場景的使用
自定義相機(jī)拍照
- 優(yōu)勢:可以根據(jù)需求實(shí)現(xiàn)一些原生相機(jī)無法支持的功能
- 劣勢:兼容性相對較差
調(diào)用原生相機(jī)
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系統(tǒng)以上需要進(jìn)行provider配置
2、imageFile則是你需要拍照完成存儲的照片文件,可以根據(jù)自己的需要自定義
自定義相機(jī)
先上效果圖:

自定義相機(jī)我這里分為以下幾步:
- 系統(tǒng)權(quán)限配置
- 沉浸式配置
- 相機(jī)布局
- 相機(jī)預(yù)覽設(shè)置
- 相機(jī)拍照
系統(tǒng)權(quán)限配置
// 相機(jī)權(quán)限 <uses-permission android:name="android.permission.CAMERA" /> // 文件讀寫權(quán)限 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> // 相機(jī)自動對焦配置 <uses-feature android:name="android.hardware.camera.autofocus" />
以上就可以滿足自定義相機(jī)所需的全部權(quán)限了,只是需要注意相機(jī)權(quán)限、文件讀寫權(quán)限都需要動態(tài)獲取。
沉浸式配置
沉浸式的配置是為了更好的獲取到相機(jī)預(yù)覽尺寸,從而避免相機(jī)預(yù)覽畫面變形。而沉浸式不同系統(tǒng)版本也存在差異化配置,這里為了簡單我直接采用開源項(xiàng)目immersionbar進(jìn)行,倉庫地址:github.com/gyf-dev/Imm… ,具體用法也很簡單,只需要在自定義相機(jī)的頁面oncreate中進(jìn)行初始化即可實(shí)現(xiàn)沉浸式效果。
ImmersionBar.with(this).init()
相機(jī)布局
這里就是最簡單的布局了,一個SurfaceView用于預(yù)覽相機(jī)畫面、一個取消按鈕、一個拍照按鈕:
<?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="點(diǎn)擊拍照"
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>
相機(jī)預(yù)覽設(shè)置
既然是相機(jī)預(yù)覽,這里就一定要先獲取相機(jī)以及獲取相機(jī)預(yù)覽尺寸,最后才是將相機(jī)預(yù)覽畫面通過SurfaceView顯示出來。
獲取相機(jī)
由于從Android2.3開始就已經(jīng)支持了多攝像頭了,因此獲取相機(jī)可以直接通過Camera.open(id)獲取,而這個id可以根據(jù)Camera.getNumberOfCameras()獲取。另外由于相機(jī)預(yù)覽畫面默認(rèn)是橫屏的,如果你是做的豎屏相機(jī),需要旋轉(zhuǎn)90度。
/**
* 獲取打開相機(jī)
*
* @return
*/
private Camera getCustomCamera() {
if (null == mCamera) {
//Camera.open()方法說明:2.3以后支持多攝像頭,所以開啟前可以通過getNumberOfCameras先獲取攝像頭數(shù)目,
// 再通過 getCameraInfo得到需要開啟的攝像頭id,然后傳入Open函數(shù)開啟攝像頭,
// 假如攝像頭開啟成功則返回一個Camera對象
try {
// 這里我直接默認(rèn)使用后置攝像頭
mCamera = Camera.open(0);
//預(yù)覽畫面默認(rèn)是橫屏的,需要旋轉(zhuǎn)90度
mCamera.setDisplayOrientation(90);
} catch (Exception e) {
}
}
return mCamera;
}
獲取最佳預(yù)覽尺寸
這一步如果不動態(tài)調(diào)整surfaceView的大小其實(shí)很難兼容所有設(shè)備,不過好在目前大多數(shù)相機(jī)都是可以通過獲取屏幕分辨率以及相機(jī)支持的分辨率,通過計算獲取到一個最佳的預(yù)覽分辨率的。
1、獲取相機(jī)支持的所有預(yù)覽分辨率
// 獲取相機(jī)支持的所有預(yù)覽尺寸,一般是從大到小倒序排列的 mCamera?.parameters.supportedPictureSizes
2、計算合適的分辨率進(jìn)行預(yù)覽
其實(shí)這里的核心就是獲取一個和SurfaceView同等分辨率比例的一個最大分辨率尺寸,這樣在預(yù)覽和拍照的時候相片才不會變形,由于我是將SurfaceView的尺寸設(shè)置為屏幕的大?。ㄟ@也是做沉浸式的原因),所以只需要獲取和屏幕一樣比例的最大預(yù)覽尺寸即可。以下是我的具體方式:
/**
* 根據(jù)屏幕尺寸以及相機(jī)支持的分辨率獲取最佳預(yù)覽分辨率
*
* @param parameters 相機(jī)參數(shù)
*/
private fun getPreviewSize(parameters: Camera.Parameters) {
// 選擇合適的圖片尺寸,必須是手機(jī)支持的尺寸
val sizeList = parameters.supportedPictureSizes
// 如果sizeList只有一個我們也沒有必要做什么了,因?yàn)榫退粋€別無選擇
if (sizeList.size > 1) {
for (size: Camera.Size in sizeList) {
Log.i("分辨率>>>", "Width=" + size.height + "__Height=" + size.width)
// 獲取當(dāng)前分辨率的最大公約數(shù)
val sizeGY = getGY(size.height, size.width)
// 校驗(yàn)是否和屏幕的分辨率比例一致,如果一致則是最大
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)
}
/**
* 獲取兩個數(shù)的公約數(shù)
*
* @param a 數(shù)字1
* @param b 數(shù)字2
*
* @return 公約數(shù)
*/
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
}
進(jìn)行相機(jī)預(yù)覽
預(yù)覽的核心分為以下幾步:
- 實(shí)現(xiàn)surfaceViewHolder的各項(xiàng)回調(diào)
- 將surfaceViewHolder設(shè)置為Camera的顯示控件
- 開啟相機(jī)預(yù)覽
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)
// 設(shè)置自動對焦模式
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);
//停止預(yù)覽
mCamera?.stopPreview()
mCamera?.lock()
//釋放相機(jī)資源
mCamera?.release()
mCamera = null
}
}
})
拍照
拍照只需要調(diào)用Camera的takePicture方法即可:
binding.btnTakePicture.setOnClickListener {
mCamera?.takePicture({}, null
) { bytearray, camera ->
// bytearray 就是照片數(shù)據(jù)
}
}
}
注意事項(xiàng):如果你在獲取相機(jī)的時候?qū)ο鄼C(jī)預(yù)覽畫面做了旋轉(zhuǎn),這里獲取到照片數(shù)據(jù)需要將它旋轉(zhuǎn)回來。
可以使用如下方法旋轉(zhuǎn)圖片:
/**
* 旋轉(zhuǎn)圖片
*
* @param bitmap 目標(biāo)圖片
* @param rotation 旋轉(zhuǎn)角度
* @Return 旋轉(zhuǎn)后的圖片
*/
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);
}
至此,關(guān)于使用Camera進(jìn)行自定義相機(jī)開發(fā)就介紹到這里,有興趣的可以去下載源碼查看。 源碼地址:gitee.com/No.N/custom…
以上就是Android Camera+SurfaceView自動聚焦防止變形拉伸的詳細(xì)內(nèi)容,更多關(guān)于Android Camera SurfaceView的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Studio 下 Flutter 開發(fā)環(huán)境搭建過程
這篇文章主要介紹了Android Studio 下 Flutter 開發(fā)環(huán)境搭建/Flutter / Dart 插件安裝 | Flutter SDK 安裝 | 環(huán)境變量配置 | 開發(fā)環(huán)境檢查,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-03-03
ubuntu用wifi連接android調(diào)試程序的步驟
這篇文章主要介紹了ubuntu用wifi連接android調(diào)試程序的步驟,需要的朋友可以參考下2014-02-02
android實(shí)現(xiàn)添加耳機(jī)狀態(tài)圖標(biāo)的方法
這篇文章主要介紹了android實(shí)現(xiàn)添加耳機(jī)狀態(tài)圖標(biāo)的方法,較為詳細(xì)的分析了Android實(shí)現(xiàn)添加耳機(jī)圖標(biāo)的原理與相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
Android 深入探究自定義view之流式布局FlowLayout的使用
FlowLayout(int align, int hgap, int vgap)創(chuàng)建一個新的流布局管理器,它具有指定的對齊方式以及指定的水平和垂直間隙,意思就是說從左上角開始添加原件,依次往后排,第一行擠滿了就換一行接著排2021-11-11
超簡單實(shí)現(xiàn)Android自定義Toast示例(附源碼)
本篇文章主要介紹了超簡單實(shí)現(xiàn)Android自定義Toast示例(附源碼),具有一定的參考價值,有興趣的可以了解一下。2017-02-02
Android下拉刷新完全解析,教你如何一分鐘實(shí)現(xiàn)下拉刷新功能(附源碼)
以下是我自己花功夫編寫了一種非常簡單的下拉刷新實(shí)現(xiàn)方案,現(xiàn)在拿出來和大家分享一下。相信在閱讀完本篇文章之后,大家都可以在自己的項(xiàng)目中一分鐘引入下拉刷新功能2013-07-07

