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:
@@ -107,6 +107,7 @@ dependencies {
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.java.websocket)
|
||||
implementation(libs.converter.gson)
|
||||
implementation(libs.flexbox)
|
||||
|
||||
implementation(libs.baserecyclerviewadapterhelper4)
|
||||
|
||||
|
||||
@@ -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
|
||||
// 减去左右 padding(16dp * 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.allScales(StateFlow)
|
||||
* 主设备秤数据总览页面
|
||||
* 按设备分组展示,每种设备对应独立的 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行列间距 10dp(margin=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="IP:192.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="IP:192.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>
|
||||
+5
-9
@@ -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="IP:192.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="IP:192.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>
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user