第一步build.gradle:
// CameraX
def camerax_version = '1.0.0-alpha06'
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
第二步布局代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/capture_button"
android:text="拍照"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:layout_centerInParent="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
第三步Activity代码
import android.util.Size
import android.graphics.Matrix
import android.util.Rational
import android.view.Surface
import android.view.ViewGroup
import androidx.camera.core.*
import com.luogiant.yuanmao.R
import com.luogiant.yuanmao.basePresenter.BaseActivity
import com.luogiant.yuanmao.constant.Constant
import kotlinx.android.synthetic.main.activity_demo.*
import java.io.File
import java.nio.ByteBuffer
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
/**
* BaseActivity为自定义activity父类
* 此处BaseActivity是自己封装的,
* 本人使用BaseActivity是因为把权限请求封装在里面了
* 小伙伴可将BaseActivity改为AppCompatActivity
*/
class DemoActivity : BaseActivity() {
val requestPermissionCode = "com.luogiant.yuanmao.demo.DemoActivity"//申请权限的code
private val executor = Executors.newSingleThreadExecutor()//单例线程
override fun setContentView() {
setContentView(R.layout.activity_demo)
//此方法存在于BaseActivity中
//BaseActivity中方法,申请权限
//在此我不贴出权限请求的代码了
//因为本文主题是实现cameraX的预览和拍照
requestMyPermissions(Constant.permissionSType3,requestPermissionCode)
}
/**
* 已申请到权限
*/
override fun hasPermissionS() {
super.hasPermissionS()
if (baseRequestPermissionCode.equals(requestPermissionCode))//如果已获取权限,则打开cameraX预览
startPreView()
}
private fun startPreView() {
viewFinder.post { startCamera() }
// viewFinder视图改变监听
viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTransform()
}
}
/**
* cameraX实现预览
*/
private fun startCamera() {
val previewConfig = PreviewConfig.Builder()
// .setTargetAspectRatioCustom(Rational(9,16))
// .setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetResolution(Size(viewFinder.width, viewFinder.height))
.build()
val preview = Preview(previewConfig)
// 取景器更新,重新计算布局
preview.setOnPreviewOutputUpdateListener {
// 获取viewFinder父容器
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)//将当前用来预览的TextureView从父容器移除
parent.addView(viewFinder, 0)//并从新添加用来预览的TextureView
viewFinder.surfaceTexture = it.surfaceTexture//将预览画面设置为我们的TextureView
updateTransform()
}
//绑定LifeCycle
CameraX.bindToLifecycle(this, preview,takePhotoListener(),gainAnalyzer())
}
/**
* 根据TextureView重新计算预览尺寸
*/
private fun updateTransform() {
val matrix = Matrix()
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
// 预览旋转输出
val rotationDegrees = when(viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> return
}
matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
// 将转换后的画面设置到TextureView
viewFinder.setTransform(matrix)
}
/**
* 拍照按钮点击监听
*/
private fun takePhotoListener(): ImageCapture{
val imageCaptureConfig = ImageCaptureConfig.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.build()
val imageCapture = ImageCapture(imageCaptureConfig)
capture_button.setOnClickListener {
imageCapture.setTargetAspectRatioCustom(Rational(9,16))//设置拍照保存尺寸
imageCapture.takePicture(getPathForFile(), executor,
object : ImageCapture.OnImageSavedListener {
override fun onError(
imageCaptureError: ImageCapture.ImageCaptureError,
message: String,
exc: Throwable?
) {
val msg = "拍照失败: $message"
// Log.e("CameraXApp", msg, exc)
viewFinder.post {
toast("拍照失败")
}
}
override fun onImageSaved(file: File) {
// val msg = "相片保存在: ${file.absolutePath}"
viewFinder.post {
toast("拍照成功")
}
}
})
}
return imageCapture
}
/**
* 获取拍照储存路径
*/
private fun getPathForFile(): File {
val mdks = File(externalCacheDir,"takePhoto")
if (!mdks.exists())
mdks.mkdirs()
val photoFile = File(mdks,"${System.currentTimeMillis()}.jpg")
if (photoFile.exists())
photoFile.delete()
photoFile.createNewFile()
return photoFile
}
private class LuminosityAnalyzer : ImageAnalysis.Analyzer {
private var lastAnalyzedTimestamp = 0L
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // 将缓冲区倒回零
val data = ByteArray(remaining())
get(data) // 将缓冲区复制到字节数组中
return data // 返回字节数组
}
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimestamp = System.currentTimeMillis()
// 计算平均流明的频率不超过每秒一次
if (currentTimestamp - lastAnalyzedTimestamp >=
TimeUnit.SECONDS.toMillis(1)) {
val buffer = image.planes[0].buffer
// 从回调对象中提取图像数据
val data = buffer.toByteArray()
// 将数据转换为像素值数组
val pixels = data.map { it.toInt() and 0xFF }
// 计算图像的平均亮度
val luma = pixels.average()
// Log the new luma value
// Log.d("CameraXApp", "平均亮度: $luma")
// 更新最后分析帧的时间戳
lastAnalyzedTimestamp = currentTimestamp
}
}
}
/**
* 画面分析器
* 此分析器可分析出亮度
* 和解决预览画面扭曲
*/
private fun gainAnalyzer(): UseCase? {
val analyzerConfig = ImageAnalysisConfig.Builder().apply {//分析器配置构建
setImageReaderMode(
ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
//构建图像分析用例并实例化我们的分析器
val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
setAnalyzer(executor, LuminosityAnalyzer())
}
return analyzerUseCase//返回分析器
}
}
网友评论