V1.3.7: 调音弹窗文字自适应 —— 修"文字显示不全"

问题: 上一版按分辨率压缩时把「文字行」也一起压扁了。label() 自带 6dp 上下内边距,
行高必须 >= 文本行高+12dp, 而紧凑模式给了 18dp(频段刻度)/28dp(音量标题)/42dp(标题),
sp 文本(尤其系统字体放大时)直接被裁掉 —— 这就是"文字显示不全"的根因。

修复: 改成「文字行 wrap_content + 只有无文字区域才用计算高度」
- 标题行/标题文字、音量标题、EQ 开关、预设行与预设按钮、频段数值与频率刻度
  全部改为 wrap_content, 高度由文本自身决定, 任何字体缩放下都不会被裁。
- 频段刻度新增 caption(): 无内边距 + includeFontPadding=false, 12sp 完整显示。
- 预设按钮改 includeFontPadding=false + 等距 padding, 不再被固定 46dp 压扁。
- 滑杆用自身固有高度(-2), SeekBar 无文字不会被裁。
- 两遍 fit: afterShow 里先 panel.measure() 实测内容高度, 若超出「屏幕高*0.92 - 框架」
  就把 EQ 频段区减掉(下限 dp(96)), 再调 fitDialogToScreen 收敛窗口 ——
  收缩的永远是频段区而不是文字; 仍放不下才滚动。
  新增 tuning dialog shrink band 日志便于现场核对。

