feat(form): 实现餐品净菜包装表单功能
- 新增 CHILDREN 字段类型支持动态子列表 - 实现包含食材的嵌套表单结构 - 添加餐品包装相关字段配置 - 集成下拉选项和日期选择器组件 - 创建食材行适配器处理子列表渲染 - 更新表单布局和样式资源文件
This commit is contained in:
@@ -11,6 +11,7 @@ import android.text.style.ForegroundColorSpan
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.graphics.toColorInt
|
import androidx.core.graphics.toColorInt
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.chad.library.adapter4.BaseQuickAdapter
|
import com.chad.library.adapter4.BaseQuickAdapter
|
||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
@@ -57,9 +58,11 @@ class FormFieldAdapter(
|
|||||||
b.tvLabel.text = buildLabelSpannable(item.label, item.required)
|
b.tvLabel.text = buildLabelSpannable(item.label, item.required)
|
||||||
|
|
||||||
// 先全部隐藏,再按类型显示对应控件
|
// 先全部隐藏,再按类型显示对应控件
|
||||||
|
b.flInputArea.gone()
|
||||||
b.etInput.gone()
|
b.etInput.gone()
|
||||||
b.flDropdown.gone()
|
b.flDropdown.gone()
|
||||||
b.flDatePicker.gone()
|
b.flDatePicker.gone()
|
||||||
|
b.llChildren.gone()
|
||||||
|
|
||||||
// 移除旧的 TextWatcher,防止 RecyclerView 复用时错误触发
|
// 移除旧的 TextWatcher,防止 RecyclerView 复用时错误触发
|
||||||
(b.etInput.tag as? TextWatcher)?.let { b.etInput.removeTextChangedListener(it) }
|
(b.etInput.tag as? TextWatcher)?.let { b.etInput.removeTextChangedListener(it) }
|
||||||
@@ -75,6 +78,15 @@ class FormFieldAdapter(
|
|||||||
FieldType.FIXED -> bindFixed(b, item)
|
FieldType.FIXED -> bindFixed(b, item)
|
||||||
FieldType.DROPDOWN -> bindDropdown(b, item)
|
FieldType.DROPDOWN -> bindDropdown(b, item)
|
||||||
FieldType.DATE_PICKER -> bindDatePicker(b, item)
|
FieldType.DATE_PICKER -> bindDatePicker(b, item)
|
||||||
|
FieldType.CHILDREN -> bindChildren(b, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type == FieldType.CHILDREN) {
|
||||||
|
b.flInputArea.gone()
|
||||||
|
b.llChildren.visible()
|
||||||
|
} else {
|
||||||
|
b.flInputArea.visible()
|
||||||
|
b.llChildren.gone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,4 +201,54 @@ class FormFieldAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 绑定 CHILDREN 类型:渲染子列表(表头 + 嵌套 RecyclerView + 添加按钮) */
|
||||||
|
private fun bindChildren(b: ListItemFormFieldBinding, item: FormField) {
|
||||||
|
b.llChildren.visible()
|
||||||
|
b.tvAddChild.text = item.childAddLabel ?: "+ 添加"
|
||||||
|
|
||||||
|
// 初始化嵌套 RecyclerView
|
||||||
|
if (b.rvChildren.adapter == null) {
|
||||||
|
b.rvChildren.layoutManager = LinearLayoutManager(context)
|
||||||
|
}
|
||||||
|
b.rvChildren.adapter = createRowAdapter(b, item)
|
||||||
|
|
||||||
|
// 添加按钮点击
|
||||||
|
b.tvAddChild.setOnClickListener {
|
||||||
|
item.children?.let { template ->
|
||||||
|
val newRow = template.map { field ->
|
||||||
|
FormField(
|
||||||
|
label = field.label,
|
||||||
|
type = field.type,
|
||||||
|
apiKey = field.apiKey,
|
||||||
|
extraApiKeys = field.extraApiKeys,
|
||||||
|
required = field.required,
|
||||||
|
hint = field.hint,
|
||||||
|
options = field.options,
|
||||||
|
submitValueId = field.submitValueId
|
||||||
|
)
|
||||||
|
}.toMutableList()
|
||||||
|
item.childrenRows.add(newRow)
|
||||||
|
b.rvChildren.adapter?.notifyItemInserted(item.childrenRows.size - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建子列表行 Adapter */
|
||||||
|
private fun createRowAdapter(b: ListItemFormFieldBinding, item: FormField): IngredientRowAdapter {
|
||||||
|
return IngredientRowAdapter(
|
||||||
|
rows = item.childrenRows,
|
||||||
|
onDelete = onDelete@{ position ->
|
||||||
|
val adapter = b.rvChildren.adapter as? IngredientRowAdapter ?: return@onDelete
|
||||||
|
item.childrenRows.removeAt(position)
|
||||||
|
adapter.notifyItemRemoved(position)
|
||||||
|
if (position < item.childrenRows.size) {
|
||||||
|
adapter.notifyItemRangeChanged(position, item.childrenRows.size - position)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChildClick = { _, _, _ ->
|
||||||
|
// 子字段值已直接更新到 row 的 FormField 中,无需额外操作
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package com.shuwei.dish.match.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.chad.library.adapter4.BaseQuickAdapter
|
||||||
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
|
import com.shuwei.dish.match.R
|
||||||
|
import com.shuwei.dish.match.databinding.ListItemIngredientRowBinding
|
||||||
|
import com.shuwei.dish.match.dialog.DropdownPopup
|
||||||
|
import com.shuwei.dish.match.model.FormField
|
||||||
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
import com.shuwei.dish.match.utils.ext.hideKeyboard
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 食材行 Adapter,用于「包含食材」子列表的嵌套渲染
|
||||||
|
* @param context Activity 上下文
|
||||||
|
* @param rows 每行包含多个 FormField,对应溯源码、食材名称、分类、用量
|
||||||
|
* @param onDelete 删除行回调
|
||||||
|
* @param onChildClick 子字段点击回调(如下拉框点击)
|
||||||
|
*/
|
||||||
|
class IngredientRowAdapter(
|
||||||
|
rows: MutableList<MutableList<FormField>>,
|
||||||
|
private val onDelete: (Int) -> Unit,
|
||||||
|
private val onChildClick: (Int, Int, FormField) -> Unit
|
||||||
|
) : BaseQuickAdapter<MutableList<FormField>, IngredientRowAdapter.VH>(rows) {
|
||||||
|
|
||||||
|
inner class VH(val binding: ListItemIngredientRowBinding) : QuickViewHolder(binding.root)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||||
|
val binding = ListItemIngredientRowBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
|
return VH(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: VH, position: Int, item: MutableList<FormField>?) {
|
||||||
|
item ?: return
|
||||||
|
val b = holder.binding
|
||||||
|
|
||||||
|
// 假设 children 模板顺序:[溯源码(TEXT), 食材名称(DROPDOWN), 分类(DROPDOWN), 用量(TEXT)]
|
||||||
|
val traceCodeField = item.getOrNull(0)
|
||||||
|
val materNameField = item.getOrNull(1)
|
||||||
|
val classifyField = item.getOrNull(2)
|
||||||
|
val amountField = item.getOrNull(3)
|
||||||
|
|
||||||
|
// 溯源码 - TEXT
|
||||||
|
traceCodeField?.let { field ->
|
||||||
|
b.etTraceCode.setText(field.value)
|
||||||
|
b.etTraceCode.hint = field.hint.ifBlank { field.label }
|
||||||
|
(b.etTraceCode.tag as? TextWatcher)?.let { b.etTraceCode.removeTextChangedListener(it) }
|
||||||
|
val watcher = object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
field.value = s?.toString() ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.etTraceCode.addTextChangedListener(watcher)
|
||||||
|
b.etTraceCode.tag = watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// 食材名称 - DROPDOWN
|
||||||
|
materNameField?.let { field ->
|
||||||
|
b.tvMaterName.hint = field.hint.ifBlank { "请选择${field.label}" }
|
||||||
|
b.tvMaterName.text = field.value.ifBlank { null }
|
||||||
|
b.flMaterName.setOnClickListener { v ->
|
||||||
|
v.hideKeyboard()
|
||||||
|
showDropdown(b.flMaterName, field, b.tvMaterName, holder.getAdapterPosition(), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分类 - DROPDOWN
|
||||||
|
classifyField?.let { field ->
|
||||||
|
b.tvClassify.hint = field.hint.ifBlank { "请选择${field.label}" }
|
||||||
|
b.tvClassify.text = field.value.ifBlank { null }
|
||||||
|
b.flClassify.setOnClickListener { v ->
|
||||||
|
v.hideKeyboard()
|
||||||
|
showDropdown(b.flClassify, field, b.tvClassify, holder.getAdapterPosition(), 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用量 - TEXT
|
||||||
|
amountField?.let { field ->
|
||||||
|
b.etAmount.setText(field.value)
|
||||||
|
b.etAmount.hint = field.hint.ifBlank { field.label }
|
||||||
|
(b.etAmount.tag as? TextWatcher)?.let { b.etAmount.removeTextChangedListener(it) }
|
||||||
|
val watcher = object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
field.value = s?.toString() ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.etAmount.addTextChangedListener(watcher)
|
||||||
|
b.etAmount.tag = watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除按钮
|
||||||
|
b.tvRemove.setOnClickListener {
|
||||||
|
val pos = holder.layoutPosition
|
||||||
|
if (pos != RecyclerView.NO_POSITION) {
|
||||||
|
onDelete(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 显示下拉弹出框 */
|
||||||
|
private fun showDropdown(
|
||||||
|
anchorView: ViewGroup,
|
||||||
|
field: FormField,
|
||||||
|
valueView: TextView,
|
||||||
|
rowPosition: Int,
|
||||||
|
childIndex: Int
|
||||||
|
) {
|
||||||
|
DropdownPopup(
|
||||||
|
context = context,
|
||||||
|
list = field.options.toMutableList(),
|
||||||
|
popWidth = anchorView.width,
|
||||||
|
popHeight = kotlin.math.min(field.options.size.coerceAtLeast(1), 4) * 50.dp
|
||||||
|
) { dictType ->
|
||||||
|
field.value = dictType.value ?: ""
|
||||||
|
field.valueId = dictType.id ?: ""
|
||||||
|
valueView.text = field.value
|
||||||
|
// 回调通知外部更新
|
||||||
|
onChildClick(rowPosition, childIndex, field)
|
||||||
|
}.also {
|
||||||
|
it.bgLayout = anchorView
|
||||||
|
it.showAsDropDown(anchorView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ class DropdownPopup(
|
|||||||
|
|
||||||
override fun showAsDropDown(anchor: View?) {
|
override fun showAsDropDown(anchor: View?) {
|
||||||
super.showAsDropDown(anchor)
|
super.showAsDropDown(anchor)
|
||||||
bgLayout?.setBackgroundResource(R.drawable.bg_white_stroke_blue)
|
bgLayout?.setBackgroundResource(R.drawable.shape_green_stroke)
|
||||||
arrowImage?.setImageResource(R.mipmap.ic_triangle_up)
|
arrowImage?.setImageResource(R.mipmap.ic_triangle_up)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ enum class FieldType {
|
|||||||
/** 日期选择弹窗 */
|
/** 日期选择弹窗 */
|
||||||
DATE_PICKER,
|
DATE_PICKER,
|
||||||
/** 固定值,不可编辑 */
|
/** 固定值,不可编辑 */
|
||||||
FIXED
|
FIXED,
|
||||||
|
/** 子列表容器,用于动态增减行(如"包含食材") */
|
||||||
|
CHILDREN
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,6 +32,9 @@ enum class FieldType {
|
|||||||
* @param value 当前显示值/输入值;DATE_PICKER 存 yyyy-MM-dd 显示格式
|
* @param value 当前显示值/输入值;DATE_PICKER 存 yyyy-MM-dd 显示格式
|
||||||
* @param valueId DATE_PICKER 存 ISO 格式 yyyy-MM-dd'T'HH:mm:ss
|
* @param valueId DATE_PICKER 存 ISO 格式 yyyy-MM-dd'T'HH:mm:ss
|
||||||
* @param extraValues 下拉选中后按 extraApiKeys 映射存储的提交值,key=接口参数名
|
* @param extraValues 下拉选中后按 extraApiKeys 映射存储的提交值,key=接口参数名
|
||||||
|
* @param children 子字段模板(CHILDREN 类型使用),每行按此模板创建一组字段
|
||||||
|
* @param childAddLabel 添加按钮文案(CHILDREN 类型使用),如 "+ 添加食材"
|
||||||
|
* @param childrenRows 子列表实际数据(CHILDREN 类型使用),每行是一个 List<FormField>
|
||||||
*/
|
*/
|
||||||
data class FormField(
|
data class FormField(
|
||||||
val label: String,
|
val label: String,
|
||||||
@@ -43,5 +48,8 @@ data class FormField(
|
|||||||
val submitValueId: Boolean = false,
|
val submitValueId: Boolean = false,
|
||||||
var value: String = "",
|
var value: String = "",
|
||||||
var valueId: String = "",
|
var valueId: String = "",
|
||||||
val extraValues: MutableMap<String, String> = mutableMapOf()
|
val extraValues: MutableMap<String, String> = mutableMapOf(),
|
||||||
|
val children: List<FormField>? = null,
|
||||||
|
val childAddLabel: String? = null,
|
||||||
|
val childrenRows: MutableList<MutableList<FormField>> = mutableListOf()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.shuwei.dish.match.R
|
|||||||
import com.shuwei.dish.match.adapter.FormFieldAdapter
|
import com.shuwei.dish.match.adapter.FormFieldAdapter
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
import com.shuwei.dish.match.base.BaseActivity
|
||||||
import com.shuwei.dish.match.databinding.ActivityCleanPackBinding
|
import com.shuwei.dish.match.databinding.ActivityCleanPackBinding
|
||||||
|
import com.shuwei.dish.match.model.DictType
|
||||||
import com.shuwei.dish.match.model.FieldType
|
import com.shuwei.dish.match.model.FieldType
|
||||||
import com.shuwei.dish.match.model.FormField
|
import com.shuwei.dish.match.model.FormField
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
@@ -170,12 +171,143 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化餐品净菜包装表单字段
|
* 初始化餐品净菜包装表单字段
|
||||||
* TODO: 由用户提供具体字段数据
|
* 对应接口 NutSupMealPackageDTO
|
||||||
*/
|
*/
|
||||||
private fun initMealPackFields() {
|
private fun initMealPackFields() {
|
||||||
fields.clear()
|
fields.clear()
|
||||||
// 待填充
|
|
||||||
formAdapter.submitList(fields)
|
// 包装餐品
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包装餐品", type = FieldType.DROPDOWN,
|
||||||
|
apiKey = "foodId", required = true,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "1", type = "宫保鸡丁"),
|
||||||
|
DictType(id = "2", value = "2", type = "鱼香肉丝"),
|
||||||
|
DictType(id = "3", value = "3", type = "红烧茄子"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 包装规格
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包装规格", type = FieldType.DROPDOWN,
|
||||||
|
apiKey = "spec", required = true,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "500g/盒", type = "500g/盒"),
|
||||||
|
DictType(id = "2", value = "1kg/盒", type = "1kg/盒"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 包装方式
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包装方式", type = FieldType.DROPDOWN,
|
||||||
|
apiKey = "packageMethod", required = true,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "真空包装", type = "真空包装"),
|
||||||
|
DictType(id = "2", value = "气调包装", type = "气调包装"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 包装份数
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包装份数", type = FieldType.INTEGER,
|
||||||
|
apiKey = "quantity", required = true, hint = "请输入份数"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 包含食材(子列表容器)
|
||||||
|
val ingredientTemplate = listOf(
|
||||||
|
FormField(label = "食材溯源码", type = FieldType.TEXT, required = true, hint = "溯源码"),
|
||||||
|
FormField(
|
||||||
|
label = "食材名称", type = FieldType.DROPDOWN, required = true,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "土豆", type = "土豆"),
|
||||||
|
DictType(id = "2", value = "鸡肉", type = "鸡肉"),
|
||||||
|
DictType(id = "3", value = "青椒", type = "青椒"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FormField(
|
||||||
|
label = "分类", type = FieldType.DROPDOWN, required = true,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "蔬菜类", type = "蔬菜类"),
|
||||||
|
DictType(id = "2", value = "肉禽类", type = "肉禽类"),
|
||||||
|
DictType(id = "3", value = "水产类", type = "水产类"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FormField(label = "每份用量", type = FieldType.TEXT, required = true, hint = "如 120g"),
|
||||||
|
)
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包含食材", type = FieldType.CHILDREN,
|
||||||
|
required = true,
|
||||||
|
children = ingredientTemplate,
|
||||||
|
childAddLabel = "+ 添加食材"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 包装日期
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包装日期", type = FieldType.DATE_PICKER,
|
||||||
|
apiKey = "packageDate", required = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 保质期至
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "保质期至", type = FieldType.DATE_PICKER,
|
||||||
|
apiKey = "expiryDate", required = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 存放温度
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "存放温度(℃)", type = FieldType.DECIMAL,
|
||||||
|
apiKey = "storageTemp", required = false, hint = "如 3.8"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 包装人
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "包装人", type = FieldType.TEXT,
|
||||||
|
apiKey = "operator", required = false, hint = "请输入包装人"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 终端设备
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "终端设备", type = FieldType.DROPDOWN,
|
||||||
|
apiKey = "deviceId", required = false,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "1", type = "包装机-01"),
|
||||||
|
DictType(id = "2", value = "2", type = "包装机-02"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 操作类型
|
||||||
|
fields.add(
|
||||||
|
FormField(
|
||||||
|
label = "操作类型", type = FieldType.DROPDOWN,
|
||||||
|
apiKey = "opType", required = true,
|
||||||
|
options = listOf(
|
||||||
|
DictType(id = "1", value = "1", type = "设备自动"),
|
||||||
|
DictType(id = "2", value = "2", type = "人工录入"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
formAdapter.submitList(fields.filter { !it.hidden })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<stroke android:color="#FFDCDCF0" android:width="1dp"/>
|
||||||
|
</shape>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<solid android:color="@color/white"/>
|
|
||||||
<stroke android:color="#FF0032C8" android:width="2dp"/>
|
|
||||||
<corners android:radius="10dp"/>
|
|
||||||
</shape>
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="#30000000">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#99FFFFFF"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/ripple_effect_light">
|
android:background="@drawable/ripple_effect_light3">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvDropdown"
|
android:id="@+id/tvDropdown"
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingVertical="10dp">
|
android:paddingVertical="10dp"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
<!-- 字段标签,必填项红色星号由 Adapter 通过 SpannableString 追加 -->
|
<!-- 字段标签,必填项红色星号由 Adapter 通过 SpannableString 追加 -->
|
||||||
<TextView
|
<TextView
|
||||||
@@ -18,11 +19,135 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="食材名称" />
|
tools:text="食材名称" />
|
||||||
|
|
||||||
|
<!-- CHILDREN 类型:子列表容器 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llChildren"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<!-- 子列表标题行 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@drawable/bg_rect_header">
|
||||||
|
|
||||||
|
<!-- 食材溯源码表头 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="食材溯源码"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#FFDCDCF0"/>
|
||||||
|
<!-- 食材名称表头 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginHorizontal="2dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="食材名称"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#FFDCDCF0"/>
|
||||||
|
|
||||||
|
<!-- 分类表头 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginHorizontal="2dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="分类"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#FFDCDCF0"/>
|
||||||
|
|
||||||
|
<!-- 每份用量表头 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="每份用量"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#FFDCDCF0"/>
|
||||||
|
|
||||||
|
<!-- 操作表头 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="操作"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 嵌套 RecyclerView -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvChildren"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
tools:listitem="@layout/list_item_ingredient_row"/>
|
||||||
|
|
||||||
|
<!-- 添加按钮 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAddChild"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:paddingVertical="5dp"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingEnd="10dp"
|
||||||
|
android:textColor="#FF0066CC"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="+ 添加食材"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- 输入区域容器,三种控件叠放,按 FieldType 切换 visibility -->
|
<!-- 输入区域容器,三种控件叠放,按 FieldType 切换 visibility -->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/flInputArea"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:layout_marginHorizontal="12dp">
|
android:layout_marginHorizontal="12dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<!-- TEXT / NUMBER / FIXED 类型:EditText -->
|
<!-- TEXT / NUMBER / FIXED 类型:EditText -->
|
||||||
<EditText
|
<EditText
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="4dp"
|
||||||
|
tools:ignore="HardcodedText">
|
||||||
|
|
||||||
|
<!-- 食材溯源码 -->
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etTraceCode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:background="@drawable/bg_white_radius10_stroke2"
|
||||||
|
android:hint="溯源码"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textColorHint="#FF96A0AA"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:ignore="Autofill,TextFields" />
|
||||||
|
|
||||||
|
<!-- 食材名称 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flMaterName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:background="@drawable/bg_white_radius10_stroke2">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvMaterName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textColorHint="#FF96A0AA"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|center_vertical"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:src="@mipmap/ic_triangle_down"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- 分类 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flClassify"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:background="@drawable/bg_white_radius10_stroke2">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvClassify"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textColorHint="#FF96A0AA"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|center_vertical"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:src="@mipmap/ic_triangle_down"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- 每份用量 -->
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etAmount"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_white_radius10_stroke2"
|
||||||
|
android:hint="如 120g"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:textColor="#FF141428"
|
||||||
|
android:textColorHint="#FF96A0AA"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:ignore="Autofill,TextFields" />
|
||||||
|
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRemove"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="×"
|
||||||
|
android:textColor="#FF96A0AA"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user