Camera权限申请
1、在清单文件中配置。
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
2、动态权限申请。
Manifest.permission.CAMERA
照相机页面全屏实现
1、采用 Theme.MaterialComponents.DayNight.NoActionBa 主题。
2、在页面中,setContentView之前,写上
this.requestWindowFeature(Window.FEATURE_NO_TITLE)
this.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
SurfaceView
如何监听SurfaceView的生命周期?
(1)首先获得SurfaceVIew的holder,然后增加CallBack
(2)首次启动会执行surfaceCreated 和 surfaceChanged 方法
(3)如果横竖屏切换,surfaceCreated 和 surfaceChanged、和surfaceDestroyed 方法。
val holder = surfaceView.holder
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(p0: SurfaceHolder) {
Log.e(TAG, "surfaceCreated")
val camera = cameraManager.openCamera(CAMERA_ID)
}
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
Log.e(TAG, "surfaceChanged")
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
Log.e(TAG, "surfaceDestroyed")
}
})
SurfaceView + Camera展示图像的流程
1、监听SurfaceView的生命周期,在 surfaceCreated 方法中,说明SurfaceView创建完成
2、然后通过 Camera.open(cameraId) 获得Camera对象。
3、然后设置 Camera 的参数,比如展示的容器,设置预览的分辨率,设置对焦模式,设置旋转的角度,设置图像格式。
4、然后开始预览。
获得相机
fun openCamera(cameraId: Int): Camera? {
this.cameraId = cameraId;
if (camera == null) {
try {
camera = Camera.open(cameraId)
} catch (e: Exception) {
}
}
return camera
}
设置相机展示的容器
camera?.setPreviewDisplay(surfaceHolder)
获得相机合适的预览分辨率
1、相机支持的分辨率有很多,比如有18个
(height:1080 width:1920、height:880 width:1920、height:720 width:1552、
height:864 width: 1536 .......)
特点:这些相机的预览分辨率都有一个特点,就是width > height
2、选择相机合适的预览分辨率的逻辑
(1)如果支持的分辨率集合为空,那么选择相机的预览分辨率。
(2)如果只有一个支持的分辨率,那么选择这一个分辨率,然后就是预览的分辨率。
(3)如果有多个支持的分辨率,首先选择分辨率相等的那个分辨率。
(4)如果分辨率不相等,那么选择宽高比最接近的那个分辨率。
具体逻辑如下:
package com.example.camerademo.utils
import android.graphics.Point
import android.hardware.Camera
import com.example.camerademo.CoreSetup
class CommonUtil {
companion object {
fun getCameraPreviewSize(parameters: Camera.Parameters): Point {
// 获取所有支持的预览分辨率,支持的分辨率有18个(1920*1080)
val rawSupportedSizes = parameters.supportedPreviewSizes
// 选择合适支持的分辨率
var supportedPreviewSize: Camera.Size?
// 如果支持的分辨率为空
if (rawSupportedSizes == null) {
supportedPreviewSize = parameters.previewSize
// TODO 这个值为啥要这么设置
return Point(supportedPreviewSize.height, supportedPreviewSize.width)
}
// 如果只有一个支持的个数
if (rawSupportedSizes.size == 1) {
return Point(rawSupportedSizes.get(0).width, rawSupportedSizes.get(0).height)
}
// 如果有多个支持的个数
// 首先判断是否有相等的,如果没有相等的,取出这个值
supportedPreviewSize =
rawSupportedSizes.find { it.width == CoreSetup.preWidth && it.height == CoreSetup.preHeight }
// 如果 != null,说明有相同的分辨率,那么选择这个分辨率
if (supportedPreviewSize != null) {
return Point(supportedPreviewSize.width, supportedPreviewSize.height)
}
// 说明没有相同的分辨率,那么此时选择最近的分辨率
return this.getClostPersize(parameters, CoreSetup.preWidth, CoreSetup.preHeight)
}
/**
* 选择最近的分辨率
*/
private fun getClostPersize(
parameters: Camera.Parameters,
width: Int,
height: Int
): Point {
var deltaRatioMin = 3.4028235E38f
val surfaceViewWidth = width
val surfaceViewHeight = height
var realWidth = 0
var realHeight = 0
// 获得宽高比
val surfaceViewRatio = surfaceViewWidth.toFloat() / surfaceViewHeight.toFloat()
// 获得支持的分辨率
val supportedPreviewSizes = parameters.supportedPreviewSizes
// 遍历集合,选择一个最接近的分辨率
var resultSize: Camera.Size? = null
// 选择相等的分辨率
for (temp in supportedPreviewSizes) {
val realRatio = temp.width.toFloat() / temp.height.toFloat()
if (realRatio == surfaceViewRatio) {
if (temp.width <= 1920) {
if (realWidth <= temp.width) {
realWidth = temp.width;
realHeight = temp.height;
}
}
}
}
// 如果没有相等的的,那么选择一个最相近的
if (realWidth == 0 || realHeight == 0) {
for (temp in supportedPreviewSizes) {
val curRatio = temp.width.toFloat() / temp.height.toFloat()
val deltaRatio = Math.abs(surfaceViewRatio - curRatio)
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
resultSize = temp
realWidth = temp.width;
realHeight = temp.height;
}
}
}
if (null == resultSize) {
resultSize = parameters.previewSize
realWidth = resultSize.width
realHeight = resultSize.height
}
return Point(realWidth, realHeight)
}
}
}
设置相机的对焦模式
// 设置对焦模式
val focusModes = parameters.supportedFocusModes
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
设置相机的旋转角度
camera?.setDisplayOrientation(CameraUtil.getOrientation(activity,cameraId))
class CameraUtil {
companion object {
fun getOrientation(activity: Activity, cameraId: Int): Int {
val info = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, info)
// 获取页面旋转的角度类型
val rotation = activity.windowManager.defaultDisplay.rotation
// 根据页面旋转的角度类型,来获取角度
var degrees = 0
when (rotation) {
Surface.ROTATION_0 -> degrees = 0
Surface.ROTATION_90 -> degrees = 90
Surface.ROTATION_180 -> degrees = 180
Surface.ROTATION_270 -> degrees = 270
else -> {
}
}
// 根据相机的旋转角度 和 页面旋转的角度,生成相机需要旋转的角度。
var result: Int
//前置
if (info.facing === Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360
result = (360 - result) % 360
} else {
// 后置
result = (info.orientation - degrees + 360) % 360
}
return result
}
}
}
定时对焦
/**
* 定时对焦
*/
private fun recyclerAutoFocus() {
val countDownTimer = object : CountDownTimer(Long.MAX_VALUE, 2000) {
override fun onTick(p0: Long) {
Log.e(TAG, "onTick执行")
camera?.autoFocus(null)
}
override fun onFinish() {
}
}
countDownTimer.start()
}
开启闪光灯
fun controlFlash() {
if (camera != null) {
val parameters = camera!!.parameters
val flashMode = parameters.flashMode
if (flashMode == null){
Toast.makeText(activity,"不支持闪光灯",Toast.LENGTH_SHORT).show()
return
}
// 正在亮灯情况,关闭灯
if (flashMode.equals(Camera.Parameters.FLASH_MODE_TORCH)){
parameters.flashMode = Camera.Parameters.FLASH_MODE_OFF
} else {
// 如果不是亮灯,打开灯
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
parameters.setExposureCompensation(-1);
}
try {
camera!!.parameters = parameters
} catch (e: Exception) {
e.printStackTrace()
}
}
}
预览图片不变形逻辑处理
1、当SurfaceView创建完成的时候,获取SurfaceView的宽高,然后设置预览的分辨率。
2、然后根据此预览分辨率,同时根据相机支持的分辨率,选择相等的分辨率或者宽高比最接近的分辨率,然后更新预览的分辨率。
3、根据预览的分辨率的宽高比 + SurfaceView的宽高比,然后等比例的放大SurfaceView的宽高,这样预览的图像就不会变形。
private fun initSurfaceView(surfaceView: SurfaceView) {
val holder = surfaceView.holder
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(p0: SurfaceHolder) {
Log.e(TAG, "surfaceCreated")
CoreSetup.preWidth = surfaceView.height
CoreSetup.preHeight = surfaceView.width
cameraManager.startPreview(holder)
}
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
Log.e(TAG, "surfaceChanged")
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
Log.e(TAG, "surfaceDestroyed")
}
})
}
class PlateidSurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (CoreSetup.preHeight == 0 || CoreSetup.preWidth == 0) {
return
}
// 计算预览的宽高
// 理想的宽
val desiredWidth = CoreSetup.preHeight
val desiredHeight = CoreSetup.preWidth
// 理想的宽高比
val desireRadio = desiredWidth.toFloat() / desiredHeight.toFloat()
// 获取SurfaceView的宽高比
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
var layoutWidth = when (widthMode) {
MeasureSpec.EXACTLY -> {
widthSize
}
MeasureSpec.AT_MOST -> {
Math.min(desiredWidth, widthSize)
}
else -> {
desiredWidth
}
}
var layoutHeight = if (heightMode == MeasureSpec.EXACTLY) {
heightSize
} else if (heightMode == MeasureSpec.AT_MOST) {
Math.min(desiredHeight, heightSize)
} else {
desiredHeight
}
// 等比例的放大SurfaceView的宽高。
val layoutRadio = layoutWidth.toFloat() / layoutHeight.toFloat()
if (layoutRadio > desireRadio) {
layoutHeight = (layoutWidth.toFloat() / desireRadio).toInt()
} else {
layoutWidth = (layoutHeight.toFloat() * desireRadio).toInt()
}
this.setMeasuredDimension(layoutWidth, layoutHeight)
}
}
传感器用来判断是竖直、倒置、左侧状态、右侧状态
class ScreenRotation(val context: Context) {
private val TAG: String = "ScreenRotation"
private val sensorService: SensorManager? by lazy { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }
// 这些参数默认为竖屏状态的参数
/**向左旋转 */
var rotateLeft = false
/**正向旋转 */
var rotateTop = true
/**向右旋转 */
var rotateRight = false
/**倒置旋转 */
var rotateBottom = false
private val listener: SensorEventListener = object : SensorEventListener {
// 传感器方向改变时会调用,及时手机放在那,也会持续调用
override fun onSensorChanged(event: SensorEvent) {
// IntervalLogUtil.e(TAG, "传感器改变了")
val x = event.values[SensorManager.DATA_X]
val y = event.values[SensorManager.DATA_Y]
// IntervalLogUtil.e(TAG, "x:$x y:$y")
val standardValue = 7
if (x > standardValue) {
if (!rotateLeft) {
IntervalLogUtil.e(TAG, "手机在左侧状态")
rotateBottom = false
rotateRight = false
rotateTop = false
rotateLeft = true
}
} else if (x < -standardValue) {
if (!rotateRight) {
IntervalLogUtil.e(TAG, "手机在右侧状态")
rotateBottom = false
rotateRight = true
rotateTop = false
rotateLeft = false
}
}else if (y < -standardValue) {
if (!rotateBottom) {
IntervalLogUtil.e(TAG, "倒置状态")
rotateBottom = true
rotateRight = false
rotateTop = false
rotateLeft = false
}
} else if (y > standardValue) {
if (!rotateTop) {
IntervalLogUtil.e(TAG, "竖屏状态")
rotateBottom = false
rotateRight = false
rotateTop = true
rotateLeft = false
}
}
}
// 当传感器的精度发生变化时会调用
override fun onAccuracyChanged(event: Sensor, accuracy: Int) {
IntervalLogUtil.e(TAG, "传感器的精度改变了")
}
}
init {
IntervalLogUtil.e(TAG, "init方法执行")
val sensor = sensorService?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorService?.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)
}
}
Camera注意事项
1、SurfaceView的初始化放在onResume中,否则会出现:当锁屏,然后解锁以后,会出现黑屏的现象。
2、现象:当A的CameraActivity切换到后台,然后打开另外的App(B)的相机预览界面,然后返回A App,此时出现了A界面黑屏,看到提示显示:java.io.IOException: setPreviewTexture failed
解决办法:
(1)在Activity的onStop方法中,移除掉SurfaceView。
override fun onStop() {
super.onStop()
(surfaceView?.parent as ViewGroup).removeView(surfaceView)
}
(2)在 SurfaceView 的 surfaceDestroyed 方法中,需要释放Camera资源。
fun removeCamera() {
try {
camera?.setPreviewCallback(null)
camera?.stopPreview()
camera?.release()
camera = null
} catch (e: Exception) {
}
}