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.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.chad.library.adapter4.BaseMultiItemAdapter
|
import com.chad.library.adapter4.BaseMultiItemAdapter
|
||||||
import com.chad.library.adapter4.BaseQuickAdapter
|
|
||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
import com.google.android.flexbox.AlignItems
|
import com.google.android.flexbox.AlignItems
|
||||||
import com.google.android.flexbox.FlexDirection
|
import com.google.android.flexbox.FlexDirection
|
||||||
import com.google.android.flexbox.FlexWrap
|
import com.google.android.flexbox.FlexWrap
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.shuwei.dish.match.R
|
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.base.BaseActivity
|
||||||
import com.shuwei.dish.match.databinding.ActivityMasterScaleBinding
|
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.ListTypeScale1Binding
|
||||||
import com.shuwei.dish.match.databinding.ListTypeScale18Binding
|
import com.shuwei.dish.match.databinding.ListTypeScale18Binding
|
||||||
import com.shuwei.dish.match.databinding.ListTypeScale2Binding
|
import com.shuwei.dish.match.databinding.ListTypeScale2Binding
|
||||||
import com.shuwei.dish.match.databinding.ListTypeScale22Binding
|
import com.shuwei.dish.match.databinding.ListTypeScale22Binding
|
||||||
import com.shuwei.dish.match.scale.ScaleData
|
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.scale.ScaleServiceManager
|
||||||
import com.shuwei.dish.match.utils.NetworkUtil
|
import com.shuwei.dish.match.utils.NetworkUtil
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
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.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
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_SCALE2 = 0
|
||||||
private const val VIEW_TYPE_SCALE22 = 1
|
private const val VIEW_TYPE_SCALE22 = 1
|
||||||
private const val VIEW_TYPE_SCALE18 = 2
|
private const val VIEW_TYPE_SCALE18 = 2
|
||||||
@@ -90,7 +82,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
.groupBy { it.deviceId }
|
.groupBy { it.deviceId }
|
||||||
.entries
|
.entries
|
||||||
.sortedWith(compareBy { entry ->
|
.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) ->
|
.map { (deviceId, scales) ->
|
||||||
DeviceGroup(
|
DeviceGroup(
|
||||||
@@ -208,9 +200,9 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
onItemViewType { position, list -> when (list.getOrNull(position)?.deviceId) {
|
onItemViewType { position, list -> when (list.getOrNull(position)?.deviceId) {
|
||||||
DEVICE_ID_2 -> VIEW_TYPE_SCALE2
|
ScaleDeviceConfig.DEVICE_ID_2 -> VIEW_TYPE_SCALE2
|
||||||
DEVICE_ID_22 -> VIEW_TYPE_SCALE22
|
ScaleDeviceConfig.DEVICE_ID_22 -> VIEW_TYPE_SCALE22
|
||||||
DEVICE_ID_18 -> VIEW_TYPE_SCALE18
|
ScaleDeviceConfig.DEVICE_ID_18 -> VIEW_TYPE_SCALE18
|
||||||
else -> VIEW_TYPE_SCALE1
|
else -> VIEW_TYPE_SCALE1
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@@ -256,7 +248,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
// ── ViewHolder: 22个秤 ─────────────────────────────────────────────────
|
// ── ViewHolder: 22个秤 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) {
|
private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) {
|
||||||
private var innerAdapter: Scale22InnerAdapter? = null
|
private var innerAdapter: Scale22GridAdapter? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(group: DeviceGroup) {
|
fun bind(group: DeviceGroup) {
|
||||||
@@ -276,7 +268,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
alignItems = AlignItems.FLEX_START
|
alignItems = AlignItems.FLEX_START
|
||||||
}
|
}
|
||||||
b.rvScales.itemAnimator = null
|
b.rvScales.itemAnimator = null
|
||||||
innerAdapter = Scale22InnerAdapter(largeSize, smallSize).apply {
|
innerAdapter = Scale22GridAdapter(largeSize, smallSize).apply {
|
||||||
onItemClick = { scale ->
|
onItemClick = { scale ->
|
||||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
||||||
}
|
}
|
||||||
@@ -288,16 +280,14 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
|
|
||||||
/** 仅更新秤数据,不触发外层任何 notify */
|
/** 仅更新秤数据,不触发外层任何 notify */
|
||||||
fun updateScales(scales: List<ScaleData>) {
|
fun updateScales(scales: List<ScaleData>) {
|
||||||
val scaleByAddress = scales.associateBy { it.address }
|
innerAdapter?.updateByAddress(scales)
|
||||||
val ordered = SCALE_ORDER_22.map { scaleByAddress[it] }
|
|
||||||
innerAdapter?.update(ordered)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ViewHolder: 18个秤 ─────────────────────────────────────────────────
|
// ── ViewHolder: 18个秤 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) {
|
private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) {
|
||||||
private var innerAdapter: Scale18InnerAdapter? = null
|
private var innerAdapter: Scale18GridAdapter? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(group: DeviceGroup) {
|
fun bind(group: DeviceGroup) {
|
||||||
@@ -307,13 +297,12 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
if (innerAdapter == null) {
|
if (innerAdapter == null) {
|
||||||
val gridLayoutManager = GridLayoutManager(b.root.context, 6).apply {
|
val gridLayoutManager = GridLayoutManager(b.root.context, 6).apply {
|
||||||
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||||
// 每个格子占1 span,共6列
|
|
||||||
override fun getSpanSize(position: Int) = 1
|
override fun getSpanSize(position: Int) = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.rvScales.layoutManager = gridLayoutManager
|
b.rvScales.layoutManager = gridLayoutManager
|
||||||
b.rvScales.itemAnimator = null
|
b.rvScales.itemAnimator = null
|
||||||
innerAdapter = Scale18InnerAdapter().apply {
|
innerAdapter = Scale18GridAdapter().apply {
|
||||||
onItemClick = { scale ->
|
onItemClick = { scale ->
|
||||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
||||||
}
|
}
|
||||||
@@ -325,9 +314,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
|
|
||||||
/** 仅更新秤数据,不触发外层任何 notify */
|
/** 仅更新秤数据,不触发外层任何 notify */
|
||||||
fun updateScales(scales: List<ScaleData>) {
|
fun updateScales(scales: List<ScaleData>) {
|
||||||
val scaleByAddress = scales.associateBy { it.address }
|
innerAdapter?.updateByAddress(scales)
|
||||||
val ordered = SCALE_ORDER_18.map { scaleByAddress[it] }
|
|
||||||
innerAdapter?.update(ordered)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
private fun stateStr(state: Int?) = when (state) {
|
||||||
|
|||||||
@@ -4,31 +4,46 @@ import android.annotation.SuppressLint
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.activity.addCallback
|
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.BaseActivity
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
import com.shuwei.dish.match.databinding.ActivitySlaveBinding
|
import com.shuwei.dish.match.databinding.ActivitySlaveBinding
|
||||||
import com.shuwei.dish.match.databinding.ListItemScaleRowBinding
|
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.scale.ScaleServiceManager
|
||||||
import com.shuwei.dish.match.utils.NetworkUtil
|
import com.shuwei.dish.match.utils.NetworkUtil
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 子设备专属页面(小屏幕设备使用)
|
* 子设备专属页面(小屏幕设备使用)
|
||||||
* 仅展示本机秤的实时数据和主设备连接状态
|
* 仅展示本机秤的实时数据和主设备连接状态
|
||||||
* 布局简洁,适配小屏幕
|
* 22个秤和18个秤使用与 MasterScaleActivity 相同的网格布局,其余使用线性列表
|
||||||
*/
|
*/
|
||||||
class SlaveActivity : BaseActivity() {
|
class SlaveActivity : BaseActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivitySlaveBinding
|
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 data class ScaleItem(val address: Int, val weight: Double, val state: Int)
|
||||||
|
|
||||||
private val scaleList = mutableListOf<ScaleItem>()
|
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
|
private var masterConnected = false
|
||||||
@@ -41,23 +56,59 @@ class SlaveActivity : BaseActivity() {
|
|||||||
|
|
||||||
binding.tvDeviceId.text = "设备:${GlobalData.deviceId}"
|
binding.tvDeviceId.text = "设备:${GlobalData.deviceId}"
|
||||||
binding.tvDeviceIp.text = "IP:${getLocalIpAddress()}"
|
binding.tvDeviceIp.text = "IP:${getLocalIpAddress()}"
|
||||||
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
|
|
||||||
binding.rvScaleList.itemAnimator = null // 关闭默认动画,避免数据频繁更新时出现闪烁错乱
|
|
||||||
binding.rvScaleList.adapter = adapter
|
|
||||||
|
|
||||||
|
setupScaleList()
|
||||||
updateMasterStatus(ScaleServiceManager.isMasterConnected)
|
updateMasterStatus(ScaleServiceManager.isMasterConnected)
|
||||||
listenLocalScales()
|
listenLocalScales()
|
||||||
listenMasterConnection()
|
listenMasterConnection()
|
||||||
addBackKeyListener()
|
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 后重复打开本页
|
* 禁用返回键,防止退回 InitActivity 后重复打开本页
|
||||||
*/
|
*/
|
||||||
private fun addBackKeyListener() {
|
private fun addBackKeyListener() {
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) { }
|
||||||
// 禁用返回键,不执行任何操作
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,19 +122,39 @@ class SlaveActivity : BaseActivity() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听本机 WeightUtil 回调,实时刷新秤列表
|
* 监听本机 WeightUtil 回调,实时刷新秤列表
|
||||||
|
* 22/18个秤走网格 adapter,其余走线性列表
|
||||||
*/
|
*/
|
||||||
private fun listenLocalScales() {
|
private fun listenLocalScales() {
|
||||||
WeightUtil.addWeightListener(TAG) { address, state, weight ->
|
WeightUtil.addWeightListener(TAG) { address, state, weight ->
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val idx = scaleList.indexOfFirst { it.address == address }
|
when {
|
||||||
val item = ScaleItem(address, weight, state)
|
scale22Adapter != null -> {
|
||||||
if (idx >= 0) {
|
// 用最新数据更新缓存后整体刷新网格
|
||||||
scaleList[idx] = item
|
val existing = scale22Adapter!!.items.toMutableList()
|
||||||
adapter.notifyItemChanged(idx)
|
val idx = existing.indexOfFirst { it.address == address }
|
||||||
} else {
|
val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis())
|
||||||
scaleList.add(item)
|
if (idx >= 0) existing[idx] = newData else existing.add(newData)
|
||||||
scaleList.sortBy { it.address }
|
scale22Adapter!!.updateByAddress(existing)
|
||||||
adapter.notifyDataSetChanged()
|
}
|
||||||
|
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")
|
@SuppressLint("SetTextI18n")
|
||||||
fun updateMasterStatus(connected: Boolean) {
|
fun updateMasterStatus(connected: Boolean) {
|
||||||
@@ -114,7 +184,6 @@ class SlaveActivity : BaseActivity() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取本机局域网 IP,委托给 NetworkUtil 统一处理版本兼容
|
* 获取本机局域网 IP,委托给 NetworkUtil 统一处理版本兼容
|
||||||
* 获取失败时返回"未知"
|
|
||||||
*/
|
*/
|
||||||
private fun getLocalIpAddress(): String {
|
private fun getLocalIpAddress(): String {
|
||||||
return NetworkUtil.getLocalIpAddress(this).ifEmpty { "未知" }
|
return NetworkUtil.getLocalIpAddress(this).ifEmpty { "未知" }
|
||||||
@@ -146,3 +215,4 @@ class SlaveActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user