diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/Scale18GridAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/Scale18GridAdapter.kt new file mode 100644 index 0000000..d8b97cc --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/adapter/Scale18GridAdapter.kt @@ -0,0 +1,100 @@ +package com.shuwei.dish.match.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.chad.library.adapter4.BaseQuickAdapter +import com.chad.library.adapter4.viewholder.QuickViewHolder +import com.shuwei.dish.match.databinding.ListItemScale22RowBinding +import com.shuwei.dish.match.scale.ScaleData +import com.shuwei.dish.match.scale.ScaleDeviceConfig +import com.shuwei.dish.match.utils.WeightUtil +import com.shuwei.dish.match.utils.ext.dp + +/** + * 18个秤的网格 Adapter + * 使用 GridLayoutManager(6列)排列,第1行格子 margin 较大(视觉更小),第2-3行 margin 较小 + * 点击格子触发清零回调 + */ +class Scale18GridAdapter : BaseQuickAdapter(mutableListOf()) { + + inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root) + + /** 用 address=0 的哨兵表示空位 */ + private val empty = ScaleData("", 0, 0.0, -1, 0) + + /** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */ + var onItemClick: ((ScaleData) -> Unit)? = null + + /** + * 按 SCALE_ORDER_18 顺序更新数据,空位用哨兵填充 + * 只更新显示字段有变化的格子,避免全量刷新 + */ + fun update(newList: List) { + val filled = newList.map { it ?: empty } + if (items.isEmpty()) { + submitList(filled) + } else { + filled.forEachIndexed { 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) + } + } + } + } + + /** + * 将 ScaleData 列表按 SCALE_ORDER_18 重排后调用 update + */ + fun updateByAddress(scales: List) { + val byAddress = scales.associateBy { it.address } + update(ScaleDeviceConfig.SCALE_ORDER_18.map { byAddress[it] }) + } + + /** 前6个位置为第1行(viewType=0,margin大),其余为第2-3行(viewType=1,margin小) */ + override fun getItemViewType(position: Int, list: List) = if (position < 6) 0 else 1 + + override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { + val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false) + // 第1行 margin=12dp(格子视觉更小),第2-3行 margin=2dp + val margin = if (viewType == 0) 12.dp else 2.dp + val screenWidth = parent.resources.displayMetrics.widthPixels + val cellSize = (screenWidth - 6 * margin * 2 - 10.dp) / 6 + b.root.layoutParams = (b.root.layoutParams as ViewGroup.MarginLayoutParams).also { + it.width = cellSize + it.height = cellSize + it.setMargins(margin, margin, margin, margin) + } + val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt() + 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) + 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") + override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) { + val valid = (item?.address ?: 0) != 0 + holder.b.tvAddr.text = if (valid) "秤${item!!.address}" else "" + holder.b.tvWeight.text = if (valid) "${item!!.weight}g" else "" + holder.b.tvState.text = if (valid) stateStr(item!!.state) else "" + } + + private fun stateStr(state: Int) = when (state) { + WeightUtil.STATE_STABLE -> "稳定" + WeightUtil.STATE_UNSTABLE -> "不稳定" + WeightUtil.STATE_OVER_WEIGHT -> "超量" + else -> "$state" + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/Scale22GridAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/Scale22GridAdapter.kt new file mode 100644 index 0000000..09775fd --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/adapter/Scale22GridAdapter.kt @@ -0,0 +1,104 @@ +package com.shuwei.dish.match.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.chad.library.adapter4.BaseQuickAdapter +import com.chad.library.adapter4.viewholder.QuickViewHolder +import com.google.android.flexbox.FlexboxLayoutManager +import com.shuwei.dish.match.databinding.ListItemScale22RowBinding +import com.shuwei.dish.match.scale.ScaleData +import com.shuwei.dish.match.scale.ScaleDeviceConfig +import com.shuwei.dish.match.utils.WeightUtil +import com.shuwei.dish.match.utils.ext.dp + +/** + * 22个秤的网格 Adapter + * 使用 FlexboxLayoutManager 按列排列,前4个格子较大(对应物理上的大秤),其余较小 + * 点击格子触发清零回调 + * + * @param largeSize 前4个大格子的尺寸(px) + * @param smallSize 其余小格子的尺寸(px) + */ +class Scale22GridAdapter( + private val largeSize: Int, + private val smallSize: Int +) : BaseQuickAdapter(mutableListOf()) { + + inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root) + + /** 用 address=0 的哨兵表示空位 */ + private val empty = ScaleData("", 0, 0.0, -1, 0) + + /** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */ + var onItemClick: ((ScaleData) -> Unit)? = null + + /** + * 按 SCALE_ORDER_22 顺序更新数据,空位用哨兵填充 + * 只更新显示字段有变化的格子,避免全量刷新 + */ + fun update(newList: List) { + val filled = newList.map { it ?: empty } + if (items.isEmpty()) { + submitList(filled) + } else { + filled.forEachIndexed { 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) + } + } + } + } + + /** + * 将 ScaleData 列表按 SCALE_ORDER_22 重排后调用 update + */ + fun updateByAddress(scales: List) { + val byAddress = scales.associateBy { it.address } + update(ScaleDeviceConfig.SCALE_ORDER_22.map { byAddress[it] }) + } + + /** 前4个位置为大格子(viewType=1),其余为小格子(viewType=0) */ + override fun getItemViewType(position: Int, list: List) = if (position < 4) 1 else 0 + + override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { + val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false) + val cellSize = if (viewType == 1) largeSize else smallSize + (b.root.layoutParams as? FlexboxLayoutManager.LayoutParams)?.apply { + width = cellSize + height = cellSize + setMargins(2.dp, 2.dp, 2.dp, 2.dp) + } + val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt() + 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) + 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") + override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) { + val valid = (item?.address ?: 0) != 0 + holder.b.tvAddr.text = if (valid) "秤${item!!.address}" else "" + holder.b.tvWeight.text = if (valid) "${item!!.weight}g" else "" + holder.b.tvState.text = if (valid) stateStr(item!!.state) else "" + } + + private fun stateStr(state: Int) = when (state) { + WeightUtil.STATE_STABLE -> "稳定" + WeightUtil.STATE_UNSTABLE -> "不稳定" + WeightUtil.STATE_OVER_WEIGHT -> "超量" + else -> "$state" + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/scale/ScaleDeviceConfig.kt b/app/src/main/java/com/shuwei/dish/match/scale/ScaleDeviceConfig.kt new file mode 100644 index 0000000..e47ca49 --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/scale/ScaleDeviceConfig.kt @@ -0,0 +1,22 @@ +package com.shuwei.dish.match.scale + +/** + * 各子设备的固定 ID 与秤地址排列顺序配置 + * MasterScaleActivity 和 SlaveActivity 共用此配置,避免重复定义 + */ +object ScaleDeviceConfig { + + const val DEVICE_ID_2 = "8fc2ab34-2137-3112-acca-f884ea8736d4" + const val DEVICE_ID_22 = "a46fa55c-113c-3511-bb1f-41e5eff77c4b" + const val DEVICE_ID_18 = "1038da9f-c6eb-326e-a1d9-d6d3af978b22" + const val DEVICE_ID_1 = "7cc0f6ea-f13d-3013-a867-fc998eb554ac" + + /** 22个秤的显示顺序(按物理位置排列) */ + val SCALE_ORDER_22 = listOf(20, 22, 19, 21, 6, 12, 18, 5, 11, 17, 4, 10, 16, 3, 9, 15, 2, 8, 14, 1, 7, 13) + + /** 18个秤的显示顺序(按物理位置排列) */ + val SCALE_ORDER_18 = listOf(6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 18, 17, 16, 15, 14, 13) + + /** 设备在列表中的显示顺序 */ + val DEVICE_ORDER = listOf(DEVICE_ID_2, DEVICE_ID_22, DEVICE_ID_18, DEVICE_ID_1) +} diff --git a/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt index acf8f17..8368753 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt @@ -12,21 +12,22 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.chad.library.adapter4.BaseMultiItemAdapter -import com.chad.library.adapter4.BaseQuickAdapter import com.chad.library.adapter4.viewholder.QuickViewHolder import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayoutManager import com.shuwei.dish.match.R +import com.shuwei.dish.match.adapter.Scale18GridAdapter +import com.shuwei.dish.match.adapter.Scale22GridAdapter import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.databinding.ActivityMasterScaleBinding -import com.shuwei.dish.match.databinding.ListItemScale22RowBinding import com.shuwei.dish.match.databinding.ListTypeScale1Binding import com.shuwei.dish.match.databinding.ListTypeScale18Binding import com.shuwei.dish.match.databinding.ListTypeScale2Binding import com.shuwei.dish.match.databinding.ListTypeScale22Binding import com.shuwei.dish.match.scale.ScaleData +import com.shuwei.dish.match.scale.ScaleDeviceConfig import com.shuwei.dish.match.scale.ScaleServiceManager import com.shuwei.dish.match.utils.NetworkUtil import com.shuwei.dish.match.utils.WeightUtil @@ -35,15 +36,6 @@ import com.shuwei.dish.match.utils.ext.toast import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -private const val DEVICE_ID_2 = "8fc2ab34-2137-3112-acca-f884ea8736d4" -private const val DEVICE_ID_22 = "a46fa55c-113c-3511-bb1f-41e5eff77c4b" -private const val DEVICE_ID_18 = "1038da9f-c6eb-326e-a1d9-d6d3af978b22" -private const val DEVICE_ID_1 = "7cc0f6ea-f13d-3013-a867-fc998eb554ac" - -private val SCALE_ORDER_22 = listOf(20, 22, 19, 21, 6, 12, 18, 5, 11, 17, 4, 10, 16, 3, 9, 15, 2, 8, 14, 1, 7, 13) -private val SCALE_ORDER_18 = listOf(6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 18, 17, 16, 15, 14, 13) -private val deviceOrder = listOf(DEVICE_ID_2, DEVICE_ID_22, DEVICE_ID_18, DEVICE_ID_1) - private const val VIEW_TYPE_SCALE2 = 0 private const val VIEW_TYPE_SCALE22 = 1 private const val VIEW_TYPE_SCALE18 = 2 @@ -90,7 +82,7 @@ class MasterScaleActivity : BaseActivity() { .groupBy { it.deviceId } .entries .sortedWith(compareBy { entry -> - deviceOrder.indexOf(entry.key).let { if (it == -1) Int.MAX_VALUE else it } + ScaleDeviceConfig.DEVICE_ORDER.indexOf(entry.key).let { if (it == -1) Int.MAX_VALUE else it } }) .map { (deviceId, scales) -> DeviceGroup( @@ -208,9 +200,9 @@ class MasterScaleActivity : BaseActivity() { } }) onItemViewType { position, list -> when (list.getOrNull(position)?.deviceId) { - DEVICE_ID_2 -> VIEW_TYPE_SCALE2 - DEVICE_ID_22 -> VIEW_TYPE_SCALE22 - DEVICE_ID_18 -> VIEW_TYPE_SCALE18 + ScaleDeviceConfig.DEVICE_ID_2 -> VIEW_TYPE_SCALE2 + ScaleDeviceConfig.DEVICE_ID_22 -> VIEW_TYPE_SCALE22 + ScaleDeviceConfig.DEVICE_ID_18 -> VIEW_TYPE_SCALE18 else -> VIEW_TYPE_SCALE1 }} } @@ -256,7 +248,7 @@ class MasterScaleActivity : BaseActivity() { // ── ViewHolder: 22个秤 ───────────────────────────────────────────────── private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) { - private var innerAdapter: Scale22InnerAdapter? = null + private var innerAdapter: Scale22GridAdapter? = null @SuppressLint("SetTextI18n") fun bind(group: DeviceGroup) { @@ -276,7 +268,7 @@ class MasterScaleActivity : BaseActivity() { alignItems = AlignItems.FLEX_START } b.rvScales.itemAnimator = null - innerAdapter = Scale22InnerAdapter(largeSize, smallSize).apply { + innerAdapter = Scale22GridAdapter(largeSize, smallSize).apply { onItemClick = { scale -> showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address) } @@ -288,16 +280,14 @@ class MasterScaleActivity : BaseActivity() { /** 仅更新秤数据,不触发外层任何 notify */ fun updateScales(scales: List) { - val scaleByAddress = scales.associateBy { it.address } - val ordered = SCALE_ORDER_22.map { scaleByAddress[it] } - innerAdapter?.update(ordered) + innerAdapter?.updateByAddress(scales) } } // ── ViewHolder: 18个秤 ───────────────────────────────────────────────── private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) { - private var innerAdapter: Scale18InnerAdapter? = null + private var innerAdapter: Scale18GridAdapter? = null @SuppressLint("SetTextI18n") fun bind(group: DeviceGroup) { @@ -307,13 +297,12 @@ class MasterScaleActivity : BaseActivity() { if (innerAdapter == null) { val gridLayoutManager = GridLayoutManager(b.root.context, 6).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - // 每个格子占1 span,共6列 override fun getSpanSize(position: Int) = 1 } } b.rvScales.layoutManager = gridLayoutManager b.rvScales.itemAnimator = null - innerAdapter = Scale18InnerAdapter().apply { + innerAdapter = Scale18GridAdapter().apply { onItemClick = { scale -> showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address) } @@ -325,9 +314,7 @@ class MasterScaleActivity : BaseActivity() { /** 仅更新秤数据,不触发外层任何 notify */ fun updateScales(scales: List) { - val scaleByAddress = scales.associateBy { it.address } - val ordered = SCALE_ORDER_18.map { scaleByAddress[it] } - innerAdapter?.update(ordered) + innerAdapter?.updateByAddress(scales) } } @@ -358,132 +345,6 @@ class MasterScaleActivity : BaseActivity() { } } - // ── 22格内嵌 Adapter ─────────────────────────────────────────────────── - - private inner class Scale22InnerAdapter( - private val largeSize: Int, - private val smallSize: Int - ) : BaseQuickAdapter(mutableListOf()) { - - inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root) - - // 用 address=0 的哨兵表示空位 - private val empty = ScaleData("", 0, 0.0, -1, 0) - - /** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */ - var onItemClick: ((ScaleData) -> Unit)? = null - - fun update(newList: List) { - val filled = newList.map { it ?: empty } - if (items.isEmpty()) { - submitList(filled) - } else { - filled.forEachIndexed { 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) - } - } - } - } - - override fun getItemViewType(position: Int, list: List) = if (position < 4) 1 else 0 - - override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { - val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false) - val cellSize = if (viewType == 1) largeSize else smallSize - (b.root.layoutParams as? FlexboxLayoutManager.LayoutParams)?.apply { - width = cellSize - height = cellSize - setMargins(2.dp, 2.dp, 2.dp, 2.dp) - } - val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt() - 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) - 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") - override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) { - val valid = (item?.address ?: 0) != 0 - holder.b.tvAddr.text = if (valid) "秤${item!!.address}" else "" - holder.b.tvWeight.text = if (valid) "${item!!.weight}g" else "" - holder.b.tvState.text = if (valid) stateStr(item!!.state) else "" - } - } - - // ── 18格内嵌 Adapter ─────────────────────────────────────────────────── - - private inner class Scale18InnerAdapter : BaseQuickAdapter(mutableListOf()) { - - inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root) - - private val empty = ScaleData("", 0, 0.0, -1, 0) - - /** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */ - var onItemClick: ((ScaleData) -> Unit)? = null - - fun update(newList: List) { - val filled = newList.map { it ?: empty } - if (items.isEmpty()) { - submitList(filled) - } else { - filled.forEachIndexed { 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) - } - } - } - } - - override fun getItemViewType(position: Int, list: List) = if (position < 6) 0 else 1 - - override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { - val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false) - // 第1行 margin=12dp(格子视觉更小),第2-3行 margin=2dp - val margin = if (viewType == 0) 12.dp else 2.dp - val screenWidth = parent.resources.displayMetrics.widthPixels - val cellSize = (screenWidth - 6 * margin * 2 - 10.dp) / 6 - b.root.layoutParams = (b.root.layoutParams as ViewGroup.MarginLayoutParams).also { - it.width = cellSize - it.height = cellSize - it.setMargins(margin, margin, margin, margin) - } - val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt() - 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) - 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") - override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) { - val valid = (item?.address ?: 0) != 0 - holder.b.tvAddr.text = if (valid) "秤${item!!.address}" else "" - holder.b.tvWeight.text = if (valid) "${item!!.weight}g" else "" - holder.b.tvState.text = if (valid) stateStr(item!!.state) else "" - } - } - // ── 工具方法 ─────────────────────────────────────────────────────────── private fun stateStr(state: Int?) = when (state) { diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt index 0aee338..2145b06 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt @@ -4,31 +4,46 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.activity.addCallback +import com.google.android.flexbox.AlignItems +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.FlexboxLayoutManager +import com.shuwei.dish.match.adapter.Scale18GridAdapter +import com.shuwei.dish.match.adapter.Scale22GridAdapter import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.GlobalData import com.shuwei.dish.match.databinding.ActivitySlaveBinding import com.shuwei.dish.match.databinding.ListItemScaleRowBinding +import com.shuwei.dish.match.scale.ScaleData +import com.shuwei.dish.match.scale.ScaleDeviceConfig 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 /** * 子设备专属页面(小屏幕设备使用) * 仅展示本机秤的实时数据和主设备连接状态 - * 布局简洁,适配小屏幕 + * 22个秤和18个秤使用与 MasterScaleActivity 相同的网格布局,其余使用线性列表 */ class SlaveActivity : BaseActivity() { private lateinit var binding: ActivitySlaveBinding - /** 本机秤数据列表:address → (weight, state) */ + /** 本机秤数据列表:address → (weight, state),线性列表模式使用 */ private data class ScaleItem(val address: Int, val weight: Double, val state: Int) private val scaleList = mutableListOf() - private val adapter = ScaleAdapter(scaleList) + private val linearAdapter = ScaleAdapter(scaleList) + + /** 22个秤网格 adapter */ + private var scale22Adapter: Scale22GridAdapter? = null + /** 18个秤网格 adapter */ + private var scale18Adapter: Scale18GridAdapter? = null /** 已连接的主设备数量(有连接即代表主设备在线) */ private var masterConnected = false @@ -41,23 +56,59 @@ class SlaveActivity : BaseActivity() { binding.tvDeviceId.text = "设备:${GlobalData.deviceId}" binding.tvDeviceIp.text = "IP:${getLocalIpAddress()}" - binding.rvScaleList.layoutManager = LinearLayoutManager(this) - binding.rvScaleList.itemAnimator = null // 关闭默认动画,避免数据频繁更新时出现闪烁错乱 - binding.rvScaleList.adapter = adapter + setupScaleList() updateMasterStatus(ScaleServiceManager.isMasterConnected) listenLocalScales() listenMasterConnection() addBackKeyListener() } + /** + * 根据 deviceId 选择对应的布局方式: + * - 22个秤:FlexboxLayoutManager + Scale22GridAdapter + * - 18个秤:GridLayoutManager(6列) + Scale18GridAdapter + * - 其他:LinearLayoutManager + 线性列表 + */ + private fun setupScaleList() { + binding.rvScaleList.itemAnimator = null + when (GlobalData.deviceId) { + ScaleDeviceConfig.DEVICE_ID_22 -> { + val screenWidth = resources.displayMetrics.widthPixels + 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 + binding.rvScaleList.layoutParams = binding.rvScaleList.layoutParams.also { it.height = rvHeight } + binding.rvScaleList.layoutManager = FlexboxLayoutManager(this).apply { + flexDirection = FlexDirection.COLUMN + flexWrap = FlexWrap.WRAP + alignItems = AlignItems.FLEX_START + } + scale22Adapter = Scale22GridAdapter(largeSize, smallSize) + binding.rvScaleList.adapter = scale22Adapter + } + ScaleDeviceConfig.DEVICE_ID_18 -> { + binding.rvScaleList.layoutManager = GridLayoutManager(this, 6).apply { + spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int) = 1 + } + } + scale18Adapter = Scale18GridAdapter() + binding.rvScaleList.adapter = scale18Adapter + } + else -> { + binding.rvScaleList.layoutManager = LinearLayoutManager(this) + binding.rvScaleList.adapter = linearAdapter + } + } + } + /** * 禁用返回键,防止退回 InitActivity 后重复打开本页 */ private fun addBackKeyListener() { - onBackPressedDispatcher.addCallback(this) { - // 禁用返回键,不执行任何操作 - } + onBackPressedDispatcher.addCallback(this) { } } /** @@ -71,19 +122,39 @@ class SlaveActivity : BaseActivity() { /** * 监听本机 WeightUtil 回调,实时刷新秤列表 + * 22/18个秤走网格 adapter,其余走线性列表 */ private fun listenLocalScales() { WeightUtil.addWeightListener(TAG) { address, state, weight -> runOnUiThread { - val idx = scaleList.indexOfFirst { it.address == address } - val item = ScaleItem(address, weight, state) - if (idx >= 0) { - scaleList[idx] = item - adapter.notifyItemChanged(idx) - } else { - scaleList.add(item) - scaleList.sortBy { it.address } - adapter.notifyDataSetChanged() + when { + scale22Adapter != null -> { + // 用最新数据更新缓存后整体刷新网格 + val existing = scale22Adapter!!.items.toMutableList() + val idx = existing.indexOfFirst { it.address == address } + val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis()) + if (idx >= 0) existing[idx] = newData else existing.add(newData) + scale22Adapter!!.updateByAddress(existing) + } + scale18Adapter != null -> { + val existing = scale18Adapter!!.items.toMutableList() + val idx = existing.indexOfFirst { it.address == address } + val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis()) + if (idx >= 0) existing[idx] = newData else existing.add(newData) + scale18Adapter!!.updateByAddress(existing) + } + else -> { + val idx = scaleList.indexOfFirst { it.address == address } + val item = ScaleItem(address, weight, state) + if (idx >= 0) { + scaleList[idx] = item + linearAdapter.notifyItemChanged(idx) + } else { + scaleList.add(item) + scaleList.sortBy { it.address } + linearAdapter.notifyDataSetChanged() + } + } } } } @@ -91,7 +162,6 @@ class SlaveActivity : BaseActivity() { /** * 更新主设备连接状态文字 - * 由 ScaleWebSocketServer 的 onOpen/onClose 回调驱动(可在后续扩展中接入) */ @SuppressLint("SetTextI18n") fun updateMasterStatus(connected: Boolean) { @@ -114,7 +184,6 @@ class SlaveActivity : BaseActivity() { /** * 获取本机局域网 IP,委托给 NetworkUtil 统一处理版本兼容 - * 获取失败时返回"未知" */ private fun getLocalIpAddress(): String { return NetworkUtil.getLocalIpAddress(this).ifEmpty { "未知" } @@ -146,3 +215,4 @@ class SlaveActivity : BaseActivity() { } } } +