refactor(spraying): 替换相机预览实现为系统媒体服务

- 移除 CameraPreviewPocActivity 及其相关布局文件
- 集成 GlassMediaServiceHelper 替代原有的相机预览功能
- 使用 PhotoFileCallback 处理拍照回调逻辑
- 更新拍照流程以支持异步回调处理
- 删除旧的 onActivityResult 处理代码
- 修改离线命令配置移除相机操作相关指令
这个提交包含在:
徐勤民 2026-04-21 17:18:17 +08:00
父节点 8258d77f1f
当前提交 56da0f5899
共有 5 个文件被更改,包括 53 次插入552 次删除

查看文件

@ -90,9 +90,6 @@
<activity <activity
android:name=".ui.InspectionCompleteActivity" android:name=".ui.InspectionCompleteActivity"
android:exported="false" /> android:exported="false" />
<activity
android:name=".ui.CameraPreviewPocActivity"
android:exported="false" />
<activity <activity
android:name=".ui.SprayingActivity" android:name=".ui.SprayingActivity"
android:exported="false" /> android:exported="false" />

查看文件

@ -45,14 +45,7 @@ object OfflineCmdServiceHelper {
) )
private val CMDS_SPRAYING = listOf( private val CMDS_SPRAYING = listOf(
OfflineCmdBean("开始", "kai shi"), OfflineCmdBean("开始", "kai shi"),
OfflineCmdBean("开始任务", "kai shi ren wu"), OfflineCmdBean("开始任务", "kai shi ren wu")
OfflineCmdBean("拍照", "pai zhao"),
OfflineCmdBean("拍摄", "pai she"),
OfflineCmdBean("放大", "fang da"),
OfflineCmdBean("拉近", "la jin"),
OfflineCmdBean("缩小", "suo xiao"),
OfflineCmdBean("拉远", "la yuan"),
OfflineCmdBean("取消", "qu xiao")
) )
private val CMDS_SPRAYING_FINISH = listOf( private val CMDS_SPRAYING_FINISH = listOf(
OfflineCmdBean("补充照片", "bu chong zhao pian"), OfflineCmdBean("补充照片", "bu chong zhao pian"),

查看文件

@ -1,407 +0,0 @@
package com.nova.brain.glass.ui
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.ImageFormat
import android.graphics.Rect
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.TotalCaptureResult
import android.hardware.camera2.params.StreamConfigurationMap
import android.media.ImageReader
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.HandlerThread
import android.view.KeyEvent
import android.view.Surface
import android.view.TextureView
import androidx.core.app.ActivityCompat
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityCameraPreviewPocBinding
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.xuqm.base.extensions.showMessage
import com.xuqm.base.ui.BaseActivity
import java.io.File
import java.io.FileOutputStream
import java.util.UUID
import kotlin.math.roundToInt
class CameraPreviewPocActivity : BaseActivity<ActivityCameraPreviewPocBinding>() {
companion object {
const val EXTRA_PHOTO_PATH = "extra_photo_path"
private const val REQUEST_CAMERA_PERMISSION = 1001
}
override fun getLayoutId(): Int = R.layout.activity_camera_preview_poc
override fun fullscreen(): Boolean = true
private val cameraManager by lazy { getSystemService(CameraManager::class.java) }
private var cameraDevice: CameraDevice? = null
private var captureSession: CameraCaptureSession? = null
private var previewRequestBuilder: CaptureRequest.Builder? = null
private var imageReader: ImageReader? = null
private var backgroundThread: HandlerThread? = null
private var backgroundHandler: Handler? = null
private var activeArraySize: Rect? = null
private var maxZoom = 1f
private var zoomLevel = 1f
private var currentOutputPath: String? = null
private var isCapturing = false
private val offlineCmdListener = object : OfflineCmdListener {
override fun onOfflineCmd(cmd: String) {
runOnUiThread {
when (cmd) {
"退出", "返回", "退回", "取消" -> finish()
"拍照", "拍摄", "开始", "开始任务" -> captureStillImage()
"放大", "拉近" -> adjustZoom(0.2f)
"缩小", "拉远" -> adjustZoom(-0.2f)
}
}
}
}
private val textureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
openCamera()
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) = Unit
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean = true
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) = Unit
}
override fun initView(savedInstanceState: Bundle?) {
super.initView(savedInstanceState)
binding.previewView.surfaceTextureListener = textureListener
binding.captureButton.setOnClickListener { captureStillImage() }
binding.zoomInButton.setOnClickListener { adjustZoom(0.2f) }
binding.zoomOutButton.setOnClickListener { adjustZoom(-0.2f) }
updateZoomText()
}
override fun onResume() {
super.onResume()
startBackgroundThread()
OfflineCmdServiceHelper.addOnLineListener(offlineCmdListener)
OfflineCmdServiceHelper.addListenerSpraying()
if (binding.previewView.isAvailable) {
openCamera()
} else {
binding.previewView.surfaceTextureListener = textureListener
}
}
override fun onPause() {
closeCamera()
OfflineCmdServiceHelper.removeListenerSpraying()
OfflineCmdServiceHelper.removeOnLineListener(offlineCmdListener)
stopBackgroundThread()
super.onPause()
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action != KeyEvent.ACTION_DOWN) return super.dispatchKeyEvent(event)
return when (event.keyCode) {
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_VOLUME_DOWN -> {
adjustZoom(-0.2f)
true
}
KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_EQUALS, KeyEvent.KEYCODE_VOLUME_UP -> {
adjustZoom(0.2f)
true
}
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_CAMERA -> {
captureStillImage()
true
}
else -> super.dispatchKeyEvent(event)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode != REQUEST_CAMERA_PERMISSION) return
if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
openCamera()
} else {
"缺少相机权限,无法验证预览".showMessage()
finish()
}
}
private fun openCamera() {
if (
ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO),
REQUEST_CAMERA_PERMISSION
)
return
}
try {
val selectedCameraId = selectBackCameraId() ?: run {
"未找到可用相机".showMessage()
finish()
return
}
val characteristics = cameraManager.getCameraCharacteristics(selectedCameraId)
activeArraySize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
maxZoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 1f)
.coerceAtLeast(1f)
zoomLevel = zoomLevel.coerceIn(1f, maxZoom)
val streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val previewSize = choosePreviewSize(streamMap)
val captureSize = chooseCaptureSize(streamMap)
val texture = binding.previewView.surfaceTexture ?: return
texture.setDefaultBufferSize(previewSize.width, previewSize.height)
imageReader?.close()
imageReader = ImageReader.newInstance(captureSize.width, captureSize.height, ImageFormat.JPEG, 2).apply {
setOnImageAvailableListener({ reader ->
val image = reader.acquireNextImage() ?: return@setOnImageAvailableListener
val outputPath = currentOutputPath
if (outputPath == null) {
image.close()
return@setOnImageAvailableListener
}
val buffer = image.planes.firstOrNull()?.buffer
val bytes = ByteArray(buffer?.remaining() ?: 0)
buffer?.get(bytes)
image.close()
runCatching {
FileOutputStream(outputPath).use { it.write(bytes) }
}.onSuccess {
runOnUiThread {
setResult(RESULT_OK, Intent().putExtra(EXTRA_PHOTO_PATH, outputPath))
finish()
}
}.onFailure {
isCapturing = false
runOnUiThread {
binding.statusText.text = "图片保存失败: ${it.message ?: "未知错误"}"
}
}
}, backgroundHandler)
}
binding.statusText.text = "相机预览验证中,可拉近拉远后拍照"
cameraManager.openCamera(selectedCameraId, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
cameraDevice = device
createPreviewSession(texture)
}
override fun onDisconnected(device: CameraDevice) {
device.close()
cameraDevice = null
runOnUiThread { binding.statusText.text = "相机已断开" }
}
override fun onError(device: CameraDevice, error: Int) {
device.close()
cameraDevice = null
runOnUiThread { binding.statusText.text = "相机打开失败: $error" }
}
}, backgroundHandler)
} catch (securityException: SecurityException) {
binding.statusText.text = "缺少相机权限"
"缺少相机权限,无法打开预览".showMessage()
}
}
private fun createPreviewSession(texture: SurfaceTexture) {
val device = cameraDevice ?: return
val previewSurface = Surface(texture)
previewRequestBuilder = try {
device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
addTarget(previewSurface)
set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
applyZoom(this)
}
} catch (e: CameraAccessException) {
handleCameraFailure("相机预览初始化失败: ${e.message}")
return
} catch (e: IllegalStateException) {
handleCameraFailure("相机设备不可用: ${e.message}")
return
}
val readerSurface = imageReader?.surface ?: return
try {
device.createCaptureSession(
listOf(previewSurface, readerSurface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
captureSession = session
val request = previewRequestBuilder?.build() ?: return
try {
session.setRepeatingRequest(request, null, backgroundHandler)
} catch (e: CameraAccessException) {
handleCameraFailure("相机预览启动失败: ${e.message}")
} catch (e: IllegalStateException) {
handleCameraFailure("相机会话不可用: ${e.message}")
}
}
override fun onConfigureFailed(session: CameraCaptureSession) {
handleCameraFailure("相机预览配置失败")
}
},
backgroundHandler
)
} catch (e: CameraAccessException) {
handleCameraFailure("相机会话创建失败: ${e.message}")
} catch (e: IllegalStateException) {
handleCameraFailure("相机会话不可用: ${e.message}")
}
}
private fun captureStillImage() {
if (isCapturing) return
val device = cameraDevice ?: return
val session = captureSession ?: return
val readerSurface = imageReader?.surface ?: return
val outputPath = createOutputFile().absolutePath
currentOutputPath = outputPath
isCapturing = true
binding.statusText.text = "拍照中,请保持稳定..."
try {
val captureBuilder = device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
addTarget(readerSurface)
set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
applyZoom(this)
set(CaptureRequest.JPEG_ORIENTATION, 0)
}
session.stopRepeating()
session.capture(captureBuilder.build(), object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
previewRequestBuilder?.build()?.let {
runCatching {
session.setRepeatingRequest(it, null, backgroundHandler)
}
}
}
}, backgroundHandler)
} catch (e: CameraAccessException) {
isCapturing = false
handleCameraFailure("拍照失败: ${e.message}")
} catch (e: IllegalStateException) {
isCapturing = false
handleCameraFailure("相机设备不可用: ${e.message}")
}
}
private fun adjustZoom(delta: Float) {
maxZoom = maxZoom.coerceAtLeast(1f)
zoomLevel = (zoomLevel + delta).coerceIn(1f, maxZoom)
updateZoomText()
val requestBuilder = previewRequestBuilder ?: return
val session = captureSession ?: return
applyZoom(requestBuilder)
try {
session.setRepeatingRequest(requestBuilder.build(), null, backgroundHandler)
} catch (e: CameraAccessException) {
handleCameraFailure("缩放失败: ${e.message}")
} catch (e: IllegalStateException) {
handleCameraFailure("相机会话不可用: ${e.message}")
}
}
private fun updateZoomText() {
binding.zoomText.text = "缩放 ${String.format("%.1f", zoomLevel)}x"
}
private fun applyZoom(builder: CaptureRequest.Builder) {
val sensorRect = activeArraySize ?: return
if (zoomLevel <= 1f) {
builder.set(CaptureRequest.SCALER_CROP_REGION, sensorRect)
return
}
val centerX = sensorRect.centerX()
val centerY = sensorRect.centerY()
val deltaX = (0.5f * sensorRect.width() / zoomLevel).roundToInt()
val deltaY = (0.5f * sensorRect.height() / zoomLevel).roundToInt()
val cropRect = Rect(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY)
builder.set(CaptureRequest.SCALER_CROP_REGION, cropRect)
}
private fun selectBackCameraId(): String? {
val ids = cameraManager.cameraIdList ?: return null
return ids.firstOrNull { id ->
val facing = cameraManager.getCameraCharacteristics(id)
.get(CameraCharacteristics.LENS_FACING)
facing == CameraCharacteristics.LENS_FACING_BACK
} ?: ids.firstOrNull()
}
private fun choosePreviewSize(map: StreamConfigurationMap?): android.util.Size {
val sizes = map?.getOutputSizes(SurfaceTexture::class.java).orEmpty()
return sizes.maxByOrNull { it.width * it.height } ?: android.util.Size(1920, 1080)
}
private fun chooseCaptureSize(map: StreamConfigurationMap?): android.util.Size {
val sizes = map?.getOutputSizes(ImageFormat.JPEG).orEmpty()
return sizes.maxByOrNull { it.width * it.height } ?: android.util.Size(1920, 1080)
}
private fun createOutputFile(): File {
val publicPicturesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
if (!publicPicturesDir.exists()) {
publicPicturesDir.mkdirs()
}
return File(publicPicturesDir, "camera_poc_${UUID.randomUUID()}.jpg")
}
private fun closeCamera() {
captureSession?.close()
captureSession = null
cameraDevice?.close()
cameraDevice = null
imageReader?.close()
imageReader = null
isCapturing = false
currentOutputPath = null
}
private fun handleCameraFailure(message: String) {
closeCamera()
runOnUiThread {
binding.statusText.text = message
message.showMessage()
setResult(RESULT_CANCELED)
finish()
}
}
private fun startBackgroundThread() {
if (backgroundThread != null) return
backgroundThread = HandlerThread("camera-preview-poc").also { it.start() }
backgroundHandler = Handler(backgroundThread!!.looper)
}
private fun stopBackgroundThread() {
backgroundThread?.quitSafely()
backgroundThread?.join()
backgroundThread = null
backgroundHandler = null
}
}

