refactor: 提取净材种类弹窗为工具类并统一各页面选择逻辑

- 新增 RawMaterialsDialogUtils,包含 showRawMaterialsDialog、
  buildSingleChoiceChipGroup、buildMockGoodsList 三个顶层函数
- FoodAdapter/list_item_food 新增净材种类选中结果展示
- VectorCollectionFragment、FoodRecognizeActivity、FoodSearchDialog
  的食材点击均改为弹出净材种类单选弹窗后再确认选中
- FoodSearchDialog 空布局补充点击加载测试数据
- setting_border 新增圆角
This commit is contained in:
2026-05-11 12:07:06 +08:00
parent 63911fe432
commit ec4c3f1232
7 changed files with 278 additions and 211 deletions
@@ -48,6 +48,14 @@ class FoodAdapter(list: MutableList<GoodsItem>) :
it.text = foodScoreText it.text = foodScoreText
} }
} }
if (item.rawMaterialsType.isNullOrBlank()) {
holder.binding.tvRawMaterialsType.gone()
} else {
holder.binding.tvRawMaterialsType.let {
it.visible()
it.text = item.rawMaterialsType
}
}
} }
override fun onCreateViewHolder( override fun onCreateViewHolder(
@@ -18,6 +18,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.shuwei.dish.match.R import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.FoodAdapter import com.shuwei.dish.match.adapter.FoodAdapter
@@ -28,6 +29,8 @@ import com.shuwei.dish.match.dialog.SeasoningSelectDialog.Companion.PAGE_SIZE
import com.shuwei.dish.match.entity.GoodsItem import com.shuwei.dish.match.entity.GoodsItem
import com.shuwei.dish.match.net.UiState import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.KeyboardUtil import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.buildMockGoodsList
import com.shuwei.dish.match.utils.showRawMaterialsDialog
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.toast
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -50,36 +53,25 @@ class FoodSearchDialog(
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity)) private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
private val list = mutableListOf<GoodsItem>() private val list = mutableListOf<GoodsItem>()
// .apply {
// add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
// add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
// add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
// add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
// add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
// add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
// add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
// add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
// add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
// add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
// add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
// add(GoodsItem(goodsId = "200012", goodsName = "A"))
// add(GoodsItem(goodsId = "200013", goodsName = "B"))
// add(GoodsItem(goodsId = "200014", goodsName = "C"))
// add(GoodsItem(goodsId = "200015", goodsName = "D"))
// add(GoodsItem(goodsId = "200016", goodsName = "E"))
// add(GoodsItem(goodsId = "200017", goodsName = "F"))
// add(GoodsItem(goodsId = "200018", goodsName = "G"))
// add(GoodsItem(goodsId = "200019", goodsName = "H"))
// }
private val adapter = FoodAdapter(list).apply { private val adapter = FoodAdapter(list).apply {
isStateViewEnable = true isStateViewEnable = true
setOnItemClickListener { _, _, position -> setOnDebouncedItemClick { _, _, position ->
list[position].isClicked = true val item = list[position]
notifyItemChanged(position) // 弹出净材种类单选弹窗,确认后再回调并关闭弹窗
binding.root.postDelayed({ showRawMaterialsDialog(
onItemSelected(list[position]) context = activity,
dismiss() types = item.rawMaterialsTypes,
}, 300) checkedType = item.rawMaterialsType
) { selectedType ->
item.rawMaterialsType = selectedType
item.isClicked = true
notifyItemChanged(position)
binding.root.postDelayed({
onItemSelected(item)
dismiss()
}, 300)
}
} }
} }
@@ -241,6 +233,11 @@ class FoodSearchDialog(
} }
binding.recyclerView.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false) binding.recyclerView.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
if (pageNo == 1) list.clear() if (pageNo == 1) list.clear()
// 将接口返回的 rawMaterialsType(全部种类)移存到 rawMaterialsTypes,供弹窗使用
records.forEach {
it.rawMaterialsTypes = it.rawMaterialsType
it.rawMaterialsType = null
}
list.addAll(records) list.addAll(records)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= PAGE_SIZE val isLoadMoreEnable = records.size >= PAGE_SIZE
@@ -269,16 +266,19 @@ class FoodSearchDialog(
false false
) )
} }
emptyViewBinding!!.tvContent.text = "暂无数据" emptyViewBinding?.let {
emptyViewBinding!!.tvSubContent.text = "未查询到食材信息,请联系管理员添加" it.tvContent.text = "暂无数据"
emptyViewBinding!!.root.layoutParams = FrameLayout.LayoutParams( it.tvSubContent.text = "未查询到食材信息,请联系管理员添加"
FrameLayout.LayoutParams.MATCH_PARENT, it.root.setOnClickListener { loadGoodsList(buildMockGoodsList().toMutableList()) }
FrameLayout.LayoutParams.MATCH_PARENT it.root.layoutParams = FrameLayout.LayoutParams(
) FrameLayout.LayoutParams.MATCH_PARENT,
binding.recyclerView.layoutManager = LinearLayoutManager(activity) FrameLayout.LayoutParams.MATCH_PARENT
binding.recyclerView.post { )
emptyViewBinding!!.root.minimumHeight = binding.recyclerView.height binding.recyclerView.layoutManager = LinearLayoutManager(activity)
adapter.stateView = emptyViewBinding!!.root binding.recyclerView.post {
it.root.minimumHeight = binding.recyclerView.height
adapter.stateView = it.root
}
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -25,6 +25,7 @@ import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.objbox.FoodModule import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.showRawMaterialsDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -122,12 +123,21 @@ class FoodRecognizeActivity : BaseActivity() {
private val adapter = FoodAdapter(list).apply { private val adapter = FoodAdapter(list).apply {
setOnItemClickListener { _, _, position -> setOnItemClickListener { _, _, position ->
list.forEachIndexed { i, item -> item.isClicked = (i == position) } val item = list[position]
// 点击选择时记录当前秤重量 // 弹出净材种类单选弹窗,确认后再选中该食材
list[position].useWeight = currentWeight showRawMaterialsDialog(
notifyDataSetChanged() context = this@FoodRecognizeActivity,
selectedPosition = position types = item.rawMaterialsTypes,
binding.tvSelectedFood.text = list[position].goodsName ?: "-" checkedType = item.rawMaterialsType
) { selectedType ->
item.rawMaterialsType = selectedType
list.forEachIndexed { i, it -> it.isClicked = (i == position) }
// 点击选择时记录当前秤重量
item.useWeight = currentWeight
notifyDataSetChanged()
selectedPosition = position
binding.tvSelectedFood.text = item.goodsName ?: "-"
}
} }
} }
@@ -260,6 +270,9 @@ class FoodRecognizeActivity : BaseActivity() {
goodsList.forEach { goodsList.forEach {
val food = nameScoreMap[it.goodsName] val food = nameScoreMap[it.goodsName]
val foodScore = ((1 - (food?.score ?: 0.0)) * 10000).toInt() val foodScore = ((1 - (food?.score ?: 0.0)) * 10000).toInt()
// 将接口返回的 rawMaterialsType(全部种类)移存到 rawMaterialsTypes,供弹窗使用
it.rawMaterialsTypes = it.rawMaterialsType
it.rawMaterialsType = null
list.add(it.also { it.foodScore = foodScore }) list.add(it.also { it.foodScore = foodScore })
} }
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
@@ -3,7 +3,6 @@ package com.shuwei.dish.match.ui.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
@@ -12,10 +11,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout import androidx.core.view.doOnLayout
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -32,7 +28,6 @@ import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.dialog.Loading import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.entity.FoodCollectionBean import com.shuwei.dish.match.entity.FoodCollectionBean
import com.shuwei.dish.match.entity.GoodsItem import com.shuwei.dish.match.entity.GoodsItem
@@ -48,6 +43,9 @@ import com.shuwei.dish.match.utils.Debouncer
import com.shuwei.dish.match.utils.ImageUploader import com.shuwei.dish.match.utils.ImageUploader
import com.shuwei.dish.match.utils.ImageUtil import com.shuwei.dish.match.utils.ImageUtil
import com.shuwei.dish.match.utils.LogSaveUtil import com.shuwei.dish.match.utils.LogSaveUtil
import com.shuwei.dish.match.utils.buildSingleChoiceChipGroup
import com.shuwei.dish.match.utils.buildMockGoodsList
import com.shuwei.dish.match.utils.showRawMaterialsDialog
import com.shuwei.dish.match.utils.ext.clickWithDebounce import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.gone
@@ -81,9 +79,27 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
isStateViewEnable = true isStateViewEnable = true
setOnDebouncedItemClick { _, _, position -> setOnDebouncedItemClick { _, _, position ->
val item = searchList[position] val item = searchList[position]
val types = item.rawMaterialsTypes
// 弹出净材种类单选弹窗 // 弹出净材种类单选弹窗
showRawMaterialsDialog(item, position, types) showRawMaterialsDialog(
context = requireContext(),
types = item.rawMaterialsTypes,
checkedType = item.rawMaterialsType
) { selectedType ->
// 清空其他已选项的状态
searchList.forEachIndexed { index, it ->
if (index != position) {
it.isClicked = false
it.rawMaterialsType = null
}
}
// 设置当前选中项
item.isClicked = true
item.rawMaterialsType = selectedType
checkedItem = item
selectedFoodId = item.goodsId
selectedFoodName = item.goodsName
notifyDataSetChanged()
}
} }
} }
} }
@@ -424,148 +440,6 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
} }
} }
/**
* 构造用于效果测试的食材假数据
* TODO: 联调完成后删除
*/
private fun buildMockGoodsList(): List<GoodsItem> = mutableListOf(
GoodsItem(goodsId = "1", goodsName = "土豆", rawMaterialsType = "土豆丝,土豆条,土豆片,土豆丁"),
GoodsItem(goodsId = "2", goodsName = "胡萝卜", rawMaterialsType = "胡萝卜丝,胡萝卜片,胡萝卜丁"),
GoodsItem(goodsId = "3", goodsName = "黄瓜", rawMaterialsType = "黄瓜丝,黄瓜片,黄瓜丁,黄瓜块"),
GoodsItem(goodsId = "4", goodsName = "洋葱", rawMaterialsType = "洋葱丝,洋葱丁,洋葱圈"),
GoodsItem(goodsId = "5", goodsName = "白菜", rawMaterialsType = "白菜丝,白菜块,白菜叶"),
GoodsItem(goodsId = "6", goodsName = "豆腐", rawMaterialsType = "豆腐块,豆腐丁"),
GoodsItem(goodsId = "7", goodsName = "猪肉", rawMaterialsType = "猪肉丝,猪肉片,猪肉丁,猪肉块"),
GoodsItem(goodsId = "8", goodsName = "鸡胸肉", rawMaterialsType = "鸡胸肉丝,鸡胸肉片,鸡胸肉丁"),
GoodsItem(goodsId = "9", goodsName = "牛肉", rawMaterialsType = "牛肉丝,牛肉片,牛肉块"),
GoodsItem(goodsId = "10", goodsName = "虾仁", rawMaterialsType = "整虾仁,切段虾仁"),
GoodsItem(goodsId = "11", goodsName = "茄子", rawMaterialsType = "茄子丝,茄子片,茄子块,茄子条"),
GoodsItem(goodsId = "12", goodsName = "青椒", rawMaterialsType = "青椒丝,青椒片,青椒块"),
GoodsItem(goodsId = "13", goodsName = "西红柿", rawMaterialsType = "西红柿片,西红柿块,西红柿丁"),
GoodsItem(goodsId = "14", goodsName = "芹菜", rawMaterialsType = "芹菜段,芹菜丝"),
GoodsItem(goodsId = "15", goodsName = "莲藕", rawMaterialsType = "莲藕片,莲藕丁,莲藕丝"),
// 以下5条 rawMaterialsType 为空,测试无净材数据场景
GoodsItem(goodsId = "16", goodsName = "菠菜"),
GoodsItem(goodsId = "17", goodsName = "生菜"),
GoodsItem(goodsId = "18", goodsName = "香菇"),
GoodsItem(goodsId = "19", goodsName = "木耳"),
GoodsItem(goodsId = "20", goodsName = "豆芽"),
)
/**
* 弹出净材种类单选弹窗
* @param item 当前点击的食材项
* @param position 在 searchList 中的位置
* @param types 净材种类字符串(逗号分隔),为空时提示无数据
*/
@SuppressLint("NotifyDataSetChanged")
private fun showRawMaterialsDialog(item: GoodsItem, position: Int, types: String?) {
val typeList = types?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() } ?: emptyList()
val dialog = CommonDialog(requireContext())
.setTitle("选择净材种类")
.setNegativeButton("取消")
if (typeList.isEmpty()) {
// 无净材数据时仅展示提示文字
dialog.setContent("无净材数据")
.setPositiveButton("确认") {}
} else {
// 构建单选 ChipGroup 作为自定义内容
val chipGroup = buildSingleChoiceChipGroup(typeList, item.rawMaterialsType)
dialog.addContentView(chipGroup) { tvContent ->
tvContent.gone()
}
dialog.setPositiveButtonInterceptable("确认") {
val checkedId = chipGroup.checkedChipId
if (checkedId == View.NO_ID) {
toast("请选择净材种类")
// 返回 false 阻止弹窗关闭
return@setPositiveButtonInterceptable false
}
val selectedType = typeList[checkedId]
// 清空其他已选项的状态
searchList.forEachIndexed { index, it ->
if (index != position) {
it.isClicked = false
it.rawMaterialsType = null
}
}
// 设置当前选中项
item.isClicked = true
item.rawMaterialsType = selectedType
checkedItem = item
selectedFoodId = item.goodsId
selectedFoodName = item.goodsName
searchAdapter.notifyDataSetChanged()
true
}
}
dialog.show()
}
/**
* 构建单选样式的 ChipGroup
* @param typeList 选项文本列表,列表中每一项作为一个 Chip 的文字
* @param checkedType 默认选中项的文本,与 typeList 中元素相等的 Chip 会被勾选;为空则无默认选中
* @return 配置完成的 ChipGroup,每个 Chip 的 id 即为其在 typeList 中的下标
*/
private fun buildSingleChoiceChipGroup(
typeList: List<String>,
checkedType: String?
): ChipGroup {
return ChipGroup(requireContext()).apply {
isSingleSelection = true
isSelectionRequired = false
setPadding(5.dp, 40.dp, 5.dp, 40.dp)
chipSpacingVertical = 10.dp
chipSpacingHorizontal = 20.dp
typeList.forEachIndexed { index, typeName ->
val chip = Chip(requireContext()).apply {
id = index
text = typeName
textSize = 22f
isCheckable = true
chipStartPadding = 20.dp.toFloat()
chipEndPadding = 20.dp.toFloat()
chipMinHeight = (textSize + 15.dp * 2).toFloat()
// 选中/未选中态背景色:主题绿 / 浅灰
chipBackgroundColor = ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked),
intArrayOf(-android.R.attr.state_checked)
),
intArrayOf(
ContextCompat.getColor(requireContext(), R.color.dish_green),
ContextCompat.getColor(requireContext(), R.color.gray_eb)
)
)
// 选中/未选中态文字色:白 / 深灰
setTextColor(
ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked),
intArrayOf(-android.R.attr.state_checked)
),
intArrayOf(
ContextCompat.getColor(requireContext(), R.color.white),
ContextCompat.getColor(requireContext(), R.color.black333)
)
)
)
// 隐藏默认的左侧勾选图标,让填充色作为唯一选中反馈
isCheckedIconVisible = false
// 去除描边
chipStrokeWidth = 0f
// 若当前项已有选中值则默认勾选对应选项
isChecked = (typeName == checkedType)
}
addView(chip)
}
}
}
var clickIndex = -1 var clickIndex = -1
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
@@ -0,0 +1,151 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.content.res.ColorStateList
import android.view.View
import androidx.core.content.ContextCompat
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.shuwei.dish.match.R
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.GoodsItem
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.toast
/**
* 构造用于效果测试的食材假数据
* TODO: 联调完成后删除
*/
fun buildMockGoodsList(): List<GoodsItem> = mutableListOf(
GoodsItem(goodsId = "1", goodsName = "土豆", rawMaterialsType = "土豆丝,土豆条,土豆片,土豆丁"),
GoodsItem(goodsId = "2", goodsName = "胡萝卜", rawMaterialsType = "胡萝卜丝,胡萝卜片,胡萝卜丁"),
GoodsItem(goodsId = "3", goodsName = "黄瓜", rawMaterialsType = "黄瓜丝,黄瓜片,黄瓜丁,黄瓜块"),
GoodsItem(goodsId = "4", goodsName = "洋葱", rawMaterialsType = "洋葱丝,洋葱丁,洋葱圈"),
GoodsItem(goodsId = "5", goodsName = "白菜", rawMaterialsType = "白菜丝,白菜块,白菜叶"),
GoodsItem(goodsId = "6", goodsName = "豆腐", rawMaterialsType = "豆腐块,豆腐丁"),
GoodsItem(goodsId = "7", goodsName = "猪肉", rawMaterialsType = "猪肉丝,猪肉片,猪肉丁,猪肉块"),
GoodsItem(goodsId = "8", goodsName = "鸡胸肉", rawMaterialsType = "鸡胸肉丝,鸡胸肉片,鸡胸肉丁"),
GoodsItem(goodsId = "9", goodsName = "牛肉", rawMaterialsType = "牛肉丝,牛肉片,牛肉块"),
GoodsItem(goodsId = "10", goodsName = "虾仁", rawMaterialsType = "整虾仁,切段虾仁"),
GoodsItem(goodsId = "11", goodsName = "茄子", rawMaterialsType = "茄子丝,茄子片,茄子块,茄子条"),
GoodsItem(goodsId = "12", goodsName = "青椒", rawMaterialsType = "青椒丝,青椒片,青椒块"),
GoodsItem(goodsId = "13", goodsName = "西红柿", rawMaterialsType = "西红柿片,西红柿块,西红柿丁"),
GoodsItem(goodsId = "14", goodsName = "芹菜", rawMaterialsType = "芹菜段,芹菜丝"),
GoodsItem(goodsId = "15", goodsName = "莲藕", rawMaterialsType = "莲藕片,莲藕丁,莲藕丝"),
// 以下5条 rawMaterialsType 为空,测试无净材数据场景
GoodsItem(goodsId = "16", goodsName = "菠菜"),
GoodsItem(goodsId = "17", goodsName = "生菜"),
GoodsItem(goodsId = "18", goodsName = "香菇"),
GoodsItem(goodsId = "19", goodsName = "木耳"),
GoodsItem(goodsId = "20", goodsName = "豆芽"),
)
/**
* 弹出净材种类单选弹窗
* 确认选中后通过 onConfirmed 回调返回选中的种类文本,业务逻辑由调用方自行处理;
* 无净材数据时展示提示文字,点击确认直接关闭弹窗。
*
* @param context 上下文
* @param types 净材种类字符串(逗号分隔),为空时提示无数据
* @param checkedType 弹窗打开时的默认选中项(与 types 中元素对比),null 表示无默认选中
* @param onConfirmed 用户选中并确认后的回调,参数为选中的净材种类文本
*/
fun showRawMaterialsDialog(
context: Context,
types: String?,
checkedType: String?,
onConfirmed: (selectedType: String) -> Unit
) {
val typeList = types?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() } ?: emptyList()
val dialog = CommonDialog(context)
.setTitle("选择净材种类")
.setNegativeButton("取消")
if (typeList.isEmpty()) {
// 无净材数据时仅展示提示文字
dialog.setContent("无净材数据")
.setPositiveButton("确认") {}
} else {
// 构建单选 ChipGroup 作为自定义内容
val chipGroup = buildSingleChoiceChipGroup(context, typeList, checkedType)
dialog.addContentView(chipGroup) { tvContent -> tvContent.gone() }
dialog.setPositiveButtonInterceptable("确认") {
val checkedId = chipGroup.checkedChipId
if (checkedId == View.NO_ID) {
context.toast("请选择净材种类")
// 返回 false 阻止弹窗关闭
return@setPositiveButtonInterceptable false
}
onConfirmed(typeList[checkedId])
true
}
}
dialog.show()
}
/**
* 构建单选样式的 ChipGroup
*
* @param context 上下文
* @param typeList 选项文本列表,每一项作为一个 Chip 的文字
* @param checkedType 默认选中项的文本,与 typeList 中元素相等的 Chip 会被勾选;为空则无默认选中
* @return 配置完成的 ChipGroup,每个 Chip 的 id 即为其在 typeList 中的下标
*/
fun buildSingleChoiceChipGroup(
context: Context,
typeList: List<String>,
checkedType: String?
): ChipGroup {
return ChipGroup(context).apply {
isSingleSelection = true
isSelectionRequired = false
setPadding(5.dp, 40.dp, 5.dp, 40.dp)
chipSpacingVertical = 10.dp
chipSpacingHorizontal = 20.dp
typeList.forEachIndexed { index, typeName ->
val chip = Chip(context).apply {
id = index
text = typeName
textSize = 22f
isCheckable = true
chipStartPadding = 20.dp.toFloat()
chipEndPadding = 20.dp.toFloat()
chipMinHeight = (textSize + 15.dp * 2).toFloat()
// 选中/未选中态背景色:主题绿 / 浅灰
chipBackgroundColor = ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked),
intArrayOf(-android.R.attr.state_checked)
),
intArrayOf(
ContextCompat.getColor(context, R.color.dish_green),
ContextCompat.getColor(context, R.color.gray_eb)
)
)
// 选中/未选中态文字色:白 / 深灰
setTextColor(
ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_checked),
intArrayOf(-android.R.attr.state_checked)
),
intArrayOf(
ContextCompat.getColor(context, R.color.white),
ContextCompat.getColor(context, R.color.black333)
)
)
)
// 隐藏默认的左侧勾选图标,让填充色作为唯一选中反馈
isCheckedIconVisible = false
// 去除描边
chipStrokeWidth = 0f
// 若当前项已有选中值则默认勾选对应选项
isChecked = (typeName == checkedType)
}
addView(chip)
}
}
}
@@ -3,4 +3,5 @@
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#FFF4FAFF"/> <solid android:color="#FFF4FAFF"/>
<stroke android:width="2dp" android:color="@color/white"/> <stroke android:width="2dp" android:color="@color/white"/>
<corners android:radius="8dp"/>
</shape> </shape>
+38 -18
View File
@@ -1,35 +1,55 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="100dp"
android:layout_margin="15dp">
<TextView <TextView
android:id="@+id/tvGoodsInfo" android:id="@+id/tvGoodsInfo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="match_parent"
android:background="@drawable/shape_white_12_corners"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingHorizontal="5dp"
android:textColor="@color/black666" android:textColor="@color/black666"
android:textSize="30sp" android:textSize="30sp"
android:paddingHorizontal="5dp" tools:text="椒盐" />
android:gravity="center"
android:layout_margin="15dp" <TextView
tools:text="椒盐" android:id="@+id/tvRawMaterialsType"
android:maxLines="1" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:background="@drawable/shape_white_12_corners"/> android:gravity="center"
android:maxLines="1"
android:textColor="@color/black999"
android:textSize="18sp"
android:visibility="gone"
android:layout_marginBottom="5dp"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tvFoodScore"
app:layout_constraintStart_toStartOf="parent"
tools:text="切丝" />
<TextView <TextView
android:id="@+id/tvFoodScore" android:id="@+id/tvFoodScore"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/black666"
android:textSize="16sp"
android:layout_marginBottom="20dp"
android:layout_gravity="bottom|center_horizontal"
tools:text="100%"
android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:visibility="gone"/> android:maxLines="1"
android:textColor="@color/black666"
android:layout_marginStart="10dp"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="@id/tvRawMaterialsType"
app:layout_constraintBottom_toBottomOf="@id/tvRawMaterialsType"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tvRawMaterialsType"
tools:text="100%" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>