实时视频UI

This commit is contained in:
2026-05-06 10:34:07 +08:00
parent 4e714ee7b6
commit a3115b6b86
25 changed files with 1212 additions and 1 deletions

View File

@@ -75,7 +75,7 @@ class HomeMenuFragment : Fragment() {
startActivity(intent)
}
binding.addFamily.setOnClickListener {
val intent = Intent(requireContext(), AddFamilyActivity::class.java)
val intent = Intent(requireContext(), ScanActivity::class.java)
startActivity(intent)
}
}

View File

@@ -0,0 +1,291 @@
package com.example.defenseapplication.home
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.ImageFormat
import android.graphics.Matrix
import android.graphics.RectF
import android.graphics.SurfaceTexture
import android.hardware.Camera
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.view.animation.TranslateAnimation
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.defenseapplication.R
import com.example.defenseapplication.databinding.ScanActivityBinding
import com.google.zxing.*
import com.google.zxing.common.HybridBinarizer
import java.util.*
class ScanActivity : AppCompatActivity(), SurfaceHolder.Callback {
private lateinit var binding: ScanActivityBinding
private var camera: Camera? = null
private var isFlashlightOn = false
private var scanHandler: Handler? = null
private var scanRunnable: Runnable? = null
private val REQUEST_CODE_CAMERA = 100
private val REQUEST_CODE_ALBUM = 101
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ScanActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initListener()
startScanLineAnimation()
}
private fun initView() {
binding.surfaceView.holder.addCallback(this)
}
private fun initListener() {
binding.btnBack.setOnClickListener {
finish()
}
binding.llAlbum.setOnClickListener {
openAlbum()
}
binding.llFlashlight.setOnClickListener {
toggleFlashlight()
}
}
private fun startScanLineAnimation() {
val animation = TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0f,
Animation.RELATIVE_TO_PARENT, 0f,
Animation.RELATIVE_TO_PARENT, 0f,
Animation.RELATIVE_TO_PARENT, 1f
)
animation.duration = 2000
animation.repeatCount = Animation.INFINITE
animation.interpolator = LinearInterpolator()
binding.scanLine.startAnimation(animation)
}
private fun openAlbum() {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(intent, REQUEST_CODE_ALBUM)
}
private fun toggleFlashlight() {
if (camera == null) {
Toast.makeText(this, "相机未准备好", Toast.LENGTH_SHORT).show()
return
}
isFlashlightOn = !isFlashlightOn
val params = camera?.parameters
if (isFlashlightOn) {
params?.flashMode = Camera.Parameters.FLASH_MODE_TORCH
binding.ivFlashlight.setColorFilter(ContextCompat.getColor(this, R.color.green))
} else {
params?.flashMode = Camera.Parameters.FLASH_MODE_OFF
binding.ivFlashlight.clearColorFilter()
}
camera?.parameters = params
}
private fun checkCameraPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
}
private fun requestCameraPermission() {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_CAMERA
)
}
private fun openCamera() {
if (!checkCameraPermission()) {
requestCameraPermission()
return
}
try {
camera = Camera.open()
camera?.setDisplayOrientation(90)
val params = camera?.parameters
params?.previewFormat = ImageFormat.NV21
val sizes = params?.supportedPreviewSizes
val optimalSize = getOptimalPreviewSize(sizes, binding.surfaceView.width, binding.surfaceView.height)
if (optimalSize != null) {
params?.setPreviewSize(optimalSize.width, optimalSize.height)
}
camera?.parameters = params
camera?.setPreviewDisplay(binding.surfaceView.holder)
camera?.startPreview()
startScanDecode()
} catch (e: Exception) {
Toast.makeText(this, "无法打开相机", Toast.LENGTH_SHORT).show()
}
}
private fun getOptimalPreviewSize(sizes: List<Camera.Size>?, w: Int, h: Int): Camera.Size? {
if (sizes == null) return null
var optimalSize: Camera.Size? = null
val targetRatio = h.toDouble() / w.toDouble()
for (size in sizes) {
val ratio = size.width.toDouble() / size.height.toDouble()
if (Math.abs(ratio - targetRatio) < 0.01) {
optimalSize = size
break
}
}
if (optimalSize == null) {
var minDiff = Int.MAX_VALUE
for (size in sizes) {
val diff = Math.abs(size.width - w) + Math.abs(size.height - h)
if (diff < minDiff) {
optimalSize = size
minDiff = diff
}
}
}
return optimalSize
}
private fun startScanDecode() {
scanHandler = Handler(Looper.getMainLooper())
scanRunnable = object : Runnable {
override fun run() {
decodeQRCode()
scanHandler?.postDelayed(this, 100)
}
}
scanHandler?.post(scanRunnable!!)
}
private fun decodeQRCode() {
if (camera == null) return
try {
val parameters = camera?.parameters
val size = parameters?.previewSize
if (size == null) return
val data = ByteArray(size.width * size.height * 3 / 2)
camera?.setOneShotPreviewCallback { bytes, _ ->
val source = PlanarYUVLuminanceSource(
bytes, size.width, size.height, 0, 0,
size.width, size.height, false
)
val binarizer = HybridBinarizer(source)
val bitmap = BinaryBitmap(binarizer)
val reader = MultiFormatReader()
val hints = Hashtable<DecodeHintType, Any>()
hints[DecodeHintType.CHARACTER_SET] = "UTF-8"
reader.setHints(hints)
try {
val result = reader.decode(bitmap)
handleScanResult(result.text)
} catch (e: Exception) {
}
}
} catch (e: Exception) {
}
}
private fun handleScanResult(result: String) {
stopScanDecode()
camera?.stopPreview()
Toast.makeText(this, "扫描结果: $result", Toast.LENGTH_SHORT).show()
val intent = Intent(this, AddFamilyActivity::class.java)
intent.putExtra("device_code", result)
startActivity(intent)
finish()
}
private fun stopScanDecode() {
scanRunnable?.let { scanHandler?.removeCallbacks(it) }
scanHandler = null
scanRunnable = null
}
override fun surfaceCreated(holder: SurfaceHolder) {
openCamera()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
if (camera != null) {
camera?.stopPreview()
try {
camera?.setPreviewDisplay(holder)
camera?.startPreview()
} catch (e: Exception) {
}
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
stopScanDecode()
camera?.stopPreview()
camera?.release()
camera = null
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_CAMERA) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera()
} else {
Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show()
finish()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_ALBUM && resultCode == RESULT_OK && data != null) {
val uri = data.data
uri?.let {
Toast.makeText(this, "已选择图片", Toast.LENGTH_SHORT).show()
}
}
}
override fun onDestroy() {
super.onDestroy()
stopScanDecode()
camera?.stopPreview()
camera?.release()
camera = null
}
}