版本号 1.3.6 -> 1.3.7(versionCode 12 -> 13)。按约定本次不发布 Release, 仅本地打包测试。
This commit is contained in:
zijunchiang
2026-09-20 16:11:33 +08:00
parent 0c7aa46458
commit 7e3d233891
3 changed files with 66 additions and 33 deletions
+7 -2
View File
@@ -108,7 +108,7 @@ $BT/apksigner verify 目标名.apk
### 版本号 ### 版本号
- `app/build.gradle`:`versionName "1.3.6"` / `versionCode 12` - `app/build.gradle`:`versionName "1.3.7"` / `versionCode 13`
- App 内显示统一用纯版本号(不加 `(versionCode)` 括号) - App 内显示统一用纯版本号(不加 `(versionCode)` 括号)
## 三、关键实现约束(避免踩坑) ## 三、关键实现约束(避免踩坑)
@@ -185,5 +185,10 @@ $BT/apksigner verify 目标名.apk
ScrollView 限制为 `屏幕高*0.92 - 弹窗标题/按钮框架(chromeDp)` —— **不要再用 `WRAP_CONTENT` 作为 ScrollView 限制为 `屏幕高*0.92 - 弹窗标题/按钮框架(chromeDp)` —— **不要再用 `WRAP_CONTENT` 作为
弹窗高度**。内容高度由「屏幕预算 - 固定区高度」反推(调音弹窗的 EQ 频段区就是这样自适应), 弹窗高度**。内容高度由「屏幕预算 - 固定区高度」反推(调音弹窗的 EQ 频段区就是这样自适应),
预算不足时弹窗内滚动而不是被裁掉。已应用:调音弹窗、扫码点歌弹窗、声音设置弹窗。 预算不足时弹窗内滚动而不是被裁掉。已应用:调音弹窗、扫码点歌弹窗、声音设置弹窗。
新增/改动弹窗时请复用该 helper,别再写死 `setLayout(w, WRAP_CONTENT)` 新增/改动弹窗时请复用该 helper,别再写死 `setLayout(w, WRAP_CONTENT)`。
⚠️ **文字行必须 wrap_content**:`label()` 自带 6dp 上下内边距,固定矮行(曾用 18/28/42dp)
会把文字裁掉 —— 这是"文字显示不全"的根因。做法是「文字行 wrap_content + 只有无文字区域
才用计算高度」:调音弹窗里唯一可伸缩的是 EQ 频段区,采用**两遍 fit**(先 `panel.measure()`
实测内容高度,超出预算就把频段区减掉,再调 `fitDialogToScreen`),这样收缩的永远是频段区
而不是文字;频段区下限 dp(96),再放不下才滚动。大字体(系统字体缩放)同理自动兜住
- **仓库可见性**:Gitea 仓库保持 **public**(若后续把 App 更新/热更新端点迁到 Gitea,非 public 会导致 403) - **仓库可见性**:Gitea 仓库保持 **public**(若后续把 App 更新/热更新端点迁到 Gitea,非 public 会导致 403)
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId "com.local.ktv" applicationId "com.local.ktv"
minSdk 21 minSdk 21
targetSdk 25 targetSdk 25
versionCode 12 versionCode 13
versionName "1.3.6" versionName "1.3.7"
ndk { ndk {
// 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版 // 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版
// 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则 // 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则
+57 -29
View File
@@ -2556,28 +2556,22 @@ class MainActivity : AppCompatActivity() {
val working = KtvEqualizer.levelsSnapshot().let { saved -> val working = KtvEqualizer.levelsSnapshot().let { saved ->
FloatArray(caps.bandCount) { saved.getOrElse(it) { 0f } } FloatArray(caps.bandCount) { saved.getOrElse(it) { 0f } }
} }
// 分辨率自适应:按屏幕高度算出「弹窗内容能占多少」,把剩余空间留给 EQ 频段区, // 分辨率自适应:**文字行全部 wrap_content**(高度由文本决定,永不被压扁/裁字),
// 矮屏自动压缩频段区高度(而不是把弹窗顶出屏幕),再叠加 fitDialogToScreen 的 // 只有 EQ 频段区是可伸缩的 —— 屏幕放不下时收缩的是频段区,不是文字。
// 高度上限兜底 —— 任何分辨率下都不会被裁切。
val metrics = resources.displayMetrics val metrics = resources.displayMetrics
val screenHeightDp = metrics.heightPixels / metrics.density val screenHeightDp = metrics.heightPixels / metrics.density
// 矮屏(高度<620dp)只用来收窄竖杆列宽;文字尺寸不随分辨率变化。
val compact = screenHeightDp < 620f val compact = screenHeightDp < 620f
val chromeDp = if (compact) 132 else 152 val chromeDp = if (compact) 132 else 152
val contentBudgetDp = (screenHeightDp * 0.92f - chromeDp).coerceAtLeast(240f) val contentBudgetDp = (screenHeightDp * 0.92f - chromeDp).coerceAtLeast(240f)
// 固定区:面板内边距 30 + 标题 42 + 音量标题 28 + 音量杆 + 开关(含间距) 50 + 预设(含间距) // 文字/控件行占位的保守估计(内边距 + 标题 + 音量标题 + 开关 + 预设),最终以实测量为准。
val fixedHeightDp = 30f + 42f + 28f + val textRowsDp = 30f + 34f + 30f + 50f + 58f
(if (compact) 38 else 44) + 50f + var bandAreaHeight = dp((contentBudgetDp - textRowsDp).coerceIn(112f, 216f).toInt())
(if (compact) 44 else 52) + 10f
val bandAreaDp = (contentBudgetDp - fixedHeightDp).coerceIn(112f, 216f)
val bandAreaHeight = dp(bandAreaDp.toInt())
val barColumnWidth = dp(if (compact) 38 else 46) val barColumnWidth = dp(if (compact) 38 else 46)
val barCaptionHeight = dp(if (compact) 18 else 20)
val volumeRowHeight = dp(if (compact) 38 else 44)
val presetRowHeight = dp(if (compact) 44 else 52)
Log.i( Log.i(
TAG, TAG,
"tuning dialog adaptive: screen=${screenHeightDp}dp compact=$compact " + "tuning dialog adaptive: screen=${screenHeightDp}dp compact=$compact " +
"budget=${contentBudgetDp}dp bandArea=${bandAreaDp}dp", "budget=${contentBudgetDp}dp band=${bandAreaHeight}px",
) )
val panel = LinearLayout(this).apply { val panel = LinearLayout(this).apply {
@@ -2603,8 +2597,11 @@ class MainActivity : AppCompatActivity() {
// ── 标题 ── // ── 标题 ──
val titleRow = FrameLayout(this) val titleRow = FrameLayout(this)
titleRow.addView( titleRow.addView(
label("调音", 23, Color.WHITE).apply { gravity = Gravity.CENTER }, label("调音", 23, Color.WHITE).apply {
FrameLayout.LayoutParams(-1, dp(42)), gravity = Gravity.CENTER
includeFontPadding = false
},
FrameLayout.LayoutParams(-1, -2),
) )
val close = label("×", 22, Color.argb(153, 255, 255, 255)).apply { val close = label("×", 22, Color.argb(153, 255, 255, 255)).apply {
gravity = Gravity.CENTER gravity = Gravity.CENTER
@@ -2616,11 +2613,11 @@ class MainActivity : AppCompatActivity() {
close, close,
FrameLayout.LayoutParams(dp(44), dp(38), Gravity.END or Gravity.CENTER_VERTICAL), FrameLayout.LayoutParams(dp(44), dp(38), Gravity.END or Gravity.CENTER_VERTICAL),
) )
panel.addView(titleRow, LinearLayout.LayoutParams(-1, dp(42))) panel.addView(titleRow, LinearLayout.LayoutParams(-1, -2))
// ── 1. 音量:横向控制杆(即时生效)── // ── 1. 音量:横向控制杆(即时生效)──
val volumeTitle = label("音量 $musicVolume%", 16, Color.WHITE) val volumeTitle = label("音量 $musicVolume%", 16, Color.WHITE)
panel.addView(volumeTitle, LinearLayout.LayoutParams(-1, dp(28))) panel.addView(volumeTitle, LinearLayout.LayoutParams(-1, -2))
var lastVolumeAppliedAt = 0L var lastVolumeAppliedAt = 0L
val volumeBar = SeekBar(this).apply { val volumeBar = SeekBar(this).apply {
max = 100 max = 100
@@ -2649,7 +2646,7 @@ class MainActivity : AppCompatActivity() {
} }
}) })
} }
panel.addView(volumeBar, LinearLayout.LayoutParams(-1, volumeRowHeight)) panel.addView(volumeBar, LinearLayout.LayoutParams(-1, -2))
// ── 2. 均衡器开关(默认关闭)── // ── 2. 均衡器开关(默认关闭)──
lateinit var eqSwitch: TextView lateinit var eqSwitch: TextView
@@ -2666,7 +2663,7 @@ class MainActivity : AppCompatActivity() {
applyEqualizerSettings() applyEqualizerSettings()
toast(if (KtvEqualizer.enabled) "均衡器已开启" else "均衡器已关闭") toast(if (KtvEqualizer.enabled) "均衡器已开启" else "均衡器已关闭")
} }
panel.addView(eqSwitch, LinearLayout.LayoutParams(dp(190), dp(42)).apply { topMargin = dp(8) }) panel.addView(eqSwitch, LinearLayout.LayoutParams(dp(200), -2).apply { topMargin = dp(8) })
eqArea = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL } eqArea = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL }
panel.addView(eqArea, LinearLayout.LayoutParams(-1, -2)) panel.addView(eqArea, LinearLayout.LayoutParams(-1, -2))
@@ -2684,9 +2681,18 @@ class MainActivity : AppCompatActivity() {
orientation = LinearLayout.HORIZONTAL orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER gravity = Gravity.CENTER
} }
eqArea.addView(presetRow, LinearLayout.LayoutParams(-1, presetRowHeight).apply { topMargin = dp(10) }) eqArea.addView(presetRow, LinearLayout.LayoutParams(-1, -2).apply { topMargin = dp(10) })
// ── 4. EQ 频段:竖向控制杆 ── // ── 4. EQ 频段:竖向控制杆 ──
// 刻度文字用无内边距的 caption:高度由文本自身决定, 不会再被固定 dp 裁掉。
fun caption(text: String, sp: Int, color: Int): TextView = TextView(this).apply {
this.text = text
textSize = sp.toFloat()
setTextColor(color)
gravity = Gravity.CENTER
includeFontPadding = false
setPadding(0, 0, 0, 0)
}
val bars = ArrayList<VerticalEqBar>(caps.bandCount) val bars = ArrayList<VerticalEqBar>(caps.bandCount)
val valueViews = ArrayList<TextView>(caps.bandCount) val valueViews = ArrayList<TextView>(caps.bandCount)
fun syncBars() { fun syncBars() {
@@ -2707,13 +2713,13 @@ class MainActivity : AppCompatActivity() {
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_HORIZONTAL gravity = Gravity.CENTER_HORIZONTAL
} }
val value = label( val value = caption(
eqLevelText(working.getOrElse(index) { 0f }), eqLevelText(working.getOrElse(index) { 0f }),
12, 12,
Color.argb(170, 255, 255, 255), Color.argb(170, 255, 255, 255),
).apply { gravity = Gravity.CENTER } )
valueViews.add(value) valueViews.add(value)
column.addView(value, LinearLayout.LayoutParams(-1, barCaptionHeight)) column.addView(value, LinearLayout.LayoutParams(-1, -2))
val bar = VerticalEqBar(this).apply { val bar = VerticalEqBar(this).apply {
setRange(caps.minDb, caps.maxDb, eqSliderSteps) setRange(caps.minDb, caps.maxDb, eqSliderSteps)
setLevel(working.getOrElse(index) { 0f }) setLevel(working.getOrElse(index) { 0f })
@@ -2735,10 +2741,8 @@ class MainActivity : AppCompatActivity() {
bars.add(bar) bars.add(bar)
column.addView(bar, LinearLayout.LayoutParams(barColumnWidth, 0, 1f)) column.addView(bar, LinearLayout.LayoutParams(barColumnWidth, 0, 1f))
column.addView( column.addView(
label(bandLabels.getOrElse(index) { "${index + 1}" } + "Hz", 12, Color.WHITE).apply { caption(bandLabels.getOrElse(index) { "${index + 1}" } + "Hz", 12, Color.WHITE),
gravity = Gravity.CENTER LinearLayout.LayoutParams(-1, -2),
},
LinearLayout.LayoutParams(-1, barCaptionHeight),
) )
barRow.addView(column, LinearLayout.LayoutParams(0, -1, 1f)) barRow.addView(column, LinearLayout.LayoutParams(0, -1, 1f))
} }
@@ -2752,6 +2756,8 @@ class MainActivity : AppCompatActivity() {
isClickable = true isClickable = true
isFocusable = true isFocusable = true
background = getDrawable(R.drawable.bg_tuning_counter) background = getDrawable(R.drawable.bg_tuning_counter)
includeFontPadding = false
setPadding(dp(6), dp(11), dp(6), dp(11))
TvFocusStyler.installAction(this) TvFocusStyler.installAction(this)
setOnClickListener { setOnClickListener {
if (name == KtvEqualizer.CUSTOM_PRESET) { if (name == KtvEqualizer.CUSTOM_PRESET) {
@@ -2768,7 +2774,7 @@ class MainActivity : AppCompatActivity() {
presetButtons[name] = presetButton presetButtons[name] = presetButton
presetRow.addView( presetRow.addView(
presetButton, presetButton,
LinearLayout.LayoutParams(0, dp(46), 1f).apply { LinearLayout.LayoutParams(0, -2, 1f).apply {
marginStart = dp(4) marginStart = dp(4)
marginEnd = dp(4) marginEnd = dp(4)
}, },
@@ -2787,7 +2793,29 @@ class MainActivity : AppCompatActivity() {
attributes = attributes.apply { dimAmount = 0.72f } attributes = attributes.apply { dimAmount = 0.72f }
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
} }
// 分辨率自适应:宽度上限 + 内容区高度上限(超出时弹窗内滚动,绝不裁切)。 // 两遍自适应:先按内容实测高度判断是否超出可用空间,超出就只压缩 EQ 频段区
// (文字行 wrap_content 保持不变),最后再收敛窗口尺寸。
val availableHeight = (metrics.heightPixels * 0.92f).toInt()
val maxContentHeight = (availableHeight - dp(chromeDp)).coerceAtLeast(dp(150))
val probeWidth = min(dp(760), (metrics.widthPixels * 0.94f).toInt())
panel.measure(
View.MeasureSpec.makeMeasureSpec(probeWidth, View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
val overflow = panel.measuredHeight - maxContentHeight
if (overflow > 0) {
val shrunk = (bandAreaHeight - overflow).coerceAtLeast(dp(96))
Log.i(
TAG,
"tuning dialog shrink band: measured=${panel.measuredHeight}px " +
"max=${maxContentHeight}px band=$bandAreaHeight->$shrunk",
)
if (shrunk < bandAreaHeight) {
bandAreaHeight = shrunk
barRow.layoutParams = barRow.layoutParams.apply { height = bandAreaHeight }
}
}
// 最终兜底:宽度上限 + 内容区高度上限(仍放不下时弹窗内滚动,绝不裁切)。
fitDialogToScreen( fitDialogToScreen(
dialog = dialog, dialog = dialog,
scroll = scroll, scroll = scroll,