人脸识别支付流程

This commit is contained in:
2025-11-28 09:13:29 +08:00
parent 88179e27b7
commit 9bcd7c9983
20 changed files with 561 additions and 216 deletions
@@ -4,8 +4,10 @@ import android.app.Dialog
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.text.TextUtils
import android.view.Display
import android.view.View
import android.view.WindowManager
import android.widget.TextView
@@ -23,6 +25,12 @@ import timber.log.Timber
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
val displays: Array<Display> by lazy {
val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.displays
}
protected lateinit var binding: VB
protected lateinit var context: Context
private var mDialogWaiting: CustomDialog? = null
@@ -8,9 +8,11 @@ import com.sw.dualscreen.activity.fragment.pay.CashPayFragment
import com.sw.dualscreen.activity.fragment.pay.FacePayFragment
import com.sw.dualscreen.activity.fragment.pay.NumberPayFragment
import com.sw.dualscreen.activity.fragment.pay.PayResultFragment
import com.sw.dualscreen.activity.fragment.pay.QrCodePayFragment
import com.sw.dualscreen.activity.fragment.pay.ScanQrCodePayFragment
import com.sw.dualscreen.databinding.ActivityPayBinding
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.presentation.pay.FacePayPresentation
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
@@ -40,10 +42,10 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
var foodInfo: FoodInfo? = null
private var qrCodePayFragment: QrCodePayFragment? = null
private var qrCodePayFragment: ScanQrCodePayFragment? = null
private var cashPayFragment: CashPayFragment? = null
private var numberPayFragment: NumberPayFragment? = null
private var facePayFragment: FacePayFragment? = null
var facePayFragment: FacePayFragment? = null
private var payResultFragment: PayResultFragment? = null
@Suppress("DEPRECATION")
@@ -53,33 +55,48 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
binding.tvFoodName.text = foodInfo?.foodName
binding.include.ivPageBack.setOnClickListener { finish() }
binding.include.tvPageTitle.text = "下单结算"
val payQrCodeClickListener = View.OnClickListener {
if (qrCodePayFragment == null) {
qrCodePayFragment = QrCodePayFragment()
binding.btnPayQrCode.setOnClickListener {
if (binding.btnPayQrCode.isChecked) {
return@setOnClickListener
}
addFragment(qrCodePayFragment!!, TAG_PAY_QR_CODE)
switchButton(true, false, false, false)
showScanQrCodePay()
}
binding.btnPayQrCode.setOnClickListener(payQrCodeClickListener)
binding.btnPayCash.setOnClickListener {
if (binding.btnPayCash.isChecked) {
return@setOnClickListener
}
if (cashPayFragment == null) {
cashPayFragment = CashPayFragment()
}
addFragment(cashPayFragment!!, TAG_PAY_CASH)
showFragment(cashPayFragment!!, TAG_PAY_CASH)
switchButton(false, true, false, false)
}
binding.btnPayNumber.setOnClickListener {
if (binding.btnPayNumber.isChecked) {
return@setOnClickListener
}
if (numberPayFragment == null) {
numberPayFragment = NumberPayFragment()
}
addFragment(numberPayFragment!!, TAG_PAY_NUMBER)
showFragment(numberPayFragment!!, TAG_PAY_NUMBER)
switchButton(false, false, true, false)
}
binding.btnPayFace.setOnClickListener {
if (binding.btnPayFace.isChecked) {
return@setOnClickListener
}
showFacePay()
}
payQrCodeClickListener.onClick(binding.btnPayQrCode)
showScanQrCodePay()
}
private fun showScanQrCodePay() {
if (qrCodePayFragment == null) {
qrCodePayFragment = ScanQrCodePayFragment()
}
showFragment(qrCodePayFragment!!, TAG_PAY_QR_CODE)
switchButton(true, false, false, false)
}
private fun switchButton(vararg arr: Boolean) {
@@ -93,38 +110,49 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
if (facePayFragment == null) {
facePayFragment = FacePayFragment()
}
addFragment(facePayFragment!!, TAG_PAY_FACE)
showFragment(facePayFragment!!, TAG_PAY_FACE)
switchButton(false, false, false, true)
}
private fun addFragment(currentFragment: Fragment, tag: String) {
private fun showFragment(fragment: Fragment, tag: String) {
runCatching {
supportFragmentManager.beginTransaction().apply {
val existFragment = supportFragmentManager.findFragmentByTag(tag)
if (existFragment == null) {
add(R.id.fragmentContainerView, currentFragment, tag)
}
val fragments = supportFragmentManager.fragments
fragments.forEach {
it?.let { fragment ->
if (fragment != currentFragment) {
hide(fragment)
}
}
}
show(currentFragment)
commitAllowingStateLoss()
replace(R.id.fragmentContainerView, fragment)
commitNowAllowingStateLoss()
}
}.onFailure {
it.printStackTrace()
}
}
// private fun showFragment(currentFragment: Fragment, tag: String) {
// runCatching {
// supportFragmentManager.beginTransaction().apply {
// val existFragment = supportFragmentManager.findFragmentByTag(tag)
// if (existFragment == null) {
// add(R.id.fragmentContainerView, currentFragment, tag)
// }
// val fragments = supportFragmentManager.fragments
// fragments.forEach {
// it?.let { fragment ->
// if (fragment != currentFragment) {
// hide(fragment)
// }
// }
// }
// show(currentFragment)
// commitAllowingStateLoss()
// }
// }.onFailure {
// it.printStackTrace()
// }
// }
fun showPayResult() {
if (payResultFragment == null) {
payResultFragment = PayResultFragment()
}
addFragment(payResultFragment!!, TAG_PAY_RESULT)
showFragment(payResultFragment!!, TAG_PAY_RESULT)
}
override fun onDestroy() {
@@ -134,5 +162,22 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
facePayFragment?.presentation?.dismiss()
super.onDestroy()
}
private var scanQrCodePayPresentation: ScanQrCodePayPresentation?=null
fun paySuccess() {
showFragment(PayResultFragment(),"")
if (displays.size > 1) {
scanQrCodePayPresentation = ScanQrCodePayPresentation(
activity = this,
display = displays[1],
type = 2
) {
scanQrCodePayPresentation?.dismiss()
}.also {
// it.foodName = foodName
// it.payAmount = payAmount
}
scanQrCodePayPresentation?.show()
}
}
}
@@ -1,9 +1,9 @@
package com.sw.dualscreen.activity.fragment.pay
import android.graphics.Bitmap
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentFacePayBinding
import com.sw.dualscreen.presentation.pay.CashPayPresentation
import com.sw.dualscreen.presentation.pay.FacePayPresentation
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
@@ -37,6 +37,10 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
showSubScreen()
}
fun loadBitmap(frame: Bitmap) {
binding.ivFaceImage.setImageBitmap(frame)
}
private fun showSubScreen() {
// 查找副屏(通常索引为1
if (displays.size > 1) {
@@ -45,6 +49,7 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
display = displays[1],
userViewModel = payActivity!!.userViewModel,
recognizeViewModel = payActivity!!.recognizeViewModel,
// parentTextureView = binding.parentTextureView,
) {
presentation?.dismiss()
}.also {
@@ -1,8 +1,14 @@
package com.sw.dualscreen.activity.fragment.pay
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import androidx.core.text.buildSpannedString
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentPayResultBinding
import com.sw.dualscreen.ext.load
class PayResultFragment: BaseFragment<FragmentPayResultBinding>() {
@@ -10,20 +16,25 @@ class PayResultFragment: BaseFragment<FragmentPayResultBinding>() {
return FragmentPayResultBinding.inflate(layoutInflater)
}
private var payActivity: PayActivity? = null
override fun initialize() {
payActivity = activity as PayActivity
binding.btnBack.setOnClickListener {
payActivity?.showFacePay()
//payActivity?.showFacePay()
}
binding.ivHeadPic.load(R.mipmap.ic_launcher)
binding.tvUserName.text = "张三"
binding.tvUserPhone.text = "132****5678"
binding.tvUserBalance.text = getBalanceText("0.00")
binding.tvPayAmount.text = "收款金额 20.00 元"
binding.tvPayInfo.text = "应收 38.00 元,余额扣除 16.80 元"
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
//显示时重新查询接口
if (hidden.not()) {
private fun getBalanceText(amount: String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(20, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
@@ -8,22 +8,25 @@ import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentQrcodePayBinding
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.presentation.pay.QrCodePayPresentation
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
import com.sw.dualscreen.utils.countDownByFlow
import kotlinx.coroutines.Job
import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding
class QrCodePayFragment: BaseFragment<FragmentQrcodePayBinding>() {
class ScanQrCodePayFragment: BaseFragment<FragmentScanQrcodePayBinding>() {
companion object {
}
private lateinit var payActivity: PayActivity
private var foodName: String? = null
private var payAmount: String? = null
private var payQrCodePic: Any? = null
var presentation: QrCodePayPresentation?=null
override fun inflateViewBinding(): FragmentQrcodePayBinding {
return FragmentQrcodePayBinding.inflate(layoutInflater)
var presentation: ScanQrCodePayPresentation?=null
override fun inflateViewBinding(): FragmentScanQrcodePayBinding {
return FragmentScanQrcodePayBinding.inflate(layoutInflater)
}
override fun initialize() {
@@ -55,7 +58,7 @@ class QrCodePayFragment: BaseFragment<FragmentQrcodePayBinding>() {
private fun showSubScreen() {
// 查找副屏(通常索引为1
if (displays.size > 1) {
presentation = QrCodePayPresentation(activity = payActivity, display = displays[1]) {
presentation = ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
presentation?.dismiss()
}.also {
it.foodName = foodName
@@ -83,7 +86,7 @@ class QrCodePayFragment: BaseFragment<FragmentQrcodePayBinding>() {
binding.tvPayResult.text = "待支付(${seconds}s)..."
},
onFinish = {
binding.tvPayResult.text = "支付倒计时结束"
binding.tvPayResult.text = "支付"
}
)
}
@@ -102,7 +102,7 @@ class MainScreenPresentation(
binding = PresentationMainScreenBinding.inflate(layoutInflater)
setContentView(binding.root)
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
window?.setBackgroundDrawableResource(android.R.color.transparent)
window?.setBackgroundDrawableResource(android.R.color.white)
initView()
setupArcCamera()
registerDataChange()
@@ -713,8 +713,10 @@ class MainScreenPresentation(
val layoutParams = previewView.layoutParams
// layoutParams.width = (608.dp * 1.5).roundToInt()
// layoutParams.height = (456.dp * 1.5).roundToInt()
layoutParams.width = (600.dp * 1.5).roundToInt()
layoutParams.height = (400.dp * 1.5).roundToInt()
// layoutParams.width = (600.dp * 1.5).roundToInt()
// layoutParams.height = (450.dp * 1.5).roundToInt()
layoutParams.width = 1024
layoutParams.height = 768
previewView.setLayoutParams(layoutParams)
faceRectView.setLayoutParams(layoutParams)
return layoutParams
@@ -41,4 +41,9 @@ class CashPayPresentation(
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
}
@@ -1,6 +1,7 @@
package com.sw.dualscreen.presentation.pay
import android.Manifest
import android.app.Dialog
import android.app.Presentation
import android.content.pm.PackageManager
import android.graphics.Bitmap
@@ -10,24 +11,26 @@ import android.os.Build
import android.os.Bundle
import android.text.Spanned
import android.text.SpannedString
import android.text.TextUtils
import android.text.style.AbsoluteSizeSpan
import android.view.Display
import android.view.TextureView
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationFacePayBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.presentation.MainScreenPresentation
import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.view.CustomDialog
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.ConfigUtil
@@ -45,13 +48,13 @@ import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.math.roundToInt
class FacePayPresentation(
val activity: PayActivity,
display: Display,
val userViewModel: UserViewModel,
val recognizeViewModel: RecognizeViewModel,
// val //parentTextureView: TextureView,
private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) , ViewTreeObserver.OnGlobalLayoutListener {
@@ -75,16 +78,20 @@ class FacePayPresentation(
super.onCreate(savedInstanceState)
binding = PresentationFacePayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(R.color.transparent)
window?.setBackgroundDrawableResource(android.R.color.white)
initView()
setupArcCamera()
registerDataChange()
pauseCamera()
}
override fun show() {
super.show()
resumeCamera()
binding.root.postDelayed({
resumeCamera()
}, 1500)
}
var foodName: String? = null
@@ -92,6 +99,14 @@ class FacePayPresentation(
private fun initView() {
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
// binding.ivPreviewImage.let {
// it.outlineProvider = object : ViewOutlineProvider() {
// override fun getOutline(view: View, outline: Outline) {
// outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
// }
// }
// it.clipToOutline = true
// }
}
private fun getAmountText(amount: String): SpannedString {
@@ -104,6 +119,7 @@ class FacePayPresentation(
override fun onGlobalLayout() {
Timber.tag(TAG).d("onGlobalLayout")
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
//parentTextureView.getViewTreeObserver().removeOnGlobalLayoutListener(this)
openCamera()
}
private fun openCamera() {
@@ -249,17 +265,16 @@ class FacePayPresentation(
val userId = faceEntity.userName
if (userId == null ) return@Observer
activity.runOnUiThread {
ToastUtils.showToast("获取用户id成功")
//ToastUtils.showToast("用户人脸识别成功,挑战支付页面")
showWaitingDialog("刷脸支付中,请稍后……")
binding.root.postDelayed({
hideWaitingDialog()
activity.paySuccess()
binding.root.postDelayed({
dismiss()
},500)
},1500)
}
// if (currentFood == null) {
// //重新识别
// activity.runOnUiThread {
// ToastUtils.showToast("currentFood == null")
// }
// //activity.recognizeFood()
// //step1FoodRecognizing()
// return@Observer
// }
// userViewModel.getUserNutritionData(
// userId = userId,
// foodId = currentFood!!.id!!
@@ -274,6 +289,7 @@ class FacePayPresentation(
private fun initArcView() {
//在布局结束后才做初始化操作
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
//parentTextureView.getViewTreeObserver().addOnGlobalLayoutListener(this)
recognizeViewModel.getCompareResultList().getValue()
}
@@ -337,8 +353,10 @@ class FacePayPresentation(
val layoutParams = previewView.layoutParams
// layoutParams.width = (608.dp * 1.5).roundToInt()
// layoutParams.height = (456.dp * 1.5).roundToInt()
layoutParams.width = (600.dp * 1.5).roundToInt()
layoutParams.height = (400.dp * 1.5).roundToInt()
// layoutParams.width = (608.dp * 1.5).roundToInt()
// layoutParams.height = (456.dp * 1.5).roundToInt()
layoutParams.width = 1024
layoutParams.height = 768
previewView.setLayoutParams(layoutParams)
faceRectView.setLayoutParams(layoutParams)
return layoutParams
@@ -440,6 +458,9 @@ class FacePayPresentation(
.previewOn(binding.dualCameraTexturePreviewRgb)
.cameraListener(cameraListener)
.build()
rgbCameraHelper!!.setSurfaceFrameCallback { frame ->
activity.facePayFragment?.loadBitmap(frame)
}
rgbCameraHelper!!.init()
// rgbCameraHelper!!.start()
}
@@ -543,7 +564,6 @@ class FacePayPresentation(
)
// 识别成功
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
ToastUtils.showToast("识别到人脸信息")
}
if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) {
@@ -561,11 +581,30 @@ class FacePayPresentation(
}
}
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
fun updateImage(bitmap: Bitmap) {
activity.runOnUiThread {
// binding.ivPreviewImage.setImageBitmap(bitmap)
}
private var mDialogWaiting:CustomDialog?=null
/**
* 显示等待提示框
*/
fun showWaitingDialog(tip: String?): Dialog? {
hideWaitingDialog()
val view = View.inflate(activity, R.layout.dialog_waiting, null)
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).text = tip
mDialogWaiting = CustomDialog(activity, view, R.style.MyDialog)
mDialogWaiting!!.show()
mDialogWaiting!!.setCancelable(true)
return mDialogWaiting
}
/**
* 隐藏等待提示框
*/
fun hideWaitingDialog() {
mDialogWaiting?.dismiss()
mDialogWaiting = null
}
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
}
@@ -1,45 +0,0 @@
package com.sw.dualscreen.presentation.pay
import android.app.Presentation
import android.os.Bundle
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import android.view.Display
import androidx.core.text.buildSpannedString
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationQrcodePayBinding
import com.sw.dualscreen.ext.load
class QrCodePayPresentation(
val activity: PayActivity,
display: Display,
private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) {
private lateinit var binding: PresentationQrcodePayBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PresentationQrcodePayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.transparent)
initView()
}
var foodName: String? = null
var payAmount: String? = null
var payQrCodePic: Any? = null
private fun initView() {
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
binding.ivPayQrCode.load(payQrCodePic)
}
private fun getAmountText(amount: String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
@@ -0,0 +1,115 @@
package com.sw.dualscreen.presentation.pay
import android.app.Presentation
import android.os.Bundle
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.view.Display
import androidx.core.graphics.toColorInt
import androidx.core.text.buildSpannedString
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.visible
class ScanQrCodePayPresentation(
val activity: PayActivity,
display: Display,
val type:Int,
private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) {
//type = 0,扫码支付默认显示二维码
//type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
//type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
private lateinit var binding: PresentationScanQrcodePayBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.transparent)
initView()
}
var foodName: String? = null
var payAmount: String? = null
var payQrCodePic: Any? = null
private fun initView() {
when(type) {
0->{
binding.layoutVip.gone()
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
binding.tvPayTip.text = "请扫码支付或出示付款码"
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
binding.ivPayQrCode.load(payQrCodePic)
}
1->{
binding.layoutVip.visible()
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
binding.ivPayQrCode.load(payQrCodePic)
binding.ivHeadPic.load(R.mipmap.ic_launcher)
binding.tvUserName.text = "张三"
binding.tvUserPhone.text = "132****5678"
binding.tvUserBalance.text = getBalanceText("0.00")
binding.tvPayTip.text = getPayTip("-16.80", "22.00")
}
2->{
binding.layoutVip.visible()
binding.layoutPaySuccess.visible()
binding.layoutScanQrCode.gone()
binding.ivHeadPic.load(R.mipmap.ic_launcher)
binding.tvUserName.text = "张三"
binding.tvUserPhone.text = "132****5678"
binding.tvUserBalance.text = getBalanceText("0.00")
binding.tvPayInfo.text = "余额扣除 16.80 元,在线支付 20.00 元"
}
}
}
private fun getPayTip(startAmount:String, endAmount: String): SpannedString {
return buildSpannedString {
append("余额扣除 ", ForegroundColorSpan("#FF5E7585".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(startAmount, ForegroundColorSpan("#FF0A1428".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(" 元,还需支付 ", ForegroundColorSpan("#FF5E7585".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(endAmount, ForegroundColorSpan("#FF0A1428".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append("", ForegroundColorSpan("#FF5E7585".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
private fun getBalanceText(amount: String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(20, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
private fun getAmountText(amount: String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
}
+13 -7
View File
@@ -17,19 +17,25 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="509dp"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="64dp">
<FrameLayout
android:id="@+id/flCameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- <FrameLayout-->
<!-- android:id="@+id/flCameraPreview"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent" />-->
<!-- <TextureView-->
<!-- android:id="@+id/parentTextureView"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:visibility="visible" />-->
<ImageView
android:id="@+id/ivFacePhoto"
android:id="@+id/ivFaceImage"
android:layout_width="match_parent"
android:layout_height="510dp"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
@@ -22,11 +22,12 @@
android:paddingTop="32dp"
android:paddingBottom="47dp">
<ImageView
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivHeadPic"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@mipmap/ic_launcher"
app:shapeAppearance="@style/CircleStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -158,14 +159,9 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:gravity="center"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
@@ -208,10 +204,6 @@
android:textSize="30sp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
@@ -67,8 +67,8 @@
<FrameLayout
android:id="@+id/flFace"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_marginHorizontal="32dp"
android:layout_height="456dp"
android:layout_marginHorizontal="28dp"
android:layout_marginTop="56dp">
<TextureView
@@ -82,6 +82,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- <ImageView-->
<!-- android:id="@+id/ivPreviewImage"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent" />-->
<!-- <ImageView-->
<!-- android:id="@+id/ivFaceRecMask"-->
<!-- android:layout_width="match_parent"-->
@@ -104,8 +108,8 @@
<FrameLayout
android:id="@+id/fl_recognize_ir"
android:layout_width="15dp"
android:layout_height="10dp"
android:layout_width="1.3333dp"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:visibility="visible">
@@ -34,7 +34,7 @@
<FrameLayout
android:id="@+id/flCameraView"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_height="456dp"
android:layout_marginHorizontal="28dp"
android:visibility="visible">
@@ -79,13 +79,13 @@
<ImageView
android:id="@+id/ivPreviewImage"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_height="456dp"
tools:background="@mipmap/ic_launcher"/>
<ImageView
android:id="@+id/ivRecImage"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_height="456dp"
tools:background="@mipmap/ic_launcher"/>
<ImageView
@@ -229,8 +229,8 @@
<FrameLayout
android:id="@+id/fl_recognize_ir"
android:layout_width="15dp"
android:layout_height="10dp"
android:layout_width="1.3333dp"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:visibility="visible">
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical">
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="210dp"
android:textColor="#FF0A1428"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="32sp"
android:textStyle="bold"
tools:text="¥36.80" />
<ImageView
android:id="@+id/ivPayQrCode"
android:layout_width="333dp"
android:layout_height="333dp"
android:layout_marginTop="56dp"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_qrcode" />
<TextView
android:id="@+id/tvPayTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="请扫码支付或出示付款码"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#FFE7EFF8">
<LinearLayout
android:id="@+id/layoutScanQrCode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:visibility="visible">
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="327dp"
android:textColor="#FF0A1428"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="48sp"
android:textStyle="bold"
tools:text="¥36.80" />
<ImageView
android:id="@+id/ivPayQrCode"
android:layout_width="333dp"
android:layout_height="333dp"
android:layout_marginTop="56dp"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_qrcode" />
<TextView
android:id="@+id/tvPayTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="请扫码支付或出示付款码"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutPaySuccess"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="471dp"
android:src="@drawable/ic_pay_success" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="支付成功"
android:textColor="#ff00c864"
android:textSize="48sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPayInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:textColor="#FF5E7585"
android:textSize="24sp"
tools:text="余额扣除 16.80 元,在线支付 20.00 元" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/layoutVip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/white"
android:layout_marginTop="56dp"
android:layout_marginHorizontal="56dp"
app:cardCornerRadius="5dp"
app:cardElevation="0dp"
android:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="28dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivHeadPic"
android:layout_width="84dp"
android:layout_height="84dp"
tools:src="@mipmap/ic_launcher"
android:layout_marginTop="28dp"
app:shapeAppearance="@style/CircleStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="27dp"
android:textColor="#ff0a1428"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvUserPhone"
app:layout_constraintStart_toEndOf="@id/ivHeadPic"
app:layout_constraintTop_toTopOf="@id/ivHeadPic"
app:layout_constraintVertical_chainStyle="packed"
tools:text="DAMIHU" />
<TextView
android:id="@+id/tvUserPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:textColor="#ff889ac2"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/ivHeadPic"
app:layout_constraintStart_toStartOf="@id/tvUserName"
app:layout_constraintTop_toBottomOf="@id/tvUserName"
tools:text="138****8000" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="28dp"
android:background="#FFDBE5FB"
app:layout_constraintTop_toBottomOf="@id/ivHeadPic" />
<TextView
android:id="@+id/tvBalanceName"
android:layout_width="wrap_content"
android:layout_height="79dp"
android:text="可用余额"
android:gravity="center_vertical"
android:textColor="#ff889ac2"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="@id/ivHeadPic"
app:layout_constraintTop_toBottomOf="@id/divider" />
<TextView
android:id="@+id/tvUserBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/tvBalanceName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvBalanceName"
tools:text="¥16.80" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
+6
View File
@@ -166,4 +166,10 @@
<!-- 遮罩层 -->
<item name="android:backgroundDimAmount">0.5</item>
</style>
<style name="CircleStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
</resources>
@@ -88,8 +88,10 @@ public class ConfigUtil {
* 默认相机分辨率
*/
// private static final String DEFAULT_PREVIEW_SIZE = "1280x720";
private static final String DEFAULT_PREVIEW_SIZE = "600x400";
// private static final String DEFAULT_PREVIEW_SIZE = "400x640";
// private static final String DEFAULT_PREVIEW_SIZE = "608x456";
private static final String DEFAULT_PREVIEW_SIZE = "1024x768";
// private static final String DEFAULT_PREVIEW_SIZE = "800x600";
// private static final String DEFAULT_PREVIEW_SIZE = "640x480";
/**
@@ -1,5 +1,6 @@
package com.sw.plate.utils.arcface.camera;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
@@ -42,8 +43,10 @@ public class DualCameraHelper implements Camera.PreviewCallback {
private Integer specificCameraId = null;
private CameraListener cameraListener;
private static final int MIN_PREVIEW_WIDTH = 720;
private static final int MIN_PREVIEW_HEIGHT = 720;
// private static final int MIN_PREVIEW_WIDTH = 720;
// private static final int MIN_PREVIEW_HEIGHT = 720;
private static final int MIN_PREVIEW_WIDTH = 800;
private static final int MIN_PREVIEW_HEIGHT = 600;
private DualCameraHelper(Builder builder) {
previewDisplayView = builder.previewDisplayView;
@@ -60,6 +63,16 @@ public class DualCameraHelper implements Camera.PreviewCallback {
}
}
public interface SurfaceFrameCallback {
void onFrameCallback(Bitmap frame);
}
private SurfaceFrameCallback surfaceFrameCallback;
public void setSurfaceFrameCallback(SurfaceFrameCallback callback) {
this.surfaceFrameCallback = callback;
}
public void init() {
if (previewDisplayView instanceof TextureView) {
((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener);
@@ -126,6 +139,12 @@ public class DualCameraHelper implements Camera.PreviewCallback {
return;
}
List<Camera.Size> supportedPreviewSize = getCommonSupportedPreviewSize();
StringBuilder stringBuilder = new StringBuilder();
for (Camera.Size size : supportedPreviewSize) {
stringBuilder.append("width=").append(size.width).append(",")
.append("height=").append(size.height).append(",");
}
Log.d(TAG, "start: "+stringBuilder);
//相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置
mCameraId = Camera.getNumberOfCameras() - 1;
//若指定了相机ID且该相机存在,则打开指定的相机
@@ -371,7 +390,12 @@ public class DualCameraHelper implements Camera.PreviewCallback {
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
if (surfaceFrameCallback!=null) {
if (previewDisplayView instanceof TextureView) {
Bitmap frame = ((TextureView) previewDisplayView).getBitmap();
surfaceFrameCallback.onFrameCallback(frame);
}
}
}
};
private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {