refactor(scale): 提取 Scale22/18GridAdapter 到 adapter 包,SlaveActivity 复用网格布局

This commit is contained in:
2026-04-16 14:26:34 +08:00
parent cba5bda757
commit c3f3b1215b
5 changed files with 329 additions and 172 deletions
@@ -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
* 使用 GridLayoutManager6列)排列,第1行格子 margin 较大(视觉更小),第2-3行 margin 较小
* 点击格子触发清零回调
*/
class Scale18GridAdapter : BaseQuickAdapter<ScaleData, Scale18GridAdapter.VH>(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<ScaleData?>) {
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<ScaleData>) {
val byAddress = scales.associateBy { it.address }
update(ScaleDeviceConfig.SCALE_ORDER_18.map { byAddress[it] })
}
/** 前6个位置为第1行(viewType=0margin大),其余为第2-3行(viewType=1margin小) */
override fun getItemViewType(position: Int, list: List<ScaleData>) = 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"
}
}
@@ -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<ScaleData, Scale22GridAdapter.VH>(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<ScaleData?>) {
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<ScaleData>) {
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<ScaleData>) = 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"
}
}
@@ -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)
}
@@ -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<ScaleData>) {
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<ScaleData>) {
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<ScaleData, Scale22InnerAdapter.VH>(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<ScaleData?>) {
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<ScaleData>) = 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<ScaleData, Scale18InnerAdapter.VH>(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<ScaleData?>) {
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<ScaleData>) = 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) {
@@ -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<ScaleItem>()
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 {
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
adapter.notifyItemChanged(idx)
linearAdapter.notifyItemChanged(idx)
} else {
scaleList.add(item)
scaleList.sortBy { it.address }
adapter.notifyDataSetChanged()
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() {
}
}
}