feat(activity): 重构 MasterScaleActivity 秤数据网格布局,按设备号适配不同显示方案

This commit is contained in:
2026-04-14 14:00:13 +08:00
parent 0477fad57b
commit b69b8c10e8
2 changed files with 239 additions and 30 deletions
@@ -2,7 +2,9 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
@@ -21,6 +23,14 @@ import com.shuwei.dish.match.utils.ext.dp
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
/** 根据列数计算正方形格子边长(屏幕可用宽度 / 列数) */
private fun android.content.Context.cellSize(columns: Int): Int {
val screenWidth = resources.displayMetrics.widthPixels
// 减去左右 padding16dp * 2)和格子间距(2dp * 2 * columns
val usable = screenWidth - 32.dp - columns * 4.dp
return usable / columns
}
/**
* 主设备秤数据总览页面(仅 MASTER 角色显示)
* 聚合展示所有设备(本机 + 远端子设备)的实时秤数据
@@ -54,7 +64,7 @@ class MasterScaleActivity : BaseActivity() {
binding.ivMasterBack.setOnClickListener { finish() }
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
binding.rvScaleList.itemAnimator = null // 关闭默认动画,避免数据频繁更新时出现闪烁错乱
binding.rvScaleList.itemAnimator = null
binding.rvScaleList.adapter = adapter
observeScaleData()
@@ -66,12 +76,9 @@ class MasterScaleActivity : BaseActivity() {
*/
private fun observeScaleData() {
val flow = ScaleServiceManager.allScales ?: return
lifecycleScope.launch {
flow.collectLatest { scaleMap ->
// 按 deviceId 分组,本机设备排最前,其余按 deviceId 排序
val localId = com.shuwei.dish.match.base.GlobalData.deviceId
// 本机 IP 实时读取,避免 App 启动时网络未就绪导致缓存为空
val localIp = NetworkUtil.getLocalIpAddress(this@MasterScaleActivity)
val groups = scaleMap.values
.groupBy { it.deviceId }
@@ -124,48 +131,240 @@ class MasterScaleActivity : BaseActivity() {
holder.binding.tvDeviceLabel.text = "设备:${group.deviceId}"
holder.binding.tvDeviceIp.text = "IP${group.ip.ifEmpty { "未知" }}"
// 清空旧秤行,重新填充
val container: LinearLayout = holder.binding.llScaleContainer
container.removeAllViews()
group.scales.forEach { scale ->
// 根据设备号选择不同的网格布局策略
when (group.deviceId) {
"a46fa55c-113c-3511-bb1f-41e5eff77c4b" -> buildLayout22(container, group.scales)
"1038da9f-c6eb-326e-a1d9-d6d3af978b22" -> buildLayout18(container, group.scales)
"8fc2ab34-2137-3112-acca-f884ea8736d4" -> buildLayout2(container, group.scales)
else -> buildLayoutDefault(container, group.scales)
}
}
}
/**
* 22个秤布局:8列,前2列权重1.5(大格子),后6列权重1.0(小格子)
* 显示顺序:[20,22,19,21,6,12,18,5,11,17,4,10,16,3,9,15,2,8,14,1,7,13]
*/
private fun buildLayout22(container: LinearLayout, scales: List<ScaleData>) {
val margin = 2.dp
val usable = resources.displayMetrics.widthPixels - 32.dp
val leftColW = (usable * 1.5f / 9f).toInt()
val rightColW = (usable * 1.0f / 9f).toInt()
val largeCellSize = leftColW - margin * 2
val smallCellSize = rightColW - margin * 2
// 按指定顺序重排
val order = listOf(20,22,19,21,6,12,18,5,11,17,4,10,16,3,9,15,2,8,14,1,7,13)
val scaleByAddress = scales.associateBy { it.address }
val sorted = order.map { scaleByAddress[it] }
val outerRow = LinearLayout(container.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
)
}
var index = 0
// 前2列:权重1.5,每列2个大格子(正方形)
repeat(2) {
val col = LinearLayout(container.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.5f)
}
repeat(2) {
col.addView(makeScaleCellFixed(sorted.getOrNull(index++), largeCellSize, largeCellSize, margin))
}
outerRow.addView(col)
}
// 后6列:权重1.0,每列3个小格子(正方形)
repeat(6) {
val col = LinearLayout(container.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f)
}
repeat(3) {
col.addView(makeScaleCellFixed(sorted.getOrNull(index++), smallCellSize, smallCellSize, margin))
}
outerRow.addView(col)
}
container.addView(outerRow)
}
/**
* 创建固定宽高的秤格子(用于22格子布局,宽高可独立指定)
* @param scale 秤数据
* @param width 内容宽度(px
* @param height 内容高度(px
* @param margin 外边距(px
*/
@SuppressLint("SetTextI18n")
private fun makeScaleCellFixed(scale: ScaleData?, width: Int, height: Int, margin: Int): View {
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
.also { it.setMargins(margin, margin, margin, margin) }
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
layoutParams = lp
gravity = Gravity.CENTER
setPadding(4.dp, 2.dp, 4.dp, 2.dp)
setBackgroundResource(R.drawable.shape_scale_cell)
if (scale != null) {
val stateStr = when (scale.state) {
WeightUtil.STATE_STABLE -> "稳定"
WeightUtil.STATE_UNSTABLE -> "不稳定"
WeightUtil.STATE_OVER_WEIGHT -> "超量"
else -> "${scale.state}"
}
// 每行:秤编号 重量 状态
val row = LinearLayout(holder.itemView.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).also { it.topMargin = 4.dp }
}
row.addView(makeTextView("${scale.address}", 22, weight = 1f))
row.addView(makeTextView("${scale.weight} g", 26, bold = true, weight = 2f))
row.addView(makeTextView(stateStr, 18, weight = 1f))
container.addView(row)
val baseSize = (height / resources.displayMetrics.density).toInt()
val addrSp = (baseSize * 0.16f).coerceIn(9f, 14f).toInt()
val weightSp = (baseSize * 0.22f).coerceIn(12f, 20f).toInt()
val stateSp = (baseSize * 0.14f).coerceIn(8f, 12f).toInt()
addView(makeCellTextView("${scale.address}", addrSp))
addView(makeCellTextView("${scale.weight}g", weightSp, bold = true))
addView(makeCellTextView(stateStr, stateSp))
}
}
}
/** 创建秤行内的 TextView */
private fun makeTextView(
text: String,
spSize: Int,
bold: Boolean = false,
weight: Float = 1f
): TextView = TextView(this@MasterScaleActivity).apply {
/**
* 18个秤布局:6列3行,第1行小格子,第2-3行大格子
* 显示顺序:[6,5,4,3,2,1, 12,11,10,9,8,7, 18,17,16,15,14,13]
*/
private fun buildLayout18(container: LinearLayout, scales: List<ScaleData>) {
val cellSize = container.context.cellSize(6)
// 按指定顺序重排(address 从1开始,转为0-based index取值)
val order = listOf(6,5,4,3,2,1, 12,11,10,9,8,7, 18,17,16,15,14,13)
val scaleByAddress = scales.associateBy { it.address }
val sorted = order.map { scaleByAddress[it] }
var index = 0
// 第1行:增大 margin 使格子看起来更小
val row1 = makeRowLayout(container)
repeat(6) { row1.addView(makeScaleCell(sorted.getOrNull(index++), cellSize, inHorizontalRow = true, margin = 8.dp)) }
container.addView(row1)
repeat(2) {
val row = makeRowLayout(container)
repeat(6) { row.addView(makeScaleCell(sorted.getOrNull(index++), cellSize, inHorizontalRow = true)) }
container.addView(row)
}
}
/** 2个秤布局:1行2列 */
private fun buildLayout2(container: LinearLayout, scales: List<ScaleData>) {
val cellSize = container.context.cellSize(2)
val row = makeRowLayout(container)
scales.take(2).forEach { row.addView(makeScaleCell(it, cellSize, inHorizontalRow = true)) }
container.addView(row)
}
/** 默认布局:逐行展示,每行加格子背景 */
@SuppressLint("SetTextI18n")
private fun buildLayoutDefault(container: LinearLayout, scales: List<ScaleData>) {
scales.forEach { scale ->
val stateStr = when (scale.state) {
WeightUtil.STATE_STABLE -> "稳定"
WeightUtil.STATE_UNSTABLE -> "不稳定"
WeightUtil.STATE_OVER_WEIGHT -> "超量"
else -> "${scale.state}"
}
val row = LinearLayout(container.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
).also { it.setMargins(2.dp, 4.dp, 2.dp, 4.dp) }
setPadding(12.dp, 16.dp, 12.dp, 16.dp)
setBackgroundResource(R.drawable.shape_scale_cell)
}
row.addView(makeTextView("${scale.address}", 22, weight = 1f))
row.addView(makeTextView("${scale.weight} g", 26, bold = true, weight = 2f))
row.addView(makeTextView(stateStr, 18, weight = 1f))
container.addView(row)
}
}
/** 创建横向行容器 */
private fun makeRowLayout(parent: ViewGroup): LinearLayout =
LinearLayout(parent.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
)
}
/** 创建纵向列容器(等宽分配) */
private fun makeColumnLayout(parent: ViewGroup): LinearLayout =
LinearLayout(parent.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
/**
* 创建单个秤格子视图
* @param scale 秤数据,为 null 时显示空格子占位
* @param cellSize 格子边长(px),宽高相等实现正方形效果
* @param inHorizontalRow 是否处于横向行中(true=按权重分配宽度,false=宽度撑满)
* @param margin 格子外边距(px),增大可让格子视觉上更小
*/
@SuppressLint("SetTextI18n")
private fun makeScaleCell(scale: ScaleData?, cellSize: Int, inHorizontalRow: Boolean, margin: Int = 2.dp): View {
// 实际格子内容高度 = 格子分配宽度 - 左右 margin,保证宽高一致接近正方形
val contentSize = cellSize - margin * 2
val lp = if (inHorizontalRow) {
LinearLayout.LayoutParams(0, contentSize, 1f)
} else {
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, contentSize)
}.also { it.setMargins(margin, margin, margin, margin) }
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
layoutParams = lp
gravity = Gravity.CENTER
setPadding(4.dp, 2.dp, 4.dp, 2.dp)
setBackgroundResource(R.drawable.shape_scale_cell)
if (scale != null) {
val stateStr = when (scale.state) {
WeightUtil.STATE_STABLE -> "稳定"
WeightUtil.STATE_UNSTABLE -> "不稳定"
WeightUtil.STATE_OVER_WEIGHT -> "超量"
else -> "${scale.state}"
}
// 字号根据格子尺寸自适应(格子越小字越小)
val baseSize = (cellSize / resources.displayMetrics.density).toInt()
val addrSp = (baseSize * 0.16f).coerceIn(9f, 14f).toInt()
val weightSp = (baseSize * 0.22f).coerceIn(12f, 20f).toInt()
val stateSp = (baseSize * 0.14f).coerceIn(8f, 12f).toInt()
addView(makeCellTextView("${scale.address}", addrSp))
addView(makeCellTextView("${scale.weight}g", weightSp, bold = true))
addView(makeCellTextView(stateStr, stateSp))
}
}
}
/** 创建格子内居中 TextView */
private fun makeCellTextView(text: String, spSize: Int, bold: Boolean = false): TextView =
TextView(this).apply {
this.text = text
textSize = spSize.toFloat()
if (bold) setTypeface(null, android.graphics.Typeface.BOLD)
setTextColor(
if (bold) getColor(R.color.home_title)
else getColor(R.color.home_sub_title)
setTextColor(if (bold) getColor(R.color.home_title) else getColor(R.color.home_sub_title))
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, weight)
gravity = Gravity.CENTER
includeFontPadding = false
}
/** 创建默认布局行内的 TextView */
private fun makeTextView(
text: String, spSize: Int, bold: Boolean = false, weight: Float = 1f
): TextView = TextView(this).apply {
this.text = text
textSize = spSize.toFloat()
if (bold) setTypeface(null, android.graphics.Typeface.BOLD)
setTextColor(if (bold) getColor(R.color.home_title) else getColor(R.color.home_sub_title))
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, weight)
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 秤格子背景:白色底 + 灰色描边 + 圆角,与页面蓝色背景形成对比 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="@color/gray_d6" />
<corners android:radius="8dp" />
</shape>