实时视频UI
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.example.defenseapplication.liveVideo
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.example.defenseapplication.databinding.LiveVideoActivityBinding
|
||||
|
||||
class LiveVideoActivity : AppCompatActivity() {
|
||||
private lateinit var binding: LiveVideoActivityBinding
|
||||
|
||||
private var isVoiceEnabled = false
|
||||
private var isLightOn = false
|
||||
private var isRecording = false
|
||||
private var recordTime = 0
|
||||
private var recordHandler: Handler? = null
|
||||
private var recordRunnable: Runnable? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = LiveVideoActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
initListener()
|
||||
}
|
||||
|
||||
private fun initListener() {
|
||||
// 返回按钮
|
||||
binding.btnBack.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
// 菜单按钮
|
||||
binding.ivMenu.setOnClickListener {
|
||||
Toast.makeText(this, "菜单", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// 全屏按钮
|
||||
binding.ivFullscreen.setOnClickListener {
|
||||
Toast.makeText(this, "全屏", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// 语音按钮
|
||||
binding.llVoice.setOnClickListener {
|
||||
toggleVoice()
|
||||
}
|
||||
|
||||
// 灯光按钮
|
||||
binding.llLight.setOnClickListener {
|
||||
toggleLight()
|
||||
}
|
||||
|
||||
// 录像按钮
|
||||
binding.llRecord.setOnClickListener {
|
||||
toggleRecording()
|
||||
}
|
||||
|
||||
// 告警日志区域
|
||||
binding.llAlarmLog.setOnClickListener {
|
||||
Toast.makeText(this, "查看告警日志", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleVoice() {
|
||||
isVoiceEnabled = !isVoiceEnabled
|
||||
|
||||
if (isVoiceEnabled) {
|
||||
binding.vVoiceActive.visibility = View.VISIBLE
|
||||
binding.ivVoice.setColorFilter(resources.getColor(com.example.defenseapplication.R.color.green))
|
||||
Toast.makeText(this, "语音已开启", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
binding.vVoiceActive.visibility = View.INVISIBLE
|
||||
binding.ivVoice.clearColorFilter()
|
||||
Toast.makeText(this, "语音已关闭", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleLight() {
|
||||
isLightOn = !isLightOn
|
||||
|
||||
if (isLightOn) {
|
||||
binding.ivLight.setImageResource(com.example.defenseapplication.R.drawable.light_on)
|
||||
binding.ivLight.setColorFilter(resources.getColor(com.example.defenseapplication.R.color.green))
|
||||
Toast.makeText(this, "灯光已开启", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
binding.ivLight.setImageResource(com.example.defenseapplication.R.drawable.low_light)
|
||||
binding.ivLight.clearColorFilter()
|
||||
Toast.makeText(this, "灯光已关闭", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleRecording() {
|
||||
isRecording = !isRecording
|
||||
|
||||
if (isRecording) {
|
||||
recordTime = 0
|
||||
binding.tvRecordTime.text = "00"
|
||||
startRecordTimer()
|
||||
binding.ivRecord.setColorFilter(resources.getColor(com.example.defenseapplication.R.color.red))
|
||||
Toast.makeText(this, "开始录像", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
stopRecordTimer()
|
||||
binding.ivRecord.clearColorFilter()
|
||||
Toast.makeText(this, "停止录像,共录制 ${recordTime} 秒", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRecordTimer() {
|
||||
recordHandler = Handler(Looper.getMainLooper())
|
||||
recordRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
recordTime++
|
||||
binding.tvRecordTime.text = String.format("%02d", recordTime)
|
||||
recordHandler?.postDelayed(this, 1000)
|
||||
}
|
||||
}
|
||||
recordHandler?.post(recordRunnable!!)
|
||||
}
|
||||
|
||||
private fun stopRecordTimer() {
|
||||
recordRunnable?.let { recordHandler?.removeCallbacks(it) }
|
||||
recordHandler = null
|
||||
recordRunnable = null
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
stopRecordTimer()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user