@@ -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 )
}
}