feat(activity): 重构 MasterScaleActivity 秤数据网格布局,按设备号适配不同显示方案
This commit is contained in:
@@ -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
|
||||
// 减去左右 padding(16dp * 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>
|
||||
Reference in New Issue
Block a user