diff --git a/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt index c18d502..25c298d 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt @@ -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) { + 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) { + 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) { + 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) { + 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) } } - diff --git a/app/src/main/res/drawable/shape_scale_cell.xml b/app/src/main/res/drawable/shape_scale_cell.xml new file mode 100644 index 0000000..acf43ea --- /dev/null +++ b/app/src/main/res/drawable/shape_scale_cell.xml @@ -0,0 +1,10 @@ + + + + + + +