refactor(activity): 重构 MasterScaleActivity 多类型秤布局,新增 Scale22GridActivity

- MasterScaleActivity 使用 BaseMultiItemAdapter 支持4种 viewType(scale1/2/18/22)
- 内嵌 adapter 改用 BaseQuickAdapter + 自定义 VH,替换原 RecyclerView.Adapter
- 新增 Scale22GridActivity,使用 FlexboxLayoutManager 实现22格混合网格布局
- 新增 list_type_scale1/2/18/22.xml 及 list_item_scale22_row.xml 布局文件
- 删除废弃的 list_item_scale_data.xml
- 新增 flexbox 依赖,注册 Scale22GridActivity
This commit is contained in:
2026-04-15 16:44:33 +08:00
parent 5ac35a9cab
commit b775b20ff1
16 changed files with 732 additions and 288 deletions
+1
View File
@@ -107,6 +107,7 @@ dependencies {
implementation(libs.retrofit)
implementation(libs.java.websocket)
implementation(libs.converter.gson)
implementation(libs.flexbox)
implementation(libs.baserecyclerviewadapterhelper4)
+5
View File
@@ -106,6 +106,11 @@
android:theme="@style/Theme.DishMatch.NoSplash"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name="com.shuwei.dish.match.ui.Scale22GridActivity"
android:theme="@style/Theme.DishMatch.NoSplash"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name="com.shuwei.dish.match.ui.SlaveActivity"
android:theme="@style/Theme.DishMatch.NoSplash"
@@ -0,0 +1,17 @@
package com.shuwei.dish.match.scale
/**
* 主设备向子设备发送的控制指令
* @param cmd 指令类型,如 "tare"(清零)
* @param deviceId 目标设备 ID,子设备收到后校验是否匹配自身
* @param address 目标秤地址
*/
data class ScaleCommand(
val cmd: String,
val deviceId: String,
val address: Int
) {
companion object {
const val CMD_TARE = "tare"
}
}
@@ -134,6 +134,22 @@ object ScaleServiceManager {
}
}
/**
* 向指定设备的指定秤发送清零指令
* - 本机秤:直接调用 WeightUtil.tareTwo
* - 子设备秤:通过 WebSocket 发送指令
* @param deviceId 目标设备 ID
* @param address 目标秤地址
*/
fun sendTare(deviceId: String, address: Int) {
if (deviceId == GlobalData.deviceId) {
com.shuwei.dish.match.utils.WeightUtil.tareTwo(address)
} else {
val cmd = ScaleCommand(ScaleCommand.CMD_TARE, deviceId, address)
wsClient?.sendCommand(deviceId, cmd)
}
}
/**
* 停止所有秤服务,在 Application.onTerminate() 或退出时调用
*/
@@ -120,6 +120,17 @@ class ScaleWebSocketClient {
reconnectDelays[deviceId] = minOf(delay * 2, RECONNECT_MAX_MS)
}
/**
* 向指定子设备发送控制指令
* @param deviceId 目标子设备 ID
* @param command 指令对象
* @return true=发送成功,false=设备未连接
*/
fun sendCommand(deviceId: String, command: ScaleCommand): Boolean {
val ws = connections[deviceId] ?: return false
return ws.send(gson.toJson(command))
}
/**
* 断开指定子设备连接(不再重连)
* @param deviceId 子设备 ID
@@ -126,7 +126,19 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
}
override fun onMessage(conn: WebSocket, message: String) {
// 服务端无需处理来自主设备的消息
try {
val cmd = gson.fromJson(message, ScaleCommand::class.java)
// 校验指令目标设备是否为本机
if (cmd.deviceId != deviceId) return
when (cmd.cmd) {
ScaleCommand.CMD_TARE -> {
Log.d(TAG, "收到清零指令, address=${cmd.address}")
com.shuwei.dish.match.utils.WeightUtil.tareTwo(cmd.address)
}
}
} catch (e: Exception) {
Log.w(TAG, "解析指令失败: ${e.message}")
}
}
override fun onError(conn: WebSocket?, ex: Exception) {
@@ -1,21 +1,31 @@
package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.content.Context
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
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter4.BaseMultiItemAdapter
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
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.R
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.databinding.ActivityMasterScaleBinding
import com.shuwei.dish.match.databinding.ListItemScaleDataBinding
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
import com.shuwei.dish.match.databinding.ListTypeScale1Binding
import com.shuwei.dish.match.databinding.ListTypeScale18Binding
import com.shuwei.dish.match.databinding.ListTypeScale2Binding
import com.shuwei.dish.match.databinding.ListTypeScale22Binding
import com.shuwei.dish.match.scale.ScaleData
import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.utils.NetworkUtil
@@ -24,44 +34,28 @@ 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
}
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 const val DEVICE_ID_2 = "8fc2ab34-2137-3112-acca-f884ea8736d4" // 2个秤
private const val DEVICE_ID_22 = "a46fa55c-113c-3511-bb1f-41e5eff77c4b" // 22个秤
private const val DEVICE_ID_18 = "1038da9f-c6eb-326e-a1d9-d6d3af978b22" // 18个秤
private const val DEVICE_ID_1 = "7cc0f6ea-f13d-3013-a867-fc998eb554ac" // 1个秤
/** 22个秤的显示顺序 */
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)
/** 18个秤的显示顺序 */
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 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_SCALE22 = 1
private const val VIEW_TYPE_SCALE18 = 2
private const val VIEW_TYPE_SCALE1 = 3
/**
* 主设备秤数据总览页面(仅 MASTER 角色显示)
* 聚合展示所有设备(本机 + 远端子设备)的实时秤数据
* 数据按设备分组展示:设备号 + IP + 该设备下所有秤数据
* 数据来源:ScaleServiceManager.allScalesStateFlow
* 主设备秤数据总览页面
* 按设备分组展示,每种设备对应独立的 item 布局和 viewType
*/
class MasterScaleActivity : BaseActivity() {
private lateinit var binding: ActivityMasterScaleBinding
/**
* 按设备分组后的数据模型
* @param deviceId 设备 ID
* @param ip 设备 IP 地址
* @param scales 该设备下所有秤数据,按 address 排序
*/
private data class DeviceGroup(
val deviceId: String,
val ip: String,
@@ -78,17 +72,13 @@ class MasterScaleActivity : BaseActivity() {
setHeaderBackground()
binding.ivMasterBack.setOnClickListener { finish() }
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
binding.rvScaleList.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
binding.rvScaleList.itemAnimator = null
binding.rvScaleList.adapter = adapter
observeScaleData()
}
/**
* 订阅 ScaleServiceManager 的全量秤数据 StateFlow
* 每次有任一秤数据更新时,重新按设备分组并刷新列表
*/
private fun observeScaleData() {
val flow = ScaleServiceManager.allScales ?: return
lifecycleScope.launch {
@@ -98,8 +88,8 @@ class MasterScaleActivity : BaseActivity() {
val groups = scaleMap.values
.groupBy { it.deviceId }
.entries
.sortedWith(compareBy { idx ->
deviceOrder.indexOf(idx.key).let { if (it == -1) Int.MAX_VALUE else it }
.sortedWith(compareBy { entry ->
deviceOrder.indexOf(entry.key).let { if (it == -1) Int.MAX_VALUE else it }
})
.map { (deviceId, scales) ->
DeviceGroup(
@@ -129,250 +119,271 @@ class MasterScaleActivity : BaseActivity() {
binding.tvConnectionStatus.text = "已发现设备:$deviceCount 台 / 共 $scaleCount 个秤"
}
/** 设备分组适配器,每项展示一台设备及其所有秤数据 */
private inner class DeviceGroupAdapter(private val data: List<DeviceGroup>) :
RecyclerView.Adapter<DeviceGroupAdapter.VH>() {
private inner class DeviceGroupAdapter(data: MutableList<DeviceGroup>) :
BaseMultiItemAdapter<DeviceGroup>(data) {
inner class VH(val binding: ListItemScaleDataBinding) :
RecyclerView.ViewHolder(binding.root)
init {
addItemType(VIEW_TYPE_SCALE2, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
QuickViewHolder(ListTypeScale2Binding.inflate(LayoutInflater.from(context), parent, false).root)
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
item ?: return
val b = ListTypeScale2Binding.bind(holder.itemView)
Scale2VH(b).bind(item)
}
})
addItemType(VIEW_TYPE_SCALE22, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
QuickViewHolder(ListTypeScale22Binding.inflate(LayoutInflater.from(context), parent, false).root)
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
item ?: return
val b = ListTypeScale22Binding.bind(holder.itemView)
Scale22VH(b).bind(item)
}
})
addItemType(VIEW_TYPE_SCALE18, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
QuickViewHolder(ListTypeScale18Binding.inflate(LayoutInflater.from(context), parent, false).root)
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
item ?: return
val b = ListTypeScale18Binding.bind(holder.itemView)
Scale18VH(b).bind(item)
}
})
addItemType(VIEW_TYPE_SCALE1, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
QuickViewHolder(ListTypeScale1Binding.inflate(LayoutInflater.from(context), parent, false).root)
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
item ?: return
val b = ListTypeScale1Binding.bind(holder.itemView)
Scale1VH(b).bind(item)
}
})
onItemViewType { position, list -> when (list.getOrNull(position)?.deviceId) {
DEVICE_ID_2 -> VIEW_TYPE_SCALE2
DEVICE_ID_22 -> VIEW_TYPE_SCALE22
DEVICE_ID_18 -> VIEW_TYPE_SCALE18
else -> VIEW_TYPE_SCALE1
}}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = VH(
ListItemScaleDataBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
// ── ViewHolder: 2个秤 ──────────────────────────────────────────────────
override fun getItemCount() = data.size
private inner class Scale2VH(val b: ListTypeScale2Binding) : RecyclerView.ViewHolder(b.root) {
@SuppressLint("SetTextI18n")
fun bind(group: DeviceGroup) {
b.tvDeviceIp.text = "IP${group.ip.ifEmpty { "未知" }}"
b.tvDeviceLabel.text = "设备:${group.deviceId}"
b.llScaleContainer.removeAllViews()
val cellSize = (resources.displayMetrics.widthPixels - 4 * 4.dp - 10.dp) / 2
group.scales.take(2).forEach { scale ->
b.llScaleContainer.addView(makeScaleCell(scale, cellSize))
}
}
}
// ── ViewHolder: 22个秤 ─────────────────────────────────────────────────
private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) {
private var bound = false
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: VH, position: Int) {
val group = data[position]
holder.binding.tvDeviceLabel.text = "设备:${group.deviceId}"
holder.binding.tvDeviceIp.text = "IP${group.ip.ifEmpty { "未知" }}"
fun bind(group: DeviceGroup) {
b.tvDeviceIp.text = "IP${group.ip.ifEmpty { "未知" }}"
b.tvDeviceLabel.text = "设备:${group.deviceId}"
val container: LinearLayout = holder.binding.llScaleContainer
container.removeAllViews()
// 根据设备号选择不同的网格布局策略
when (group.deviceId) {
DEVICE_ID_22 -> buildLayout22(container, group.scales)
DEVICE_ID_18 -> buildLayout18(container, group.scales)
DEVICE_ID_2 -> buildLayout2(container, group.scales)
else -> buildLayoutDefault(container, group.scales)
if (!bound) {
// 计算格子尺寸(与 Scale22GridActivity 一致)
val screenWidth = resources.displayMetrics.widthPixels
val usable = screenWidth - 8 * 4.dp - 10.dp // 减去8列格子margin + 外层RV左右margin各5dp
val smallSize = usable / 9
val largeSize = (usable - smallSize * 6) / 2
val rvHeight = smallSize * 3 + 3 * 4.dp
b.rvScales.layoutParams = b.rvScales.layoutParams.also { it.height = rvHeight }
b.rvScales.layoutManager = FlexboxLayoutManager(b.root.context).apply {
flexDirection = FlexDirection.COLUMN
flexWrap = FlexWrap.WRAP
alignItems = AlignItems.FLEX_START
}
b.rvScales.itemAnimator = null
b.rvScales.adapter = Scale22InnerAdapter(largeSize, smallSize)
bound = true
}
val scaleByAddress = group.scales.associateBy { it.address }
val ordered = SCALE_ORDER_22.map { scaleByAddress[it] }
(b.rvScales.adapter as Scale22InnerAdapter).update(ordered)
}
}
/**
* 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>) {
// ── ViewHolder: 18个秤 ─────────────────────────────────────────────────
private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) {
private var bound = false
@SuppressLint("SetTextI18n")
fun bind(group: DeviceGroup) {
b.tvDeviceIp.text = "IP${group.ip.ifEmpty { "未知" }}"
b.tvDeviceLabel.text = "设备:${group.deviceId}"
if (!bound) {
val gridLayoutManager = GridLayoutManager(b.root.context, 6).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
// 每个格子占1 span,共6列
override fun getSpanSize(position: Int) = 1
}
}
b.rvScales.layoutManager = gridLayoutManager
b.rvScales.itemAnimator = null
b.rvScales.adapter = Scale18InnerAdapter()
bound = true
}
val scaleByAddress = group.scales.associateBy { it.address }
val ordered = SCALE_ORDER_18.map { scaleByAddress[it] }
(b.rvScales.adapter as Scale18InnerAdapter).update(ordered)
}
}
// ── ViewHolder: 1个秤 ──────────────────────────────────────────────────
private inner class Scale1VH(val b: ListTypeScale1Binding) : RecyclerView.ViewHolder(b.root) {
@SuppressLint("SetTextI18n")
fun bind(group: DeviceGroup) {
b.tvDeviceIp.text = "IP${group.ip.ifEmpty { "未知" }}"
b.tvDeviceLabel.text = "设备:${group.deviceId}"
val scale = group.scales.firstOrNull()
b.tvAddr.text = scale?.let { "${it.address}" } ?: ""
b.tvWeight.text = scale?.let { "${it.weight} g" } ?: ""
b.tvState.text = stateStr(scale?.state)
}
}
// ── 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)
fun update(newList: List<ScaleData?>) {
val filled = newList.map { it ?: empty }
if (items.isEmpty()) {
submitList(filled)
} else {
filled.forEachIndexed { i, scale ->
if (items.getOrNull(i) != scale) 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)
return VH(b)
}
@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)
fun update(newList: List<ScaleData?>) {
val filled = newList.map { it ?: empty }
if (items.isEmpty()) {
submitList(filled)
} else {
filled.forEachIndexed { i, scale ->
if (items.getOrNull(i) != scale) 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)
return VH(b)
}
@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 -> "超量"
null -> ""
else -> "$state"
}
/** 创建正方形秤格子(用于 Scale2VH) */
@SuppressLint("SetTextI18n")
private fun makeScaleCell(scale: ScaleData, cellSize: Int): LinearLayout {
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 scaleByAddress = scales.associateBy { it.address }
val sorted = SCALE_ORDER_22.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) { colIdx ->
val col = LinearLayout(container.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.5f)
}
repeat(2) { rowIdx ->
val cell = makeScaleCellFixed(sorted.getOrNull(index++), largeCellSize, largeCellSize, margin)
// 两列中间水平间距翻倍(第1列右边距、第2列左边距),列内两格子中间垂直间距翻倍,外侧保持不变
cell.updateLayoutParams<LinearLayout.LayoutParams> {
bottomMargin = if (rowIdx == 0) margin * 2 else margin
topMargin = if (rowIdx == 1) margin * 2 else margin
rightMargin = if (colIdx == 0) margin * 2 else margin
leftMargin = if (colIdx == 1) margin * 2 else margin
}
col.addView(cell)
}
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
layoutParams = LinearLayout.LayoutParams(0, cellSize - margin * 2, 1f)
.also { it.setMargins(margin, margin, margin, margin) }
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 = (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))
}
val baseSize = ((cellSize - margin * 2) / resources.displayMetrics.density).toInt()
addView(makeCellText("${scale.address}", (baseSize * 0.16f).coerceIn(9f, 14f).toInt()))
addView(makeCellText("${scale.weight}g", (baseSize * 0.22f).coerceIn(12f, 20f).toInt(), bold = true))
addView(makeCellText(stateStr(scale.state), (baseSize * 0.14f).coerceIn(8f, 12f).toInt()))
}
}
/**
* 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)
// 第2、3行列间距 10dpmargin=5dp),单独计算格子尺寸
val cellSize23 = (resources.displayMetrics.widthPixels - 32.dp - 6 * 10.dp) / 6
// 按指定顺序重排(address 从1开始,转为0-based index取值)
val scaleByAddress = scales.associateBy { it.address }
val sorted = SCALE_ORDER_18.map { scaleByAddress[it] }
var index = 0
// 第1行:增大 margin 使格子看起来更小
val row1 = makeRowLayout(container)
repeat(6) { row1.addView(makeScaleCell(sorted.getOrNull(index++), cellSize, inHorizontalRow = true, margin = 12.dp)) }
container.addView(row1)
repeat(2) { rowIdx ->
val row = makeRowLayout(container)
// 第2行(rowIdx=0)底部加 6dp,使第2、3行间距与第1、2行一致(均为10dp)
if (rowIdx == 0) row.updateLayoutParams<LinearLayout.LayoutParams> { bottomMargin = 6.dp }
repeat(6) { row.addView(makeScaleCell(sorted.getOrNull(index++), cellSize23, inHorizontalRow = true, margin = 5.dp)) }
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
gravity = Gravity.CENTER_VERTICAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 120.dp
).also { it.setMargins(2.dp, 4.dp, 2.dp, 4.dp) }
setPadding(30.dp, 0, 0, 0)
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 =
private fun makeCellText(text: String, spSize: Int, bold: Boolean = false): TextView =
TextView(this).apply {
this.text = text
textSize = spSize.toFloat()
@@ -384,15 +395,4 @@ class MasterScaleActivity : BaseActivity() {
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,142 @@
package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
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.base.BaseActivity
import com.shuwei.dish.match.databinding.ActivityScale22GridBinding
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
import com.shuwei.dish.match.scale.ScaleData
import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.dp
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
/** 22格设备的 deviceId */
private const val DEVICE_ID_22 = "a46fa55c-113c-3511-bb1f-41e5eff77c4b"
/**
* 列优先顺序:大列1(20,22) → 大列2(19,21) → 小列1~6
* FlexboxLayoutManager COLUMN 方向按列填充,顺序天然对应
*/
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)
/** 前4个 item(大列1+大列2,各2行)为大格子 */
private fun isLargeCell(position: Int) = position < 4
/**
* 22格秤混合网格布局页
* FlexboxLayoutManager(COLUMN) 竖向排列:
* - 大格子 flexBasisPercent=1.5/9,每列2个,自动换列
* - 小格子 flexBasisPercent=1.0/9,每列3个,自动换列
*/
class Scale22GridActivity : BaseActivity() {
private lateinit var binding: ActivityScale22GridBinding
private val scaleList = mutableListOf<ScaleData?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityScale22GridBinding.inflate(layoutInflater)
setContentView(binding.root)
setHeaderBackground()
binding.ivBack.setOnClickListener { finish() }
// 格子尺寸计算:
// 屏幕宽 - 8列×左右margin(2dp×2) = 可用宽,共9份(大1.5+小1.0×6)
val screenWidth = resources.displayMetrics.widthPixels
val usable = screenWidth - 8 * 4.dp - 4.dp // 减去8列格子margin + RecyclerView左右margin各2dp
val smallSize = usable / 9
val largeSize = (usable - smallSize * 6) / 2 // 剩余宽度平分给2列大格子
// 高度以小格子3行为准(3行×格子高 + 3行×上下margin各2dp
val rvHeight = smallSize * 3 + 3 * 4.dp
binding.rvScale22.layoutParams = binding.rvScale22.layoutParams.also {
it.height = rvHeight
}
binding.rvScale22.layoutManager = FlexboxLayoutManager(this).apply {
flexDirection = FlexDirection.COLUMN
flexWrap = FlexWrap.WRAP
alignItems = AlignItems.FLEX_START
}
binding.rvScale22.itemAnimator = null
binding.rvScale22.adapter = Scale22Adapter(scaleList, largeSize, smallSize)
observeScaleData()
}
private fun observeScaleData() {
val flow = ScaleServiceManager.allScales ?: return
lifecycleScope.launch {
flow.collectLatest { scaleMap ->
val scaleByAddress = scaleMap.values
.filter { it.deviceId == DEVICE_ID_22 }
.associateBy { it.address }
val newList = SCALE_ORDER_22.map { scaleByAddress[it] }
if (scaleList.isEmpty()) {
scaleList.addAll(newList)
binding.rvScale22.adapter?.notifyItemRangeInserted(0, scaleList.size)
} else {
newList.forEachIndexed { i, scale ->
if (scaleList[i] != scale) {
scaleList[i] = scale
binding.rvScale22.adapter?.notifyItemChanged(i)
}
}
}
}
}
}
private inner class Scale22Adapter(
private val data: List<ScaleData?>,
private val largeSize: Int, // 大格子宽高(px
private val smallSize: Int // 小格子宽高(px
) : RecyclerView.Adapter<Scale22Adapter.VH>() {
inner class VH(val b: ListItemScale22RowBinding) : RecyclerView.ViewHolder(b.root)
override fun getItemViewType(position: Int) = if (isLargeCell(position)) 1 else 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val cellSize = if (viewType == 1) largeSize else smallSize
// 固定宽高实现正方形,FlexboxLayoutManager COLUMN 方向按高度装满后自动换列
(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)
return VH(b)
}
override fun getItemCount() = data.size
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: VH, position: Int) {
val scale = data[position]
holder.b.tvAddr.text = scale?.let { "${it.address}" } ?: ""
holder.b.tvWeight.text = scale?.let { "${it.weight}g" } ?: ""
holder.b.tvState.text = when (scale?.state) {
WeightUtil.STATE_STABLE -> "稳定"
WeightUtil.STATE_UNSTABLE -> "不稳定"
WeightUtil.STATE_OVER_WEIGHT -> "超量"
null -> ""
else -> "${scale.state}"
}
}
}
}
@@ -53,10 +53,12 @@
android:id="@+id/rvScaleList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/tvConnectionStatus"
app:layout_constraintBottom_toBottomOf="parent"
android:overScrollMode="never"
android:padding="0dp"
tools:listitem="@layout/list_item_scale_data"/>
tools:listitem="@layout/list_type_scale22"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="@color/bg_color">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="22格秤数据"
android:textColor="@color/home_title"
android:textSize="36sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/ivBack"
android:layout_width="70dp"
android:layout_height="60dp"
android:paddingStart="30dp"
android:paddingEnd="10dp"
android:src="@drawable/ic_back"
app:layout_constraintBottom_toBottomOf="@id/tvTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvTitle"
tools:ignore="ContentDescription" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvScale22"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvTitle"
tools:listitem="@layout/list_item_scale22_row" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 单个秤格子:地址 + 重量 + 状态 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="4dp"
android:background="@drawable/shape_scale_cell">
<TextView
android:id="@+id/tvAddr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:includeFontPadding="false"
android:textColor="@color/home_sub_title" />
<TextView
android:id="@+id/tvWeight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:includeFontPadding="false"
android:textColor="@color/home_title"
android:textStyle="bold" />
<TextView
android:id="@+id/tvState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:includeFontPadding="false"
android:textColor="@color/home_sub_title" />
</LinearLayout>
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 1个秤设备的卡片布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp">
<TextView
android:id="@+id/tvDeviceIp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
android:includeFontPadding="false"
android:textColor="@color/home_title"
tools:text="IP192.168.1.100" />
<TextView
android:id="@+id/tvDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/home_sub_title"
android:textSize="14sp"
tools:text="设备:device_01" />
<!-- 单个秤数据行 -->
<LinearLayout
android:id="@+id/llScaleContainer"
android:layout_width="match_parent"
android:layout_height="120dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="5dp"
android:layout_marginBottom="2dp"
android:paddingStart="30dp"
android:background="@drawable/shape_scale_cell">
<TextView
android:id="@+id/tvAddr"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="22sp"
android:textColor="@color/home_sub_title"
tools:text="秤 1" />
<TextView
android:id="@+id/tvWeight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:textSize="26sp"
android:textStyle="bold"
android:textColor="@color/home_title"
tools:text="100.0 g" />
<TextView
android:id="@+id/tvState"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18sp"
android:textColor="@color/home_sub_title"
tools:text="稳定" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_eb"
android:layout_marginTop="10dp" />
</LinearLayout>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 18个秤设备的卡片布局,内嵌 RecyclerView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp">
<TextView
android:id="@+id/tvDeviceIp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
android:includeFontPadding="false"
android:textColor="@color/home_title"
tools:text="IP192.168.1.100" />
<TextView
android:id="@+id/tvDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/home_sub_title"
android:textSize="14sp"
tools:text="设备:device_01" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvScales"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:overScrollMode="never" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_eb"
android:layout_marginTop="10dp" />
</LinearLayout>
@@ -1,15 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 设备卡片:竖向排列,显示设备号、IP、以及该设备下所有秤数据 -->
<!-- 2个秤设备卡片布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="10dp">
<!-- 设备 IP -->
<TextView
android:id="@+id/tvDeviceIp"
android:layout_width="wrap_content"
@@ -20,7 +17,6 @@
android:textColor="@color/home_title"
tools:text="IP192.168.1.100" />
<!-- 设备号 -->
<TextView
android:id="@+id/tvDeviceLabel"
android:layout_width="wrap_content"
@@ -29,18 +25,18 @@
android:textSize="14sp"
tools:text="设备:device_01" />
<!-- 该设备下所有秤数据行,由代码动态填充 -->
<!-- 水平排列2个秤格子,高度固定 400dp -->
<LinearLayout
android:id="@+id/llScaleContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_height="400dp"
android:orientation="horizontal"
android:layout_marginTop="5dp" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_eb"
android:layout_marginTop="10dp"/>
android:layout_marginTop="10dp" />
</LinearLayout>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 22个秤设备的卡片布局,内嵌 RecyclerView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp">
<TextView
android:id="@+id/tvDeviceIp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
android:includeFontPadding="false"
android:textColor="@color/home_title"
tools:text="IP192.168.1.100" />
<TextView
android:id="@+id/tvDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/home_sub_title"
android:textSize="14sp"
tools:text="设备:device_01" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvScales"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:overScrollMode="never" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_eb"
android:layout_marginTop="10dp" />
</LinearLayout>
+2
View File
@@ -31,6 +31,7 @@ refreshHeaderClassics = "3.0.0-alpha"
refreshHeaderRadar = "3.0.0-alpha"
roomRuntime = "2.5.2"
java-websocket = "1.5.7"
flexbox = "3.0.0"
[libraries]
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
@@ -77,6 +78,7 @@ refresh-header-classics = { module = "io.github.scwang90:refresh-header-classics
refresh-layout-kernel = { module = "io.github.scwang90:refresh-layout-kernel", version.ref = "refreshLayoutKernel" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" }
java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "java-websocket" }
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }