初始化本地仓库
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
package uts.sdk.modules.masCodeScan
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.os.SystemClock
|
||||
import android.view.View
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
|
||||
data class HitMark(
|
||||
val rawValue: String,
|
||||
val format: Int,
|
||||
val formatName: String,
|
||||
val screenRect: Rect
|
||||
) {
|
||||
val centerX: Float get() = screenRect.exactCenterX()
|
||||
val centerY: Float get() = screenRect.exactCenterY()
|
||||
}
|
||||
|
||||
/**
|
||||
* 多码点选覆盖层:定格帧 + 圆点标记。
|
||||
*/
|
||||
class MultiCodePickView(context: Context) : View(context) {
|
||||
|
||||
private var stillFrame: Bitmap? = null
|
||||
private var rawHits: List<Barcode> = emptyList()
|
||||
private var hitMarks: List<HitMark> = emptyList()
|
||||
private var visualCrop: Rect = Rect()
|
||||
private var stillW: Int = 0
|
||||
private var stillH: Int = 0
|
||||
private var layerW: Int = 0
|
||||
private var layerH: Int = 0
|
||||
private var recognizing: Boolean = false
|
||||
private var recognizingStartedAt: Long = 0L
|
||||
|
||||
private val density: Float = resources.displayMetrics.density
|
||||
private val dotOuterR: Float = 18f * density
|
||||
private val dotInnerR: Float = 6f * density
|
||||
private val touchR: Float = 28f * density
|
||||
private val breatheDurationMs = 1500L
|
||||
|
||||
private val haloPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.FILL
|
||||
color = Color.argb(60, 7, 193, 96)
|
||||
}
|
||||
private val shellPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.FILL
|
||||
color = Color.WHITE
|
||||
}
|
||||
private val corePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.FILL
|
||||
color = Color.rgb(7, 193, 96)
|
||||
}
|
||||
private val shadePaint = Paint().apply {
|
||||
color = Color.argb(40, 0, 0, 0)
|
||||
}
|
||||
private val promptBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.argb(190, 0, 0, 0)
|
||||
}
|
||||
private val promptTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
textSize = 16f * resources.displayMetrics.scaledDensity
|
||||
textAlign = Paint.Align.CENTER
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
}
|
||||
private val loadingTrackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 3f * density
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
color = Color.argb(100, 255, 255, 255)
|
||||
}
|
||||
private val loadingArcPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 3f * density
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
color = Color.rgb(7, 193, 96)
|
||||
}
|
||||
private val loadingTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
textSize = 15f * resources.displayMetrics.scaledDensity
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
layerW = right - left
|
||||
layerH = bottom - top
|
||||
if (changed && rawHits.isNotEmpty()) {
|
||||
rebuildHitMarks()
|
||||
}
|
||||
}
|
||||
|
||||
fun showRecognizing(bitmap: Bitmap) {
|
||||
if (stillFrame !== bitmap) {
|
||||
stillFrame?.recycle()
|
||||
}
|
||||
stillFrame = bitmap
|
||||
rawHits = emptyList()
|
||||
hitMarks = emptyList()
|
||||
visualCrop.setEmpty()
|
||||
stillW = bitmap.width
|
||||
stillH = bitmap.height
|
||||
recognizing = true
|
||||
recognizingStartedAt = SystemClock.elapsedRealtime()
|
||||
visibility = VISIBLE
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun presentStillFrame(bitmap: Bitmap, barcodes: List<Barcode>, visualCropRect: Rect) {
|
||||
if (stillFrame !== bitmap) {
|
||||
stillFrame?.recycle()
|
||||
}
|
||||
stillFrame = bitmap
|
||||
rawHits = barcodes
|
||||
visualCrop = Rect(visualCropRect)
|
||||
stillW = bitmap.width
|
||||
stillH = bitmap.height
|
||||
recognizing = false
|
||||
rebuildHitMarks()
|
||||
visibility = VISIBLE
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun rebuildHitMarks() {
|
||||
if (layerW <= 0 || layerH <= 0 || stillW <= 0 || stillH <= 0) {
|
||||
hitMarks = emptyList()
|
||||
return
|
||||
}
|
||||
hitMarks = rawHits.mapNotNull { barcode ->
|
||||
val box = barcode.boundingBox ?: return@mapNotNull null
|
||||
val adjustedBox = Rect(
|
||||
box.left - visualCrop.left,
|
||||
box.top - visualCrop.top,
|
||||
box.right - visualCrop.left,
|
||||
box.bottom - visualCrop.top
|
||||
)
|
||||
val clippedBox = Rect(adjustedBox)
|
||||
if (!clippedBox.intersect(0, 0, stillW, stillH)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
val screenRect = mapBoxToScreen(clippedBox, stillW, stillH, layerW, layerH)
|
||||
HitMark(
|
||||
rawValue = barcode.rawValue ?: "",
|
||||
format = barcode.format,
|
||||
formatName = SymbologyUtil.nameOf(barcode.format),
|
||||
screenRect = screenRect
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
stillFrame?.recycle()
|
||||
stillFrame = null
|
||||
rawHits = emptyList()
|
||||
hitMarks = emptyList()
|
||||
visualCrop.setEmpty()
|
||||
stillW = 0
|
||||
stillH = 0
|
||||
recognizing = false
|
||||
recognizingStartedAt = 0L
|
||||
visibility = GONE
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun pickHitAt(x: Float, y: Float): HitMark? {
|
||||
return hitMarks.minByOrNull {
|
||||
val dx = x - it.centerX
|
||||
val dy = y - it.centerY
|
||||
dx * dx + dy * dy
|
||||
}?.takeIf {
|
||||
val dx = x - it.centerX
|
||||
val dy = y - it.centerY
|
||||
dx * dx + dy * dy <= touchR * touchR
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapBoxToScreen(
|
||||
boundingBox: Rect,
|
||||
sourceW: Int, sourceH: Int,
|
||||
viewW: Int, viewH: Int
|
||||
): Rect {
|
||||
val scale = max(viewW.toFloat() / sourceW, viewH.toFloat() / sourceH)
|
||||
val offsetX = (viewW - sourceW * scale) / 2f
|
||||
val offsetY = (viewH - sourceH * scale) / 2f
|
||||
return Rect(
|
||||
(boundingBox.left * scale + offsetX).toInt(),
|
||||
(boundingBox.top * scale + offsetY).toInt(),
|
||||
(boundingBox.right * scale + offsetX).toInt(),
|
||||
(boundingBox.bottom * scale + offsetY).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
stillFrame?.let { bmp ->
|
||||
if (layerW <= 0 || layerH <= 0) return@let
|
||||
val bmpW = bmp.width.toFloat()
|
||||
val bmpH = bmp.height.toFloat()
|
||||
val scale = max(layerW.toFloat() / bmpW, layerH.toFloat() / bmpH)
|
||||
val drawW = (bmpW * scale).toInt()
|
||||
val drawH = (bmpH * scale).toInt()
|
||||
val left = (layerW - drawW) / 2
|
||||
val top = (layerH - drawH) / 2
|
||||
val destRect = Rect(left, top, left + drawW, top + drawH)
|
||||
canvas.drawBitmap(bmp, null, destRect, null)
|
||||
if (!recognizing) {
|
||||
canvas.drawRect(destRect, shadePaint)
|
||||
}
|
||||
}
|
||||
if (recognizing) {
|
||||
drawRecognizing(canvas)
|
||||
postInvalidateDelayed(16)
|
||||
return
|
||||
}
|
||||
if (hitMarks.size >= 2) {
|
||||
val prompt = "已识别到多个码,请选择一个"
|
||||
val horizontalPadding = 18f * density
|
||||
val verticalPadding = 10f * density
|
||||
val textBounds = Rect()
|
||||
promptTextPaint.getTextBounds(prompt, 0, prompt.length, textBounds)
|
||||
val promptWidth = textBounds.width() + horizontalPadding * 2
|
||||
val promptHeight = textBounds.height() + verticalPadding * 2
|
||||
val promptLeft = (layerW - promptWidth) / 2f
|
||||
// 放在底部操作按钮上方,避免遮挡条码和顶部关闭按钮。
|
||||
val promptTop = layerH - promptHeight - 100f * density
|
||||
canvas.drawRoundRect(
|
||||
promptLeft,
|
||||
promptTop,
|
||||
promptLeft + promptWidth,
|
||||
promptTop + promptHeight,
|
||||
promptHeight / 2,
|
||||
promptHeight / 2,
|
||||
promptBackgroundPaint
|
||||
)
|
||||
val baseline = promptTop + verticalPadding - textBounds.top
|
||||
canvas.drawText(prompt, layerW / 2f, baseline, promptTextPaint)
|
||||
}
|
||||
val breatheProgress = (SystemClock.elapsedRealtime() % breatheDurationMs).toFloat() / breatheDurationMs
|
||||
val breatheWave = ((sin(breatheProgress * Math.PI * 2.0) + 1.0) / 2.0).toFloat()
|
||||
for (mark in hitMarks) {
|
||||
haloPaint.alpha = (34 + breatheWave * 70).toInt()
|
||||
canvas.drawCircle(mark.centerX, mark.centerY, touchR + breatheWave * 8f * density, haloPaint)
|
||||
canvas.drawCircle(mark.centerX, mark.centerY, dotOuterR, shellPaint)
|
||||
canvas.drawCircle(mark.centerX, mark.centerY, dotInnerR, corePaint)
|
||||
}
|
||||
if (hitMarks.isNotEmpty()) {
|
||||
postInvalidateDelayed(16)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawRecognizing(canvas: Canvas) {
|
||||
val cx = layerW / 2f
|
||||
val cy = layerH / 2f
|
||||
val radius = 24f * density
|
||||
val bounds = android.graphics.RectF(cx - radius, cy - radius, cx + radius, cy + radius)
|
||||
val elapsed = SystemClock.elapsedRealtime() - recognizingStartedAt
|
||||
val startAngle = (elapsed % 1000L) * 360f / 1000f - 90f
|
||||
canvas.drawCircle(cx, cy, radius, loadingTrackPaint)
|
||||
canvas.drawArc(bounds, startAngle, 110f, false, loadingArcPaint)
|
||||
canvas.drawText("识别中", cx, cy + radius + 30f * density, loadingTextPaint)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user