查看文件

@ -1,19 +1,26 @@
package com.nova.brain.glass.ui package com.nova.brain.glass.ui
import android.content.Intent import android.content.Intent
import android.os.Environment
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivitySprayingBinding import com.nova.brain.glass.databinding.ActivitySprayingBinding
import com.nova.brain.glass.helper.GlassMediaServiceHelper
import com.nova.brain.glass.helper.OfflineCmdListener import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.helper.SprayingPhotoManager import com.nova.brain.glass.helper.SprayingPhotoManager
import com.nova.brain.glass.model.ItemItem import com.nova.brain.glass.model.ItemItem
import com.nova.brain.glass.viewmodel.SprayingVM import com.nova.brain.glass.viewmodel.SprayingVM
import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution
import com.rokid.security.system.server.media.callback.PhotoFileCallback
import com.xuqm.base.adapter.BasePagedAdapter import com.xuqm.base.adapter.BasePagedAdapter
import com.xuqm.base.adapter.CommonPagedAdapter import com.xuqm.base.adapter.CommonPagedAdapter
import com.xuqm.base.adapter.ViewHolder import com.xuqm.base.adapter.ViewHolder
import com.xuqm.base.common.LogHelper
import com.xuqm.base.extensions.showMessage import com.xuqm.base.extensions.showMessage
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import java.io.File
import java.util.UUID
class SprayingActivity : class SprayingActivity :
BaseListFormLayoutNormalActivity<ItemItem, SprayingVM, ActivitySprayingBinding>() { BaseListFormLayoutNormalActivity<ItemItem, SprayingVM, ActivitySprayingBinding>() {
@ -28,10 +35,6 @@ class SprayingActivity :
} }
private var productionInfoId: String = "" private var productionInfoId: String = ""
companion object {
private const val REQUEST_CAMERA_PREVIEW = 2001
}
private val listener = object : OfflineCmdListener { private val listener = object : OfflineCmdListener {
override fun onOfflineCmd(cmd: String) { override fun onOfflineCmd(cmd: String) {
runOnUiThread { runOnUiThread {
@ -45,6 +48,7 @@ class SprayingActivity :
binding.hint.text = "拍照中,请稍后..." binding.hint.text = "拍照中,请稍后..."
} }
SprayingPhotoManager.clear() SprayingPhotoManager.clear()
isPhoto = true
takePhoto() takePhoto()
} }
} }
@ -54,10 +58,46 @@ class SprayingActivity :
} }
fun takePhoto() { fun takePhoto() {
startActivityForResult( val fileName = "test_${System.currentTimeMillis()}.png"
Intent(this, CameraPreviewPocActivity::class.java), val publicPicturesDir =
REQUEST_CAMERA_PREVIEW Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
) val file = File(publicPicturesDir, fileName)
GlassMediaServiceHelper.takePhoto(PhotoResolution.RESOLUTION_1080P, file.absolutePath)
}
private val photoCallbackId = UUID.randomUUID().toString()
private val mPhotoFileCallback = object : PhotoFileCallback.Stub() {
override fun onTakePhoto(path: String) {
LogHelper.d("onTakePhoto-->path = $path")
}
override fun getCallbackId(): String {
return photoCallbackId
}
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
LogHelper.d("width:$width--height:$height")
if (path == null) {
if (isPhoto) {
isPhoto = false
takePhoto()
} else {
runOnUiThread {
binding.hint.text = "单击或语音输入“开始”,进入下一步"
}
"相机异常".showMessage()
}
} else {
SprayingPhotoManager.addPhoto(path)
startActivity(Intent(this@SprayingActivity, SprayingOCRActivity::class.java).apply {
putExtra("path", path)
putExtra("taskId", taskId)
putExtra("productionInfoId", productionInfoId)
})
finish()
}
}
} }
@ -83,6 +123,7 @@ class SprayingActivity :
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
GlassMediaServiceHelper.addPhotoCallback(mPhotoFileCallback)
OfflineCmdServiceHelper.addOnLineListener(listener) OfflineCmdServiceHelper.addOnLineListener(listener)
OfflineCmdServiceHelper.addListenerSpraying() OfflineCmdServiceHelper.addListenerSpraying()
} }
@ -91,28 +132,7 @@ class SprayingActivity :
super.onPause() super.onPause()
OfflineCmdServiceHelper.removeListenerSpraying() OfflineCmdServiceHelper.removeListenerSpraying()
OfflineCmdServiceHelper.removeOnLineListener(listener) OfflineCmdServiceHelper.removeOnLineListener(listener)
} GlassMediaServiceHelper.removePhotoCallback(mPhotoFileCallback)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != REQUEST_CAMERA_PREVIEW) return
if (resultCode != RESULT_OK) {
binding.hint.text = "单击或语音输入“开始”,进入下一步"
return
}
val path = data?.getStringExtra(CameraPreviewPocActivity.EXTRA_PHOTO_PATH)
if (path.isNullOrBlank()) {
binding.hint.text = "单击或语音输入“开始”,进入下一步"
"未获取到照片".showMessage()
return
}
SprayingPhotoManager.addPhoto(path)
startActivity(Intent(this, SprayingOCRActivity::class.java).apply {
putExtra("path", path)
putExtra("taskId", taskId)
putExtra("productionInfoId", productionInfoId)
})
finish()
} }
override fun onDestroy() { override fun onDestroy() {
@ -120,6 +140,7 @@ class SprayingActivity :
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} }
private var isPhoto = false
private val adapter = object : CommonPagedAdapter<ItemItem>(R.layout.item_photo) { private val adapter = object : CommonPagedAdapter<ItemItem>(R.layout.item_photo) {
override fun convert(holder: ViewHolder, item: ItemItem, position: Int) { override fun convert(holder: ViewHolder, item: ItemItem, position: Int) {
holder holder
@ -130,6 +151,7 @@ class SprayingActivity :
binding.hint.text = "拍照中,请稍后..." binding.hint.text = "拍照中,请稍后..."
} }
SprayingPhotoManager.clear() SprayingPhotoManager.clear()
isPhoto = true
takePhoto() takePhoto()
} }
} }

查看文件

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<TextureView
android:id="@+id/previewView"
android:layout_width="0dp"
android:layout_height="0dp"
android:keepScreenOn="true"
app:layout_constraintBottom_toTopOf="@+id/controlBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/statusText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_item"
android:gravity="center"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="相机预览验证中"
android:textColor="#FF40FF5E"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/zoomText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@drawable/bg_item"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="缩放 1.0x"
android:textColor="#FF40FF5E"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/statusText" />
<LinearLayout
android:id="@+id/controlBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@drawable/bg_task_title_selected"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/zoomOutButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_photo"
android:gravity="center"
android:paddingVertical="8dp"
android:text="拉远"
android:textColor="#FF40FF5E"
android:textSize="16sp" />
<TextView
android:id="@+id/captureButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_weight="1"
android:background="@drawable/bg_photo"
android:gravity="center"
android:paddingVertical="8dp"
android:text="拍照"
android:textColor="#FF40FF5E"
android:textSize="16sp" />
<TextView
android:id="@+id/zoomInButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_photo"
android:gravity="center"
android:paddingVertical="8dp"
android:text="拉近"
android:textColor="#FF40FF5E"
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>