feat(activity): 重构 MasterScaleActivity 秤数据网格布局,按设备号适配不同显示方案
This commit is contained in:
@@ -2,7 +2,9 @@ package com.shuwei.dish.match.ui
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@@ -21,6 +23,14 @@ import com.shuwei.dish.match.utils.ext.dp
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
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 角色显示)
|
* 主设备秤数据总览页面(仅 MASTER 角色显示)
|
||||||
* 聚合展示所有设备(本机 + 远端子设备)的实时秤数据
|
* 聚合展示所有设备(本机 + 远端子设备)的实时秤数据
|
||||||
@@ -54,7 +64,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
|
|
||||||
binding.ivMasterBack.setOnClickListener { finish() }
|
binding.ivMasterBack.setOnClickListener { finish() }
|
||||||
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
|
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
|
||||||
binding.rvScaleList.itemAnimator = null // 关闭默认动画,避免数据频繁更新时出现闪烁错乱
|
binding.rvScaleList.itemAnimator = null
|
||||||
binding.rvScaleList.adapter = adapter
|
binding.rvScaleList.adapter = adapter
|
||||||
|
|
||||||
observeScaleData()
|
observeScaleData()
|
||||||
@@ -66,12 +76,9 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun observeScaleData() {
|
private fun observeScaleData() {
|
||||||
val flow = ScaleServiceManager.allScales ?: return
|
val flow = ScaleServiceManager.allScales ?: return
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
flow.collectLatest { scaleMap ->
|
flow.collectLatest { scaleMap ->
|
||||||
// 按 deviceId 分组,本机设备排最前,其余按 deviceId 排序
|
|
||||||
val localId = com.shuwei.dish.match.base.GlobalData.deviceId
|
val localId = com.shuwei.dish.match.base.GlobalData.deviceId
|
||||||
// 本机 IP 实时读取,避免 App 启动时网络未就绪导致缓存为空
|
|
||||||
val localIp = NetworkUtil.getLocalIpAddress(this@MasterScaleActivity)
|
val localIp = NetworkUtil.getLocalIpAddress(this@MasterScaleActivity)
|
||||||
val groups = scaleMap.values
|
val groups = scaleMap.values
|
||||||
.groupBy { it.deviceId }
|
.groupBy { it.deviceId }
|
||||||
@@ -124,48 +131,240 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
holder.binding.tvDeviceLabel.text = "设备:${group.deviceId}"
|
holder.binding.tvDeviceLabel.text = "设备:${group.deviceId}"
|
||||||
holder.binding.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
holder.binding.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
||||||
|
|
||||||
// 清空旧秤行,重新填充
|
|
||||||
val container: LinearLayout = holder.binding.llScaleContainer
|
val container: LinearLayout = holder.binding.llScaleContainer
|
||||||
container.removeAllViews()
|
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) {
|
val stateStr = when (scale.state) {
|
||||||
WeightUtil.STATE_STABLE -> "稳定"
|
WeightUtil.STATE_STABLE -> "稳定"
|
||||||
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
||||||
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
||||||
else -> "${scale.state}"
|
else -> "${scale.state}"
|
||||||
}
|
}
|
||||||
// 每行:秤编号 重量 状态
|
val baseSize = (height / resources.displayMetrics.density).toInt()
|
||||||
val row = LinearLayout(holder.itemView.context).apply {
|
val addrSp = (baseSize * 0.16f).coerceIn(9f, 14f).toInt()
|
||||||
orientation = LinearLayout.HORIZONTAL
|
val weightSp = (baseSize * 0.22f).coerceIn(12f, 20f).toInt()
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
val stateSp = (baseSize * 0.14f).coerceIn(8f, 12f).toInt()
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
addView(makeCellTextView("秤${scale.address}", addrSp))
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
addView(makeCellTextView("${scale.weight}g", weightSp, bold = true))
|
||||||
).also { it.topMargin = 4.dp }
|
addView(makeCellTextView(stateStr, stateSp))
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 创建秤行内的 TextView */
|
/**
|
||||||
private fun makeTextView(
|
* 18个秤布局:6列3行,第1行小格子,第2-3行大格子
|
||||||
text: String,
|
* 显示顺序:[6,5,4,3,2,1, 12,11,10,9,8,7, 18,17,16,15,14,13]
|
||||||
spSize: Int,
|
*/
|
||||||
bold: Boolean = false,
|
private fun buildLayout18(container: LinearLayout, scales: List<ScaleData>) {
|
||||||
weight: Float = 1f
|
val cellSize = container.context.cellSize(6)
|
||||||
): TextView = TextView(this@MasterScaleActivity).apply {
|
// 按指定顺序重排(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
|
this.text = text
|
||||||
textSize = spSize.toFloat()
|
textSize = spSize.toFloat()
|
||||||
if (bold) setTypeface(null, android.graphics.Typeface.BOLD)
|
if (bold) setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
setTextColor(
|
setTextColor(if (bold) getColor(R.color.home_title) else getColor(R.color.home_sub_title))
|
||||||
if (bold) getColor(R.color.home_title)
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
else getColor(R.color.home_sub_title)
|
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