From 7e3d233891144a48d7bec5183c1747fff76ff335 Mon Sep 17 00:00:00 2001 From: zijunchiang Date: Sun, 20 Sep 2026 16:11:33 +0800 Subject: [PATCH] =?UTF-8?q?V1.3.7:=20=E8=B0=83=E9=9F=B3=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=87=AA=E9=80=82=E5=BA=94=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=BF=AE"=E6=96=87=E5=AD=97=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=B8=8D=E5=85=A8"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: 上一版按分辨率压缩时把「文字行」也一起压扁了。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, 仅本地打包测试。 --- AGENTS.md | 9 +- app/build.gradle | 4 +- .../main/java/com/local/ktv/MainActivity.kt | 86 ++++++++++++------- 3 files changed, 66 insertions(+), 33 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 14c8c5c..fd77760 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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)` 括号) ## 三、关键实现约束(避免踩坑) @@ -185,5 +185,10 @@ $BT/apksigner verify 目标名.apk ScrollView 限制为 `屏幕高*0.92 - 弹窗标题/按钮框架(chromeDp)` —— **不要再用 `WRAP_CONTENT` 作为 弹窗高度**。内容高度由「屏幕预算 - 固定区高度」反推(调音弹窗的 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) diff --git a/app/build.gradle b/app/build.gradle index d9b95ba..7b6ac6a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.local.ktv" minSdk 21 targetSdk 25 - versionCode 12 - versionName "1.3.6" + versionCode 13 + versionName "1.3.7" ndk { // 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版 // 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则 diff --git a/app/src/main/java/com/local/ktv/MainActivity.kt b/app/src/main/java/com/local/ktv/MainActivity.kt index 16bf370..362b531 100644 --- a/app/src/main/java/com/local/ktv/MainActivity.kt +++ b/app/src/main/java/com/local/ktv/MainActivity.kt @@ -2556,28 +2556,22 @@ class MainActivity : AppCompatActivity() { val working = KtvEqualizer.levelsSnapshot().let { saved -> FloatArray(caps.bandCount) { saved.getOrElse(it) { 0f } } } - // 分辨率自适应:按屏幕高度算出「弹窗内容能占多少」,把剩余空间留给 EQ 频段区, - // 矮屏自动压缩频段区高度(而不是把弹窗顶出屏幕),再叠加 fitDialogToScreen 的 - // 高度上限兜底 —— 任何分辨率下都不会被裁切。 + // 分辨率自适应:**文字行全部 wrap_content**(高度由文本决定,永不被压扁/裁字), + // 只有 EQ 频段区是可伸缩的 —— 屏幕放不下时收缩的是频段区,不是文字。 val metrics = resources.displayMetrics val screenHeightDp = metrics.heightPixels / metrics.density + // 矮屏(高度<620dp)只用来收窄竖杆列宽;文字尺寸不随分辨率变化。 val compact = screenHeightDp < 620f val chromeDp = if (compact) 132 else 152 val contentBudgetDp = (screenHeightDp * 0.92f - chromeDp).coerceAtLeast(240f) - // 固定区:面板内边距 30 + 标题 42 + 音量标题 28 + 音量杆 + 开关(含间距) 50 + 预设(含间距) - val fixedHeightDp = 30f + 42f + 28f + - (if (compact) 38 else 44) + 50f + - (if (compact) 44 else 52) + 10f - val bandAreaDp = (contentBudgetDp - fixedHeightDp).coerceIn(112f, 216f) - val bandAreaHeight = dp(bandAreaDp.toInt()) + // 文字/控件行占位的保守估计(内边距 + 标题 + 音量标题 + 开关 + 预设),最终以实测量为准。 + val textRowsDp = 30f + 34f + 30f + 50f + 58f + var bandAreaHeight = dp((contentBudgetDp - textRowsDp).coerceIn(112f, 216f).toInt()) 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( TAG, "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 { @@ -2603,8 +2597,11 @@ class MainActivity : AppCompatActivity() { // ── 标题 ── val titleRow = FrameLayout(this) titleRow.addView( - label("调音", 23, Color.WHITE).apply { gravity = Gravity.CENTER }, - FrameLayout.LayoutParams(-1, dp(42)), + label("调音", 23, Color.WHITE).apply { + gravity = Gravity.CENTER + includeFontPadding = false + }, + FrameLayout.LayoutParams(-1, -2), ) val close = label("×", 22, Color.argb(153, 255, 255, 255)).apply { gravity = Gravity.CENTER @@ -2616,11 +2613,11 @@ class MainActivity : AppCompatActivity() { close, 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. 音量:横向控制杆(即时生效)── 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 val volumeBar = SeekBar(this).apply { max = 100 @@ -2649,7 +2646,7 @@ class MainActivity : AppCompatActivity() { } }) } - panel.addView(volumeBar, LinearLayout.LayoutParams(-1, volumeRowHeight)) + panel.addView(volumeBar, LinearLayout.LayoutParams(-1, -2)) // ── 2. 均衡器开关(默认关闭)── lateinit var eqSwitch: TextView @@ -2666,7 +2663,7 @@ class MainActivity : AppCompatActivity() { applyEqualizerSettings() 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 } panel.addView(eqArea, LinearLayout.LayoutParams(-1, -2)) @@ -2684,9 +2681,18 @@ class MainActivity : AppCompatActivity() { orientation = LinearLayout.HORIZONTAL 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 频段:竖向控制杆 ── + // 刻度文字用无内边距的 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(caps.bandCount) val valueViews = ArrayList(caps.bandCount) fun syncBars() { @@ -2707,13 +2713,13 @@ class MainActivity : AppCompatActivity() { orientation = LinearLayout.VERTICAL gravity = Gravity.CENTER_HORIZONTAL } - val value = label( + val value = caption( eqLevelText(working.getOrElse(index) { 0f }), 12, Color.argb(170, 255, 255, 255), - ).apply { gravity = Gravity.CENTER } + ) valueViews.add(value) - column.addView(value, LinearLayout.LayoutParams(-1, barCaptionHeight)) + column.addView(value, LinearLayout.LayoutParams(-1, -2)) val bar = VerticalEqBar(this).apply { setRange(caps.minDb, caps.maxDb, eqSliderSteps) setLevel(working.getOrElse(index) { 0f }) @@ -2735,10 +2741,8 @@ class MainActivity : AppCompatActivity() { bars.add(bar) column.addView(bar, LinearLayout.LayoutParams(barColumnWidth, 0, 1f)) column.addView( - label(bandLabels.getOrElse(index) { "${index + 1}" } + "Hz", 12, Color.WHITE).apply { - gravity = Gravity.CENTER - }, - LinearLayout.LayoutParams(-1, barCaptionHeight), + caption(bandLabels.getOrElse(index) { "${index + 1}" } + "Hz", 12, Color.WHITE), + LinearLayout.LayoutParams(-1, -2), ) barRow.addView(column, LinearLayout.LayoutParams(0, -1, 1f)) } @@ -2752,6 +2756,8 @@ class MainActivity : AppCompatActivity() { isClickable = true isFocusable = true background = getDrawable(R.drawable.bg_tuning_counter) + includeFontPadding = false + setPadding(dp(6), dp(11), dp(6), dp(11)) TvFocusStyler.installAction(this) setOnClickListener { if (name == KtvEqualizer.CUSTOM_PRESET) { @@ -2768,7 +2774,7 @@ class MainActivity : AppCompatActivity() { presetButtons[name] = presetButton presetRow.addView( presetButton, - LinearLayout.LayoutParams(0, dp(46), 1f).apply { + LinearLayout.LayoutParams(0, -2, 1f).apply { marginStart = dp(4) marginEnd = dp(4) }, @@ -2787,7 +2793,29 @@ class MainActivity : AppCompatActivity() { attributes = attributes.apply { dimAmount = 0.72f } 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( dialog = dialog, scroll = scroll,