refactor(scale): 提取 Scale22/18GridAdapter 到 adapter 包,SlaveActivity 复用网格布局
This commit is contained in:
@@ -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<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=0,margin大),其余为第2-3行(viewType=1,margin小) */
|
||||
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 {
|
||||
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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user