新增菜品采集
@@ -57,14 +57,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// implementation(
|
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
||||||
// fileTree(
|
|
||||||
// mapOf(
|
|
||||||
// "dir" to "libs",
|
|
||||||
// "include" to listOf("*.aar", "*.jar")
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import android.view.View
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.model.IActivityResult
|
||||||
import com.sw.dualscreen.view.CustomDialog
|
import com.sw.dualscreen.view.CustomDialog
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -81,4 +81,20 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
mDialogWaiting?.dismiss()
|
mDialogWaiting?.dismiss()
|
||||||
mDialogWaiting = null
|
mDialogWaiting = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var launchPermissionCallback: IActivityResult.RequestPermissionCallback? = null
|
||||||
|
|
||||||
|
fun requestSinglePermissionResult(
|
||||||
|
permission: String,
|
||||||
|
permissionCallback: IActivityResult.RequestPermissionCallback?
|
||||||
|
) {
|
||||||
|
this.launchPermissionCallback = permissionCallback
|
||||||
|
permissionLauncher.launch(permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val permissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { result: Boolean ->
|
||||||
|
launchPermissionCallback?.callback(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
|
import androidx.camera.core.ImageCaptureException
|
||||||
|
import androidx.camera.core.Preview
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.adapter.FoodCollectionAdapter
|
||||||
|
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
|
||||||
|
import com.sw.dualscreen.objbox.Food
|
||||||
|
import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||||
|
import com.sw.dualscreen.objbox.FoodModule
|
||||||
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
|
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
||||||
|
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||||
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
|
import com.sw.dualscreen.utils.StorageUtils
|
||||||
|
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
||||||
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
|
import com.sw.plate.utils.ToastUtils
|
||||||
|
import io.objectbox.Box
|
||||||
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import org.pytorch.IValue
|
||||||
|
import org.pytorch.Module
|
||||||
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||||
|
|
||||||
|
private val TAG = "FoodCollectionActivity"
|
||||||
|
private var imageCapture: ImageCapture? = null
|
||||||
|
private var isAnalyzing = true // 控制是否进行图像分析
|
||||||
|
private var imageAnalysis: ImageAnalysis? = null
|
||||||
|
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
|
||||||
|
private var presentation: SecondaryScreenPresentation? = null
|
||||||
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
private val viewModel by viewModels<UserViewModel>()
|
||||||
|
private var selectedFoodName: String? = ""
|
||||||
|
private var bottomSheetDialog: CustomBottomSheetDialog? = null
|
||||||
|
|
||||||
|
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 list: MutableList<FoodCollectionBean> = mutableListOf()
|
||||||
|
|
||||||
|
private val adapter: FoodCollectionAdapter by lazy {
|
||||||
|
FoodCollectionAdapter(list).apply {
|
||||||
|
setOnItemClickListener { _, _, position ->
|
||||||
|
if (list[position].isShowCamera) {
|
||||||
|
takePhoto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
|
||||||
|
list.removeAt(position)
|
||||||
|
adapter.notifyItemRemoved(position)
|
||||||
|
adapter.notifyItemRangeChanged(position, list.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewModel(): BaseViewModel {
|
||||||
|
return viewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inflateViewBinding(): ActivityFoodCollectionBinding {
|
||||||
|
return ActivityFoodCollectionBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initialize() {
|
||||||
|
binding.ivBack.setOnClickListener { finish() }
|
||||||
|
list.add(FoodCollectionBean(isShowCamera = true))
|
||||||
|
binding.rvFoodCollection.let {
|
||||||
|
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
|
||||||
|
it.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnFoodSearch.setOnClickListener {
|
||||||
|
bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
|
||||||
|
selectedFoodName = it.foodName
|
||||||
|
binding.tvSelectedFood.text = selectedFoodName
|
||||||
|
}
|
||||||
|
bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnSave.setOnClickListener {
|
||||||
|
if (selectedFoodName.isNullOrBlank()) {
|
||||||
|
Toast.makeText(this, "请选择菜品名称", Toast.LENGTH_SHORT).show()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
if (list.size <= 1) {
|
||||||
|
Toast.makeText(this, "请拍摄菜品照片", Toast.LENGTH_SHORT).show()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
vectorThread()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takePhoto() {
|
||||||
|
StorageUtils.requestCameraPermission(this) {
|
||||||
|
val outputFile = File(this.filesDir, "IMG_${System.currentTimeMillis()}.jpg")
|
||||||
|
outputFileUri =
|
||||||
|
FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", outputFile)
|
||||||
|
takePictureLauncher.launch(outputFileUri!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var outputFileUri: Uri? = null
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private val takePictureLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
||||||
|
if (success) {
|
||||||
|
//Loading.show(this)
|
||||||
|
if (list.size < 6) {
|
||||||
|
list.add(list.size - 1, FoodCollectionBean(imageUri = outputFileUri))
|
||||||
|
} else {
|
||||||
|
list[list.size - 1] = FoodCollectionBean(imageUri = outputFileUri)
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun vectorThread() {
|
||||||
|
//Loading.show(this)
|
||||||
|
showWaitingDialog("加载中……")
|
||||||
|
Thread {
|
||||||
|
list.forEachIndexed { index, it ->
|
||||||
|
image2VectorTask(it.imageUri!!, index)
|
||||||
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
window.decorView.postDelayed({
|
||||||
|
//Loading.dismiss()
|
||||||
|
hideWaitingDialog()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun image2VectorTask(imageUri: Uri, position: Int) {
|
||||||
|
if (module == null) {
|
||||||
|
module =
|
||||||
|
Module.load(FoodModule.copyAssetToCache(this, "best_embedding_model_mobile.pt"))
|
||||||
|
}
|
||||||
|
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 imageVector = outputTensor.dataAsFloatArray
|
||||||
|
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
|
||||||
|
list[position].let {
|
||||||
|
it.imageVector = imageVector
|
||||||
|
it.isFinish = true
|
||||||
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
adapter.notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.sw.dualscreen.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.chad.library.adapter4.BaseQuickAdapter
|
||||||
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.databinding.ListItemFoodCollectionBinding
|
||||||
|
import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||||
|
|
||||||
|
class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
|
||||||
|
BaseQuickAdapter<FoodCollectionBean, FoodCollectionAdapter.VH>(list) {
|
||||||
|
|
||||||
|
inner class VH(var binding: ListItemFoodCollectionBinding) : QuickViewHolder(binding.root)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val binding = ListItemFoodCollectionBinding.inflate(inflater, parent, false)
|
||||||
|
return VH(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: VH, position: Int, item: FoodCollectionBean?) {
|
||||||
|
val binding = holder.binding
|
||||||
|
item?.let {
|
||||||
|
binding.ivFinish.visibility = if (it.isFinish) View.VISIBLE else View.GONE
|
||||||
|
binding.ivDelete.visibility = if (it.isShowCamera) View.GONE else View.VISIBLE
|
||||||
|
binding.imageView.run {
|
||||||
|
if (it.isShowCamera) {
|
||||||
|
scaleType = ImageView.ScaleType.CENTER
|
||||||
|
setImageResource(R.drawable.ic_camera256)
|
||||||
|
} else {
|
||||||
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
|
setImageURI(it.imageUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.sw.dualscreen.model;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IActivityResult {
|
||||||
|
|
||||||
|
interface LaunchActivityCallback {
|
||||||
|
void callback(ActivityResult result);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LaunchTackPictureCallback {
|
||||||
|
void callback(boolean result);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestPermissionCallback {
|
||||||
|
void callback(boolean result);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestMultiplePermissionsCallback {
|
||||||
|
void callback(Map<String,Boolean> result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.sw.dualscreen.objbox
|
package com.sw.dualscreen.objbox
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
data class FoodClassInfo(
|
data class FoodClassInfo(
|
||||||
var class_names: List<String>,
|
var class_names: List<String>,
|
||||||
var class_to_idx: Map<String, Int>,
|
var class_to_idx: Map<String, Int>,
|
||||||
@@ -12,3 +15,11 @@ data class FoodClassInfo(
|
|||||||
// var foodIdx: Int,
|
// var foodIdx: Int,
|
||||||
// var score: Double
|
// var score: Double
|
||||||
//)
|
//)
|
||||||
|
|
||||||
|
data class FoodCollectionBean(
|
||||||
|
var imageUri: Uri? = null,
|
||||||
|
var bitmap: Bitmap? = null,
|
||||||
|
var imageVector: FloatArray? = null,
|
||||||
|
var isShowCamera: Boolean = false,
|
||||||
|
var isFinish:Boolean = false
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import com.sw.dualscreen.activity.BaseActivity
|
||||||
|
|
||||||
|
object StorageUtils {
|
||||||
|
|
||||||
|
fun requestPermission(activity: BaseActivity<*>, callback: () -> Unit) {
|
||||||
|
activity.requestSinglePermissionResult(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
// activity.requestSinglePermissionResult(Manifest.permission.CAMERA) {
|
||||||
|
//// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
//// if (Environment.isExternalStorageManager()) {
|
||||||
|
//// callback()
|
||||||
|
//// } else {
|
||||||
|
//// val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
////// activity.startActivityForResult(intent, MANAGE_REQUEST_CODE)
|
||||||
|
//// activity.launchActivityResult(intent) {
|
||||||
|
//// callback()
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//// } else {
|
||||||
|
//
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestCameraPermission(activity: BaseActivity<*>, callback: () -> Unit) {
|
||||||
|
activity.requestSinglePermissionResult(Manifest.permission.CAMERA) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#ffffff"/>
|
||||||
|
<stroke android:color="#cdcdcd" android:width="1dp"/>
|
||||||
|
<corners android:radius="5dp"/>
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivBack"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:src="@drawable/ic_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="菜品采集"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvFoodCollection"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="3"
|
||||||
|
tools:itemCount="6"
|
||||||
|
tools:listitem="@layout/list_item_food_collection" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSelectedFood"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginHorizontal="6dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingEnd="5dp"
|
||||||
|
android:hint="选择菜品名称"
|
||||||
|
android:background="@drawable/bg_gray"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnFoodSearch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:text="菜品检索"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSave"
|
||||||
|
android:layout_width="260dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:text="保存"
|
||||||
|
android:layout_marginVertical="20dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="110dp"
|
||||||
|
android:background="@drawable/bg_gray"
|
||||||
|
android:layout_margin="3dp"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="center"
|
||||||
|
tools:src="@drawable/ic_camera256"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivDelete"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:src="@drawable/ic_delete_red"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivFinish"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:src="@drawable/ic_finish"/>
|
||||||
|
</FrameLayout>
|
||||||