Update Android app login, home, and face verification flows.
Sync current local UI and activity changes to remote repository. Made-with: Cursor
This commit is contained in:
@@ -5,17 +5,75 @@ import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.example.defenseapplication.R
|
||||
import com.example.defenseapplication.databinding.HomeActivityBinding
|
||||
import com.example.defenseapplication.databinding.HomeMenuFragmentBinding
|
||||
|
||||
class HomeActivity : AppCompatActivity() {
|
||||
// ViewBinding 核心对象
|
||||
private lateinit var binding: HomeActivityBinding
|
||||
|
||||
// 定义两个 Fragment
|
||||
private val homeMenuFragment = HomeMenuFragment()
|
||||
private val personalFragment = PersonalFragment()
|
||||
|
||||
// 当前显示的 Fragment
|
||||
private var currentFragment: Fragment = homeMenuFragment
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.home_activity)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
|
||||
// 初始化 ViewBinding
|
||||
binding = HomeActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
// 默认显示首页
|
||||
switchFragment(homeMenuFragment)
|
||||
// 第一次加载,只 add 首页
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.fragment_container, homeMenuFragment)
|
||||
.commit()
|
||||
// ========== 首页点击 ==========
|
||||
binding.tabHome.setOnClickListener {
|
||||
switchFragment(homeMenuFragment)
|
||||
// 切换图标 + 文字颜色
|
||||
binding.ivHome.setImageResource(R.drawable.home_checked)
|
||||
binding.tvHome.setTextColor(resources.getColor(R.color.ic_selected))
|
||||
|
||||
binding.ivProfile.setImageResource(R.drawable.profile)
|
||||
binding.tvProfile.setTextColor(resources.getColor(R.color.ic_normal))
|
||||
}
|
||||
|
||||
// ========== 个人点击 ==========
|
||||
binding.tabProfile.setOnClickListener {
|
||||
switchFragment(personalFragment)
|
||||
// 切换图标 + 文字颜色
|
||||
binding.ivHome.setImageResource(R.drawable.home)
|
||||
binding.tvHome.setTextColor(resources.getColor(R.color.ic_normal))
|
||||
|
||||
binding.ivProfile.setImageResource(R.drawable.profile_checked)
|
||||
binding.tvProfile.setTextColor(resources.getColor(R.color.ic_selected))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换 Fragment 的方法
|
||||
*/
|
||||
private fun switchFragment(fragment: Fragment) {
|
||||
if (currentFragment == fragment) return
|
||||
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
|
||||
// 如果没添加过,才 add
|
||||
if (!fragment.isAdded) {
|
||||
transaction.add(R.id.fragment_container, fragment)
|
||||
}
|
||||
|
||||
// 隐藏当前,显示目标
|
||||
transaction.hide(currentFragment)
|
||||
transaction.show(fragment)
|
||||
transaction.commit()
|
||||
|
||||
currentFragment = fragment
|
||||
}
|
||||
}
|
||||
@@ -6,55 +6,24 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.example.defenseapplication.R
|
||||
import com.example.defenseapplication.databinding.HomeMenuFragmentBinding
|
||||
|
||||
// TODO: Rename parameter arguments, choose names that match
|
||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
||||
private const val ARG_PARAM1 = "param1"
|
||||
private const val ARG_PARAM2 = "param2"
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* Use the [HomeMenuFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class HomeMenuFragment : Fragment() {
|
||||
// TODO: Rename and change types of parameters
|
||||
private var param1: String? = null
|
||||
private var param2: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
param1 = it.getString(ARG_PARAM1)
|
||||
param2 = it.getString(ARG_PARAM2)
|
||||
}
|
||||
}
|
||||
// ViewBinding
|
||||
private var _binding: HomeMenuFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.home_menu_fragment, container, false)
|
||||
): View {
|
||||
_binding = HomeMenuFragmentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
* this fragment using the provided parameters.
|
||||
*
|
||||
* @param param1 Parameter 1.
|
||||
* @param param2 Parameter 2.
|
||||
* @return A new instance of fragment HomeMenuFragment.
|
||||
*/
|
||||
// TODO: Rename and change types and number of parameters
|
||||
@JvmStatic
|
||||
fun newInstance(param1: String, param2: String) =
|
||||
HomeMenuFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(ARG_PARAM1, param1)
|
||||
putString(ARG_PARAM2, param2)
|
||||
}
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null // 避免内存泄漏
|
||||
}
|
||||
}
|
||||
@@ -6,55 +6,24 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.example.defenseapplication.R
|
||||
import com.example.defenseapplication.databinding.PersonalFragmentBinding
|
||||
|
||||
// TODO: Rename parameter arguments, choose names that match
|
||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
||||
private const val ARG_PARAM1 = "param1"
|
||||
private const val ARG_PARAM2 = "param2"
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* Use the [PersonalFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class PersonalFragment : Fragment() {
|
||||
// TODO: Rename and change types of parameters
|
||||
private var param1: String? = null
|
||||
private var param2: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
param1 = it.getString(ARG_PARAM1)
|
||||
param2 = it.getString(ARG_PARAM2)
|
||||
}
|
||||
}
|
||||
// ViewBinding
|
||||
private var _binding: PersonalFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.personal_fragment, container, false)
|
||||
): View {
|
||||
_binding = PersonalFragmentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
* this fragment using the provided parameters.
|
||||
*
|
||||
* @param param1 Parameter 1.
|
||||
* @param param2 Parameter 2.
|
||||
* @return A new instance of fragment PersonalFragment.
|
||||
*/
|
||||
// TODO: Rename and change types and number of parameters
|
||||
@JvmStatic
|
||||
fun newInstance(param1: String, param2: String) =
|
||||
PersonalFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(ARG_PARAM1, param1)
|
||||
putString(ARG_PARAM2, param2)
|
||||
}
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null // 避免内存泄漏
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,39 @@
|
||||
package com.example.defenseapplication.login
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.example.defenseapplication.R
|
||||
import com.example.defenseapplication.databinding.FaceLoginActivityBinding
|
||||
|
||||
class FaceLoginActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: FaceLoginActivityBinding
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.face_login_activity)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
binding = FaceLoginActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setupListeners()
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
// 返回按钮
|
||||
binding.backIcon.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
// 继续按钮
|
||||
binding.btnStartVerification.setOnClickListener {
|
||||
val intent= Intent(this, FaceVerificationActivity::class.java)
|
||||
startActivity(intent)
|
||||
Toast.makeText(this, "跳转到人脸识别页面", Toast.LENGTH_SHORT).show()
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,110 @@
|
||||
package com.example.defenseapplication.login
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.example.defenseapplication.R
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.defenseapplication.databinding.FaceVerificationActivityBinding
|
||||
import com.example.defenseapplication.view.FaceAnalyzer
|
||||
import com.google.mlkit.vision.face.FaceDetection
|
||||
import com.google.mlkit.vision.face.FaceDetectorOptions
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class FaceVerificationActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: FaceVerificationActivityBinding
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private var faceDetector: com.google.mlkit.vision.face.FaceDetector? = null
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CAMERA_PERMISSION = 100
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.face_verification_activity)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
// 使用标准 ViewBinding
|
||||
binding = FaceVerificationActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// 初始化人脸检测器(选项可自行调整)
|
||||
val options = FaceDetectorOptions.Builder()
|
||||
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
|
||||
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_NONE)
|
||||
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
|
||||
.build()
|
||||
faceDetector = FaceDetection.getClient(options)
|
||||
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
// 检查并请求权限
|
||||
checkCameraPermissionAndStart()
|
||||
}
|
||||
|
||||
private fun checkCameraPermissionAndStart() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
startCamera()
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(Manifest.permission.CAMERA),
|
||||
REQUEST_CAMERA_PERMISSION
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// 预览用例
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(binding.previewView.surfaceProvider)
|
||||
}
|
||||
|
||||
// 分析用例(人脸检测)
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
imageAnalysis.setAnalyzer(cameraExecutor, FaceAnalyzer(faceDetector, binding.overlay))
|
||||
|
||||
// 选择后置摄像头
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
// 绑定到生命周期
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
|
||||
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startCamera()
|
||||
} else {
|
||||
Toast.makeText(this, "需要相机权限才能进行人脸验证", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
faceDetector?.close()
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.example.defenseapplication.R
|
||||
import com.example.defenseapplication.databinding.ForgetPasswordActivityBinding
|
||||
import com.example.defenseapplication.okhttp.OkHttpClientManager
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -79,20 +78,6 @@ class ForgetPasswordActivity : AppCompatActivity() {
|
||||
isSendingCode = true
|
||||
binding.sendCodeBtn.isEnabled = false
|
||||
|
||||
OkHttpClientManager.postForm(
|
||||
url = "https://httpbin.org/post",
|
||||
params = mapOf("phone" to phoneNumber)
|
||||
) { result ->
|
||||
result.onSuccess {
|
||||
verifyCode = (100000 + Random.nextInt(900000)).toString()
|
||||
startCountDown()
|
||||
Toast.makeText(this, "验证码已发送(演示码:$verifyCode)", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure {
|
||||
Toast.makeText(this@ForgetPasswordActivity, "发送失败,请重试", Toast.LENGTH_SHORT).show()
|
||||
resetSendButton()
|
||||
}
|
||||
isSendingCode = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,20 +166,7 @@ class ForgetPasswordActivity : AppCompatActivity() {
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
OkHttpClientManager.postJson(
|
||||
url = "https://httpbin.org/post",
|
||||
json = requestJson
|
||||
) { result ->
|
||||
result.onSuccess {
|
||||
Toast.makeText(this, "密码重置成功,请重新登录", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}.onFailure {
|
||||
Toast.makeText(this, "网络异常,请重试", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
binding.btnNext.isEnabled = true
|
||||
binding.btnNext.text = "确定"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.example.defenseapplication.login
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.example.defenseapplication.databinding.RegisterActivityBinding
|
||||
|
||||
@@ -31,6 +33,9 @@ class RegisterActivity : AppCompatActivity() {
|
||||
// 下一步按钮
|
||||
binding.btnNext.setOnClickListener {
|
||||
if (!isLoading) {
|
||||
val intent= Intent(this, FaceLoginActivity::class.java)
|
||||
startActivity(intent)
|
||||
Toast.makeText(this, "跳转到人脸页面", Toast.LENGTH_SHORT).show()
|
||||
//performRegistration()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,63 @@
|
||||
package com.example.defenseapplication.login
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.example.defenseapplication.R
|
||||
import com.example.defenseapplication.databinding.RegisterSuccessActivityBinding
|
||||
import com.example.defenseapplication.home.HomeActivity
|
||||
|
||||
class RegisterSuccessActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: RegisterSuccessActivityBinding
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.register_success_activity)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
binding = RegisterSuccessActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
// 返回按钮
|
||||
binding.backIcon.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
// 点击按钮:取消倒计时并立即跳转
|
||||
binding.btnEnter.setOnClickListener {
|
||||
cancelTimer()
|
||||
jumpToPlatform()
|
||||
}
|
||||
// 启动3秒倒计时,自动跳转
|
||||
startAutoJumpTimer()
|
||||
}
|
||||
|
||||
private fun startAutoJumpTimer() {
|
||||
countDownTimer = object : CountDownTimer(3000L, 1000L) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
val seconds = (millisUntilFinished / 1000).toInt()
|
||||
binding.btnEnter.text = "进入平台(${seconds}S)"
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
binding.btnEnter.text = "进入平台(0S)"
|
||||
jumpToPlatform()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun cancelTimer() {
|
||||
countDownTimer?.cancel()
|
||||
countDownTimer = null
|
||||
}
|
||||
|
||||
private fun jumpToPlatform() {
|
||||
startActivity(Intent(this, HomeActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cancelTimer()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,44 @@
|
||||
package com.example.defenseapplication.view
|
||||
|
||||
class FaceAnalyzer {
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.camera.core.ExperimentalGetImage
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.google.mlkit.vision.face.FaceDetector
|
||||
/*
|
||||
* 人脸分析器
|
||||
* */
|
||||
@ExperimentalGetImage
|
||||
class FaceAnalyzer(
|
||||
private val detector: FaceDetector?,
|
||||
private val overlayView: FaceOverlayView
|
||||
) : ImageAnalysis.Analyzer {
|
||||
|
||||
override fun analyze(imageProxy: ImageProxy) {
|
||||
if (detector == null) {
|
||||
imageProxy.close()
|
||||
return
|
||||
}
|
||||
val mediaImage = imageProxy.image ?: run {
|
||||
imageProxy.close()
|
||||
return
|
||||
}
|
||||
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
||||
|
||||
detector.process(image)
|
||||
.addOnSuccessListener { faces ->
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
overlayView.setFaces(faces)
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Log.e("FaceAnalyzer", "人脸检测失败", e)
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,36 @@
|
||||
package com.example.defenseapplication.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.google.mlkit.vision.face.Face
|
||||
|
||||
class FaceOverlayView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
||||
private val paint = Paint().apply {
|
||||
color = Color.GREEN
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 4f
|
||||
}
|
||||
private val textPaint = Paint().apply {
|
||||
color = Color.YELLOW
|
||||
textSize = 40f
|
||||
}
|
||||
private var faces = mutableListOf<Face>()
|
||||
|
||||
fun setFaces(faces: List<Face>) {
|
||||
this.faces = faces.toMutableList()
|
||||
invalidate() // 触发 onDraw
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
for (face in faces) {
|
||||
val rect = face.boundingBox
|
||||
canvas.drawRect(rect, paint)
|
||||
canvas.drawText("Face", rect.left.toFloat(), rect.top - 10f, textPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user