修改模式切换流程

This commit is contained in:
mazengfei
2025-11-10 18:55:21 +08:00
parent 4abc855b36
commit b4087e435e
15 changed files with 421 additions and 136 deletions
Binary file not shown.
@@ -9,10 +9,12 @@ import timber.log.Timber
class MyApp : App() {
companion object {
const val DEBUG: Boolean = true
var instance: MyApp?=null
}
override fun onCreate() {
super.onCreate()
instance = this
Timber.plant(Timber.DebugTree())
var deviceId = AppUtil.getUDID(this)
Timber.d("UDID = ${AppUtil.getUDID(this)}")
@@ -3,12 +3,17 @@ package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Typeface
import android.net.Uri
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels
import androidx.camera.view.PreviewView
import androidx.core.net.toUri
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.example.utils.FloatBase64Utils
@@ -19,7 +24,9 @@ import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
import com.sw.dualscreen.ext.clickWithDebounce
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodCollectionBean
@@ -28,6 +35,7 @@ import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.utils.BitmapCropper
import com.sw.dualscreen.utils.BitmapSaver
import com.sw.dualscreen.utils.CameraHelper
import com.sw.dualscreen.utils.CameraUtils
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.viewmodel.BaseViewModel
@@ -46,17 +54,19 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
private val viewModel by viewModels<UserViewModel>()
private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
private var module: Module? = null
private var box: Box<Food>? = null
private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
private lateinit var cameraHelper: CameraHelper
// private lateinit var cameraHelper: CameraHelper
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
private val debouncer = Debouncer(2000)
private lateinit var previewView: PreviewView
private val cameraUtils: CameraUtils by lazy {
CameraUtils(this)
}
private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(foodCollectionList).apply {
@@ -67,9 +77,15 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
// }
// }
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
list.removeAt(position)
collectionAdapter.notifyItemRemoved(position)
collectionAdapter.notifyItemRangeChanged(position, list.size)
// list.removeAt(position)
// collectionAdapter.notifyItemRemoved(position)
// collectionAdapter.notifyItemRangeChanged(position, list.size)
foodCollectionList[position].let {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
notifyItemChanged(position)
}
}
}
@@ -82,54 +98,77 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
return ActivityFoodCollectionBinding.inflate(layoutInflater)
}
private fun cameraCallback(uri:Uri){
private val cameraCallback: (Uri) -> Unit = { uri ->
val index = foodCollectionList.indexOfFirst { it.bitmap == null }
if (index == -1) {
ToastUtils.showToast("每次只允许保存6条数据")
return
rerurn@ cameraCallback
}
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
val cropBitmap = BitmapCropper.cropCenter(
original = bitmap,
targetWidth = 1300, targetHeight = 900,
//offsetX = 30, offsetY = 100
)
// val cropBitmap = BitmapCropper.cropCenter(
// original = bitmap,
// targetWidth = 1300, targetHeight = 900,
// //offsetX = 30, offsetY = 100
// )
val file = BitmapSaver.saveToAppFilesDir(
cropBitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
foodCollectionList[index].let {
it.bitmap = cropBitmap
it.bitmap = bitmap
it.isShowCamera = false
it.imageUri = file?.toUri()
}
collectionAdapter.notifyItemChanged(index)
}
}
override fun initialize() {
// 初始化 CameraHelper
cameraHelper = CameraHelper(
context = this,
caller = this,
authority = "${packageName}.fileprovider"
) { uri, path ->
//if (foodCollectionList.size < 6) {
// val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
// foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
//} else {
// foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
//}
//collectionAdapter.notifyDataSetChanged()
cameraCallback(uri)
@SuppressLint("NotifyDataSetChanged")
private fun takePhoto() {
val count = foodCollectionList.count { it.bitmap != null }
if (count == 6) {
ToastUtils.showToast("每次只允许保存6条数据")
return
}
cameraUtils.takePhoto(cameraCallback)
}
override fun initialize() {
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 180.dp
height = 180.dp
}
}
cameraUtils.setPreviewController(previewView)
// 初始化 CameraHelper
// cameraHelper = CameraHelper(
// context = this,
// caller = this,
// authority = "${packageName}.fileprovider"
// ) { uri, path ->
// //if (foodCollectionList.size < 6) {
// // val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
// // foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
// //} else {
// // foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
// //}
// //collectionAdapter.notifyDataSetChanged()
// cameraCallback(uri)
// }
binding.ivBack.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
repeat(6) {
foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
}
binding.rvFoodCollection.let {
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
it.adapter = collectionAdapter
@@ -164,15 +203,17 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}
binding.btnCollectedGoods.setOnClickListener {
startActivity(Intent(this, CollectedDataActivity::class.java))
}
binding.btnTakePhoto.clickWithDebounce {
binding.btnTakePhoto.text = "拍照"
val count = foodCollectionList.count { it.bitmap != null }
if (count == 6) {
ToastUtils.showToast("每次只允许保存6条数据")
return@clickWithDebounce
}
cameraHelper.openCamera()
// cameraHelper.openCamera()
takePhoto()
}
binding.btnClearData.setOnClickListener { clearData() }
foodAdapter = createAdapter()
@@ -192,11 +233,11 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
//Loading.show(this)
showWaitingDialog("加载中……")
Thread {
foodCollectionList.forEachIndexed { index, it ->
if (!it.isShowCamera)
image2VectorTask(it.imageUri!!, index)
}
foodCollectionList
.filter { it.bitmap != null }
.forEachIndexed { index, it ->
image2VectorTask(it, index)
}
runOnUiThread {
window.decorView.postDelayed({
//Loading.dismiss()
@@ -206,44 +247,33 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}.start()
}
private fun image2VectorTask(imageUri: Uri, position: Int) {
if (module == null) {
module =
Module.load(FoodModule.copyAssetToCache(this, "best_embedding_model_mobile.pt"))
}
private fun initBox() {
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
ImageUtil.uriToBitmap(this, imageUri)?.let { bitmap ->
//val bitmap2 = BitmapCropper.cropCenter(bitmap!!, 500, 500)
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module!!.forward(IValue.from(inputTensor)).toTensor()
}
val base64Str = FloatBase64Utils.floatArrayToBase64(outputTensor.dataAsFloatArray)
Timber.tag("mzf1").e(base64Str)
val imageVector = outputTensor.dataAsFloatArray
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
foodCollectionList[position].let {
it.imageVector = imageVector
it.isFinish = true
private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
initBox()
val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
Timber
viewModel.postImageData(
context,
foodId = selectedFoodId.toString(),
foodName = selectedFoodName.toString(),
foodVector = base64Str,
uri = imageUri
)
}
runOnUiThread {
collectionAdapter.notifyItemChanged(position)
}
Timber.tag("registerDataChange").e("box.all.size=${box?.all?.size}")
val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
Timber.tag("mzf1").e(base64Str)
// viewModel.postImageData(
// context,
// foodId = selectedFoodId.toString(),
// foodName = selectedFoodName.toString(),
// foodVector = base64Str,
// uri = item.imageUri!!
// )
box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
foodCollectionList[position].let {
it.imageVector = imageVector
it.isFinish = true
}
runOnUiThread {
collectionAdapter.notifyItemChanged(position)
}
}
@@ -258,6 +288,8 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
super.registerDataChange()
lifecycleScope.launch {
viewModel.searchFoodInfoList.collect {
// foodList.clear()
// foodList.addAll(it)
foodAdapter.updateData(it)
}
}
@@ -294,7 +326,9 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}
)
}
private var clickIndex = -1
@SuppressLint("NotifyDataSetChanged")
private fun clearData() {
foodCollectionList.forEach {
@@ -306,9 +340,27 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
clickIndex = -1
binding.editFoodName.setText("")
foodAdapter.updateData(mutableListOf())
foodAdapter.notifyDataSetChanged()
// foodList.clear()
// foodAdapter.updateData(mutableListOf())
// foodAdapter.notifyDataSetChanged()
//loadEmptyView()
}
override fun onResume() {
super.onResume()
cameraUtils.bind()
binding.llCameraFlag.run {
visibility = View.VISIBLE
postDelayed({
visibility = View.GONE
}, 3000)
}
}
override fun onPause() {
super.onPause()
cameraUtils.unbind()
binding.llCameraFlag.visibility = View.VISIBLE
}
}
@@ -235,15 +235,15 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap ->
Timber.d("registerDataChange photoUri 拿到bitmap")
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir(
bmp,
bitmap,
this,
"IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
val resultList = FoodModule.queryFood(bmp)
val resultList = FoodModule.queryFood(bitmap)
Timber.d("registerDataChange photoUri 拿到识别数据")
val foodName = resultList.joinToString(separator = ",")
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
@@ -33,7 +33,8 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
setImageResource(R.drawable.ic_camera256)
} else {
scaleType = ImageView.ScaleType.FIT_CENTER
setImageURI(it.imageUri)
//setImageURI(it.imageUri)
setImageBitmap(it.bitmap)
}
}
}
@@ -13,7 +13,7 @@ import com.sw.dualscreen.ext.hideKeyboard
abstract class BaseDialog(
context: Context,
var defWidth: Int = 400.dp,
var defHeight: Int = 300.dp
var defHeight: Int = 260.dp
) : Dialog(context, R.style.DialogTheme) {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -1,10 +1,13 @@
package com.sw.dualscreen.objbox
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.utils.AssetsTool
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.sw.dualscreen.utils.AssetsTool
import com.sw.dualscreen.utils.ImageUtil
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import io.objectbox.query.Query
@@ -19,34 +22,64 @@ import java.io.InputStream
object FoodModule {
private const val THRESHOLD = 0.8
private lateinit var module_mobile: Module
private lateinit var box: Box<Food>
private lateinit var embeddingsList: List<List<Float>>
private lateinit var labelsList: IntArray
private lateinit var classInfo: FoodClassInfo
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
val DEFAULT_FOOD_INDEX = -1
fun init(context: Context) {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
box = ObjectBox.boxStore.boxFor(Food::class)
// if (box.all.isNotEmpty()) {
// box.removeAll()
// }
// if (box.all.isEmpty()) {
// initFoodData(context)
// }
Thread {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
box = ObjectBox.boxStore.boxFor(Food::class)
//if (box.all.isNotEmpty()) {
// box.removeAll()
//}
//if (box.all.isEmpty()) {
// initDefFoodData(context)
//}
}.start()
}
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
fun uri2FloatArray(uri: Uri): FloatArray? {
return MyApp.instance?.let { context ->
ImageUtil.uriToBitmap(context, uri)?.let {
bitmap2FloatArray(it)
}
}
}
fun bitmap2FloatArray(bitmap: Bitmap): FloatArray {
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
return outputTensor.dataAsFloatArray
}
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
return uri2FloatArray(uri)?.let {
queryFood(it, queryCount)
}
}
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
val inputTensor =
TensorImageUtils.bitmapToFloat32Tensor(
bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
val floatArray = outputTensor.dataAsFloatArray
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
return queryFood(floatArray, queryCount)
}
@@ -56,24 +89,35 @@ object FoodModule {
//查询比较分数
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
val map = mutableMapOf<String, Int>()
query.findIdsWithScores().forEach {
val idScoreList = query.findIdsWithScores()
val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach {
nameScoreList.add(IdNameScore(id = it.id, name = box.get(it.id).name?:"", score = it.score))
Timber.tag("registerDataChange").d("queryFood数据:${box.get(it.id).name}===score=${it.score}")
}
// Timber.tag("FoodModule").d("queryFood数据:${nameScoreList.toJsonString()}")
idScoreList.filter { it.score < 0.20 }.forEach {
val food = box.get(it.id)
Timber.d("${food.name}|${food.foodIdx}|${it.score}")
if (1 - it.score >= THRESHOLD) {
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.name?.let { key ->
val count = map[key] ?: 0
map.put(key, count + 1)
}
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.name?.let { key ->
val count = map[key] ?: 0
map[key] = count + 1
}
}
val list = map.entries.sortedByDescending { it.value }.map { it.key }
return list
}
data class IdNameScore(
val id:Long,
val name:String,
val score: Double
)
private fun initFoodData(context: Context) {
if (box.all.isNotEmpty()) {
fun initDefFoodData(context: Context, action:()-> Unit={}) {
val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
if (count > 0) {
return
}
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
@@ -91,8 +135,9 @@ object FoodModule {
val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray()
box.put(Food(name = foodName, foodVector = array, foodIdx = index))
box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
}
action()
}
@@ -29,7 +29,8 @@ object BitmapSaver {
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
quality: Int = 100
): File? {
val dir = context.getExternalFilesDir(null)
//val dir = context.getExternalFilesDir(null)
val dir = context.cacheDir
return saveBitmap(bitmap, File(dir, fileName), format, quality)
}
@@ -0,0 +1,81 @@
package com.sw.dualscreen.utils
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.camera.core.CameraSelector
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
class CameraUtils(private var activity: ComponentActivity) {
private var cameraController: LifecycleCameraController? = null
private var photoCaptureHelper: PhotoCaptureHelper? = null
// private var isCameraReady = false
fun takePhoto(callback: (Uri) -> Unit) {
cameraController?.let {
if (photoCaptureHelper == null) {
initCaptureHelper()
}
}
photoCaptureHelper?.let {
it.addSuccessCallback(callback)
it.bindCameraCallback {
bind()
}
it.takePhoto()
}
}
private fun initCaptureHelper() {
photoCaptureHelper = PhotoCaptureHelper(
context = activity,
cameraController = cameraController!!,
onSuccess = {},
onError = { msg ->
//toast(msg)
}
)
}
fun setPreviewController(previewView: PreviewView?) {
if (previewView?.controller == null) {
previewView?.controller = cameraController
}
}
fun initCamera() {
if (cameraController == null) {
cameraController = LifecycleCameraController(activity).apply {
// 必须设置有效的用例
setEnabledUseCases(
CameraController.IMAGE_CAPTURE
// or CameraController.VIDEO_CAPTURE
)
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
bind()
}
// if (isCameraReady.not()) {
// try {
// cameraController!!.initializationFuture.addListener({
// isCameraReady = true
// Timber.d("Camera initialized successfully")
// }, ContextCompat.getMainExecutor(this))
// } catch (e: Exception) {
// Timber.d("Camera initialized error = ${e.message}")
// }
// }
}
fun bind() {
cameraController?.bindToLifecycle(activity)
}
fun unbind() {
cameraController?.unbind()
}
}
@@ -0,0 +1,92 @@
package com.sw.dualscreen.utils
import android.content.Context
import android.net.Uri
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.view.CameraController
import androidx.core.content.ContextCompat
import timber.log.Timber
import java.io.File
/**
* 拍照工具类
* @param context Context 上下文
* @param cameraController CameraController 相机控制器
* @param onSuccess (Uri) -> Unit 拍照成功回调
* @param onError (String) -> Unit 拍照失败回调
*/
class PhotoCaptureHelper(
private val context: Context,
private val cameraController: CameraController,
private val onSuccess: (Uri) -> Unit = {},
private val onError: (String) -> Unit = {}
) {
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
fun addSuccessCallback(callback:(Uri) -> Unit) {
if (callbackList.contains(callback).not()) {
callbackList.add(callback)
}
}
private var bindCamera:(()->Unit)?=null
fun bindCameraCallback(callback:()->Unit) {
this.bindCamera = callback
}
/**
* 拍照方法
* @param fileNamePrefix 文件名前缀,默认为"IMG_"
* @param fileExtension 文件扩展名,默认为".jpg"
*/
fun takePhoto(
fileNamePrefix: String = "IMG_",
fileExtension: String = ".jpg"
) {
Timber.d("开始拍照采集")
try {
val executor = ContextCompat.getMainExecutor(context)
val cacheDir = context.cacheDir
val photoFile = File.createTempFile(
"${fileNamePrefix}${System.currentTimeMillis()}",
fileExtension,
cacheDir
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
cameraController.takePicture(
outputOptions,
executor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
Timber.d("照片保存成功: $photoUri")
onSuccess(photoUri)
callbackList.forEach {
it(photoUri)
}
}
override fun onError(exception: ImageCaptureException) {
val errorMsg = "拍照失败: ${exception.message}"
if (errorMsg.contains("Not bound to a valid Camera")) {
if (bindCamera != null) {
bindCamera?.invoke()
//takePhoto()
}
}
Timber.e(exception, errorMsg)
onError(errorMsg)
}
}
)
} catch (e: Exception) {
val errorMsg = "创建临时文件失败: ${e.message}"
Timber.e(e, errorMsg)
onError(errorMsg)
}
}
}
@@ -65,6 +65,7 @@ abstract class BaseViewModel() : ViewModel() {
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
GlobalData.appBaseUrl="http://192.168.1.210/gateway/local"
GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
GlobalData.appId = equipmentInfo.arcsoftAppId!!
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
@@ -7,12 +7,12 @@
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="68dp"
android:layout_height="88dp"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/ivBack"
android:layout_width="50dp"
android:layout_width="68dp"
android:layout_height="68dp"
android:padding="10dp"
android:src="@drawable/ic_back" />
@@ -8,14 +8,15 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="68dp"
android:layout_height="88dp"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/ivBack"
android:layout_width="50dp"
android:layout_width="68dp"
android:layout_height="68dp"
android:padding="10dp"
android:padding="15dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_back" />
<TextView
@@ -31,7 +32,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="10dp">
android:layout_marginBottom="24dp">
<FrameLayout
android:id="@+id/flPreview"
@@ -54,6 +55,7 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/white"
android:orientation="vertical"
android:visibility="visible">
@@ -79,32 +81,32 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:orientation="vertical"
android:orientation="horizontal"
android:gravity="center_vertical">
<Button
android:id="@+id/btnTakePhoto"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_width="140dp"
android:layout_height="80dp"
android:gravity="center"
android:text="拍照"
android:text="开始拍照"
android:textColor="#ffffff"
android:textSize="18sp" />
<Button
android:id="@+id/btnClearData"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_height="80dp"
android:gravity="center"
android:text="清除"
android:layout_marginVertical="5dp"
android:layout_marginHorizontal="10dp"
android:textColor="#ffffff"
android:textSize="18sp" />
<Button
android:id="@+id/btnCollectedGoods"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="220dp"
android:layout_height="80dp"
android:gravity="center"
android:text="查看已采集菜品"
android:text="已采集菜品"
android:textColor="#ffffff"
android:textSize="18sp" />
@@ -125,9 +127,9 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="66dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="10dp"
android:layout_marginTop="24dp"
android:layout_marginRight="24dp"
android:gravity="center_vertical"
android:orientation="horizontal">
@@ -141,11 +143,11 @@
android:gravity="center_vertical"
android:hint="输入菜品名称"
android:imeOptions="actionSearch"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="18sp" />
android:textSize="26sp" />
<Button
android:id="@+id/btnFoodSearch"
@@ -154,7 +156,7 @@
android:layout_marginLeft="12dp"
android:text="菜品检索"
android:textColor="#ffffff"
android:textSize="16sp" />
android:textSize="26sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
@@ -171,9 +173,9 @@
android:layout_width="260dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:layout_marginVertical="10dp"
android:layout_marginVertical="24dp"
android:text="保存"
android:textColor="#ffffff"
android:textSize="20sp" />
android:textSize="26sp" />
</LinearLayout>
+9 -9
View File
@@ -2,15 +2,15 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="400dp"
android:layout_height="200dp"
android:layout_height="260dp"
android:gravity="center"
android:orientation="vertical"
android:background="@drawable/bg_white_radius12"
tools:ignore="HardcodedText">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_tip_warn"
tools:ignore="ContentDescription" />
@@ -19,11 +19,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp"
android:layout_marginVertical="20dp"
android:layout_marginVertical="30dp"
android:gravity="center"
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?"
android:textColor="#ff141428"
android:textSize="18sp" />
android:textSize="20sp" />
<LinearLayout
android:layout_width="wrap_content"
@@ -33,8 +33,8 @@
<TextView
android:id="@+id/btnCancel"
android:layout_width="100dp"
android:layout_height="40dp"
android:layout_width="120dp"
android:layout_height="50dp"
android:background="@drawable/bg_white_stroke_blue_ripple"
android:text="取消"
android:gravity="center"
@@ -43,8 +43,8 @@
<TextView
android:id="@+id/btnConfirm"
android:layout_width="100dp"
android:layout_height="40dp"
android:layout_width="120dp"
android:layout_height="50dp"
android:layout_marginStart="30dp"
android:background="@drawable/bg_blue_ripple"
android:text="确认"
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center"/>
</merge>