feat(activity): 为 MasterScaleActivity 各秤格子新增清零点击事件,修复高频数据更新导致点击失效的问题
This commit is contained in:
@@ -141,12 +141,13 @@ object ScaleServiceManager {
|
||||
* @param deviceId 目标设备 ID
|
||||
* @param address 目标秤地址
|
||||
*/
|
||||
fun sendTare(deviceId: String, address: Int) {
|
||||
if (deviceId == GlobalData.deviceId) {
|
||||
fun sendTare(deviceId: String, address: Int): Boolean {
|
||||
return if (deviceId == GlobalData.deviceId) {
|
||||
com.shuwei.dish.match.utils.WeightUtil.tareTwo(address)
|
||||
true
|
||||
} else {
|
||||
val cmd = ScaleCommand(ScaleCommand.CMD_TARE, deviceId, address)
|
||||
wsClient?.sendCommand(deviceId, cmd)
|
||||
wsClient?.sendCommand(deviceId, cmd) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.shuwei.dish.match.scale.ScaleServiceManager
|
||||
import com.shuwei.dish.match.utils.NetworkUtil
|
||||
import com.shuwei.dish.match.utils.WeightUtil
|
||||
import com.shuwei.dish.match.utils.ext.dp
|
||||
import com.shuwei.dish.match.utils.ext.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -99,17 +100,43 @@ class MasterScaleActivity : BaseActivity() {
|
||||
scales = scales.sortedBy { it.address }
|
||||
)
|
||||
}
|
||||
updateList(groups)
|
||||
|
||||
val structureChanged = groups.size != groupList.size ||
|
||||
groups.indices.any { groups[it].deviceId != groupList[it].deviceId }
|
||||
|
||||
if (structureChanged) {
|
||||
updateList(groups)
|
||||
} else {
|
||||
groups.forEach { group -> adapter.updateScalesForDevice(group.deviceId, group.scales) }
|
||||
}
|
||||
|
||||
updateStatusBar(scaleMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun updateList(groups: List<DeviceGroup>) {
|
||||
groupList.clear()
|
||||
groupList.addAll(groups)
|
||||
adapter.notifyDataSetChanged()
|
||||
if (groupList.isEmpty()) {
|
||||
groupList.addAll(groups)
|
||||
adapter.notifyDataSetChanged()
|
||||
return
|
||||
}
|
||||
// 精确 diff,只 notify 真正变化的 item,避免全量刷新打断触摸事件
|
||||
groups.forEachIndexed { i, group ->
|
||||
if (i < groupList.size) {
|
||||
if (groupList[i] != group) {
|
||||
groupList[i] = group
|
||||
adapter.notifyItemChanged(i)
|
||||
}
|
||||
} else {
|
||||
groupList.add(group)
|
||||
adapter.notifyItemInserted(i)
|
||||
}
|
||||
}
|
||||
while (groupList.size > groups.size) {
|
||||
groupList.removeAt(groupList.lastIndex)
|
||||
adapter.notifyItemRemoved(groupList.size)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -122,6 +149,18 @@ class MasterScaleActivity : BaseActivity() {
|
||||
private inner class DeviceGroupAdapter(data: MutableList<DeviceGroup>) :
|
||||
BaseMultiItemAdapter<DeviceGroup>(data) {
|
||||
|
||||
private val scale1VHMap = mutableMapOf<String, Scale1VH>()
|
||||
private val scale2VHMap = mutableMapOf<String, Scale2VH>()
|
||||
private val scale22VHMap = mutableMapOf<String, Scale22VH>()
|
||||
private val scale18VHMap = mutableMapOf<String, Scale18VH>()
|
||||
|
||||
fun updateScalesForDevice(deviceId: String, scales: List<ScaleData>) {
|
||||
scale1VHMap[deviceId]?.updateScales(scales)
|
||||
scale2VHMap[deviceId]?.updateScales(scales)
|
||||
scale22VHMap[deviceId]?.updateScales(scales)
|
||||
scale18VHMap[deviceId]?.updateScales(scales)
|
||||
}
|
||||
|
||||
init {
|
||||
addItemType(VIEW_TYPE_SCALE2, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
||||
@@ -129,7 +168,9 @@ class MasterScaleActivity : BaseActivity() {
|
||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
||||
item ?: return
|
||||
val b = ListTypeScale2Binding.bind(holder.itemView)
|
||||
Scale2VH(b).bind(item)
|
||||
val vh = Scale2VH(b)
|
||||
scale2VHMap[item.deviceId] = vh
|
||||
vh.bind(item)
|
||||
}
|
||||
})
|
||||
addItemType(VIEW_TYPE_SCALE22, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||
@@ -138,7 +179,10 @@ class MasterScaleActivity : BaseActivity() {
|
||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
||||
item ?: return
|
||||
val b = ListTypeScale22Binding.bind(holder.itemView)
|
||||
Scale22VH(b).bind(item)
|
||||
// 每次 onBind 时注册/更新 VH 缓存,后续数据更新直接走 VH,不再触发 notify
|
||||
val vh = Scale22VH(b)
|
||||
scale22VHMap[item.deviceId] = vh
|
||||
vh.bind(item)
|
||||
}
|
||||
})
|
||||
addItemType(VIEW_TYPE_SCALE18, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||
@@ -147,7 +191,9 @@ class MasterScaleActivity : BaseActivity() {
|
||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
||||
item ?: return
|
||||
val b = ListTypeScale18Binding.bind(holder.itemView)
|
||||
Scale18VH(b).bind(item)
|
||||
val vh = Scale18VH(b)
|
||||
scale18VHMap[item.deviceId] = vh
|
||||
vh.bind(item)
|
||||
}
|
||||
})
|
||||
addItemType(VIEW_TYPE_SCALE1, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||
@@ -156,7 +202,9 @@ class MasterScaleActivity : BaseActivity() {
|
||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
||||
item ?: return
|
||||
val b = ListTypeScale1Binding.bind(holder.itemView)
|
||||
Scale1VH(b).bind(item)
|
||||
val vh = Scale1VH(b)
|
||||
scale1VHMap[item.deviceId] = vh
|
||||
vh.bind(item)
|
||||
}
|
||||
})
|
||||
onItemViewType { position, list -> when (list.getOrNull(position)?.deviceId) {
|
||||
@@ -171,14 +219,36 @@ class MasterScaleActivity : BaseActivity() {
|
||||
// ── ViewHolder: 2个秤 ──────────────────────────────────────────────────
|
||||
|
||||
private inner class Scale2VH(val b: ListTypeScale2Binding) : RecyclerView.ViewHolder(b.root) {
|
||||
private lateinit var cells: List<LinearLayout>
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(group: DeviceGroup) {
|
||||
b.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
||||
b.tvDeviceLabel.text = "设备:${group.deviceId}"
|
||||
b.llScaleContainer.removeAllViews()
|
||||
val cellSize = (resources.displayMetrics.widthPixels - 4 * 4.dp - 10.dp) / 2
|
||||
group.scales.take(2).forEach { scale ->
|
||||
b.llScaleContainer.addView(makeScaleCell(scale, cellSize))
|
||||
|
||||
if (!::cells.isInitialized) {
|
||||
val cellSize = (resources.displayMetrics.widthPixels - 4 * 4.dp - 10.dp) / 2
|
||||
cells = group.scales.take(2).map { scale ->
|
||||
makeScaleCell(scale, cellSize).also { cell ->
|
||||
cell.setOnClickListener {
|
||||
val s = cell.tag as? ScaleData ?: return@setOnClickListener
|
||||
showTareDialog(deviceId = s.deviceId, ip = s.ip, address = s.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
b.llScaleContainer.removeAllViews()
|
||||
cells.forEach { b.llScaleContainer.addView(it) }
|
||||
}
|
||||
updateScales(group.scales)
|
||||
}
|
||||
|
||||
fun updateScales(scales: List<ScaleData>) {
|
||||
if (!::cells.isInitialized) return
|
||||
scales.take(2).forEachIndexed { i, scale ->
|
||||
cells.getOrNull(i)?.let { cell ->
|
||||
cell.tag = scale
|
||||
updateScaleCell(cell, scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,17 +256,16 @@ class MasterScaleActivity : BaseActivity() {
|
||||
// ── ViewHolder: 22个秤 ─────────────────────────────────────────────────
|
||||
|
||||
private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) {
|
||||
private var bound = false
|
||||
private var innerAdapter: Scale22InnerAdapter? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(group: DeviceGroup) {
|
||||
b.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
||||
b.tvDeviceLabel.text = "设备:${group.deviceId}"
|
||||
|
||||
if (!bound) {
|
||||
// 计算格子尺寸(与 Scale22GridActivity 一致)
|
||||
if (innerAdapter == null) {
|
||||
val screenWidth = resources.displayMetrics.widthPixels
|
||||
val usable = screenWidth - 8 * 4.dp - 10.dp // 减去8列格子margin + 外层RV左右margin各5dp
|
||||
val usable = screenWidth - 8 * 4.dp - 10.dp
|
||||
val smallSize = usable / 9
|
||||
val largeSize = (usable - smallSize * 6) / 2
|
||||
val rvHeight = smallSize * 3 + 3 * 4.dp
|
||||
@@ -207,26 +276,35 @@ class MasterScaleActivity : BaseActivity() {
|
||||
alignItems = AlignItems.FLEX_START
|
||||
}
|
||||
b.rvScales.itemAnimator = null
|
||||
b.rvScales.adapter = Scale22InnerAdapter(largeSize, smallSize)
|
||||
bound = true
|
||||
innerAdapter = Scale22InnerAdapter(largeSize, smallSize).apply {
|
||||
onItemClick = { scale ->
|
||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
||||
}
|
||||
}
|
||||
b.rvScales.adapter = innerAdapter
|
||||
}
|
||||
val scaleByAddress = group.scales.associateBy { it.address }
|
||||
updateScales(group.scales)
|
||||
}
|
||||
|
||||
/** 仅更新秤数据,不触发外层任何 notify */
|
||||
fun updateScales(scales: List<ScaleData>) {
|
||||
val scaleByAddress = scales.associateBy { it.address }
|
||||
val ordered = SCALE_ORDER_22.map { scaleByAddress[it] }
|
||||
(b.rvScales.adapter as Scale22InnerAdapter).update(ordered)
|
||||
innerAdapter?.update(ordered)
|
||||
}
|
||||
}
|
||||
|
||||
// ── ViewHolder: 18个秤 ─────────────────────────────────────────────────
|
||||
|
||||
private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) {
|
||||
private var bound = false
|
||||
private var innerAdapter: Scale18InnerAdapter? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(group: DeviceGroup) {
|
||||
b.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
||||
b.tvDeviceLabel.text = "设备:${group.deviceId}"
|
||||
|
||||
if (!bound) {
|
||||
if (innerAdapter == null) {
|
||||
val gridLayoutManager = GridLayoutManager(b.root.context, 6).apply {
|
||||
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||
// 每个格子占1 span,共6列
|
||||
@@ -235,23 +313,45 @@ class MasterScaleActivity : BaseActivity() {
|
||||
}
|
||||
b.rvScales.layoutManager = gridLayoutManager
|
||||
b.rvScales.itemAnimator = null
|
||||
b.rvScales.adapter = Scale18InnerAdapter()
|
||||
bound = true
|
||||
innerAdapter = Scale18InnerAdapter().apply {
|
||||
onItemClick = { scale ->
|
||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
||||
}
|
||||
}
|
||||
b.rvScales.adapter = innerAdapter
|
||||
}
|
||||
val scaleByAddress = group.scales.associateBy { it.address }
|
||||
updateScales(group.scales)
|
||||
}
|
||||
|
||||
/** 仅更新秤数据,不触发外层任何 notify */
|
||||
fun updateScales(scales: List<ScaleData>) {
|
||||
val scaleByAddress = scales.associateBy { it.address }
|
||||
val ordered = SCALE_ORDER_18.map { scaleByAddress[it] }
|
||||
(b.rvScales.adapter as Scale18InnerAdapter).update(ordered)
|
||||
innerAdapter?.update(ordered)
|
||||
}
|
||||
}
|
||||
|
||||
// ── ViewHolder: 1个秤 ──────────────────────────────────────────────────
|
||||
|
||||
private inner class Scale1VH(val b: ListTypeScale1Binding) : RecyclerView.ViewHolder(b.root) {
|
||||
|
||||
init {
|
||||
b.llScaleContainer.setOnClickListener {
|
||||
val scale = b.llScaleContainer.tag as? ScaleData ?: return@setOnClickListener
|
||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(group: DeviceGroup) {
|
||||
b.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
||||
b.tvDeviceLabel.text = "设备:${group.deviceId}"
|
||||
val scale = group.scales.firstOrNull()
|
||||
updateScales(group.scales)
|
||||
}
|
||||
|
||||
fun updateScales(scales: List<ScaleData>) {
|
||||
val scale = scales.firstOrNull()
|
||||
b.llScaleContainer.tag = scale
|
||||
b.tvAddr.text = scale?.let { "秤 ${it.address}" } ?: ""
|
||||
b.tvWeight.text = scale?.let { "${it.weight} g" } ?: ""
|
||||
b.tvState.text = stateStr(scale?.state)
|
||||
@@ -270,13 +370,20 @@ class MasterScaleActivity : BaseActivity() {
|
||||
// 用 address=0 的哨兵表示空位
|
||||
private val empty = ScaleData("", 0, 0.0, -1, 0)
|
||||
|
||||
/** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */
|
||||
var onItemClick: ((ScaleData) -> Unit)? = null
|
||||
|
||||
fun update(newList: List<ScaleData?>) {
|
||||
val filled = newList.map { it ?: empty }
|
||||
if (items.isEmpty()) {
|
||||
submitList(filled)
|
||||
} else {
|
||||
filled.forEachIndexed { i, scale ->
|
||||
if (items.getOrNull(i) != scale) set(i, scale)
|
||||
val cur = items.getOrNull(i)
|
||||
// 排除 ts 时间戳比较,只有显示字段变化才触发更新
|
||||
if (cur == null || cur.address != scale.address || cur.weight != scale.weight || cur.state != scale.state) {
|
||||
set(i, scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +402,15 @@ class MasterScaleActivity : BaseActivity() {
|
||||
b.tvAddr.textSize = (baseSp * 0.16f).coerceIn(9f, 14f)
|
||||
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
||||
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
||||
return VH(b)
|
||||
val vh = VH(b)
|
||||
// 在 ViewHolder 创建时绑定点击,只执行一次,不受 notifyItemChanged 重绑定影响
|
||||
b.root.setOnClickListener {
|
||||
val pos = vh.bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
items.getOrNull(pos)?.takeIf { it.address != 0 }?.let { onItemClick?.invoke(it) }
|
||||
}
|
||||
}
|
||||
return vh
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -315,13 +430,19 @@ class MasterScaleActivity : BaseActivity() {
|
||||
|
||||
private val empty = ScaleData("", 0, 0.0, -1, 0)
|
||||
|
||||
/** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */
|
||||
var onItemClick: ((ScaleData) -> Unit)? = null
|
||||
|
||||
fun update(newList: List<ScaleData?>) {
|
||||
val filled = newList.map { it ?: empty }
|
||||
if (items.isEmpty()) {
|
||||
submitList(filled)
|
||||
} else {
|
||||
filled.forEachIndexed { i, scale ->
|
||||
if (items.getOrNull(i) != scale) set(i, scale)
|
||||
val cur = items.getOrNull(i)
|
||||
if (cur == null || cur.address != scale.address || cur.weight != scale.weight || cur.state != scale.state) {
|
||||
set(i, scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +464,15 @@ class MasterScaleActivity : BaseActivity() {
|
||||
b.tvAddr.textSize = (baseSp * 0.16f).coerceIn(9f, 14f)
|
||||
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
||||
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
||||
return VH(b)
|
||||
val vh = VH(b)
|
||||
// 在 ViewHolder 创建时绑定点击,只执行一次,不受 notifyItemChanged 重绑定影响
|
||||
b.root.setOnClickListener {
|
||||
val pos = vh.bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
items.getOrNull(pos)?.takeIf { it.address != 0 }?.let { onItemClick?.invoke(it) }
|
||||
}
|
||||
}
|
||||
return vh
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -365,7 +494,7 @@ class MasterScaleActivity : BaseActivity() {
|
||||
else -> "$state"
|
||||
}
|
||||
|
||||
/** 创建正方形秤格子(用于 Scale2VH) */
|
||||
/** 创建正方形秤格子(用于 Scale2VH 初始化) */
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun makeScaleCell(scale: ScaleData, cellSize: Int): LinearLayout {
|
||||
val margin = 2.dp
|
||||
@@ -380,9 +509,18 @@ class MasterScaleActivity : BaseActivity() {
|
||||
addView(makeCellText("秤${scale.address}", (baseSize * 0.16f).coerceIn(9f, 14f).toInt()))
|
||||
addView(makeCellText("${scale.weight}g", (baseSize * 0.22f).coerceIn(12f, 20f).toInt(), bold = true))
|
||||
addView(makeCellText(stateStr(scale.state), (baseSize * 0.14f).coerceIn(8f, 12f).toInt()))
|
||||
tag = scale
|
||||
}
|
||||
}
|
||||
|
||||
/** 仅更新格子内文字,不重建 view(用于 Scale2VH 数据刷新) */
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun updateScaleCell(cell: LinearLayout, scale: ScaleData) {
|
||||
(cell.getChildAt(0) as? TextView)?.text = "秤${scale.address}"
|
||||
(cell.getChildAt(1) as? TextView)?.text = "${scale.weight}g"
|
||||
(cell.getChildAt(2) as? TextView)?.text = stateStr(scale.state)
|
||||
}
|
||||
|
||||
private fun makeCellText(text: String, spSize: Int, bold: Boolean = false): TextView =
|
||||
TextView(this).apply {
|
||||
this.text = text
|
||||
@@ -395,4 +533,23 @@ class MasterScaleActivity : BaseActivity() {
|
||||
gravity = Gravity.CENTER
|
||||
includeFontPadding = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出清零确认对话框
|
||||
* @param deviceId 目标设备 ID
|
||||
* @param ip 设备 IP
|
||||
* @param address 目标秤地址
|
||||
*/
|
||||
private fun showTareDialog(deviceId: String, ip: String, address: Int) {
|
||||
android.app.AlertDialog.Builder(this)
|
||||
.setTitle("清零确认")
|
||||
.setMessage("设备 IP:${ip.ifEmpty { "未知" }}\n确认对秤 $address 执行清零操作?")
|
||||
.setPositiveButton("确认") { _, _ ->
|
||||
val sent = ScaleServiceManager.sendTare(deviceId, address)
|
||||
if (!sent) toast("设备未连接,清零失败")
|
||||
}
|
||||
.setNegativeButton("取消", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user