界面: 歌曲列表行高自适应铺满屏幕(消除投影/高分屏底部空白)

- SongListAdapter: 新增 rowHeightPx/fitRowHeight/rowsPerPage, 分页列表(2列目录5行/排行6行)
  按 ListView 实际可视高度均分行高(clamp 52-160dp), 内容垂直居中, 背景整行铺满
- 滚动型列表(已点/下载/已唱)保持自然行高
- MainActivity.renderSongList: 布局完成后 post fitRowHeight
This commit is contained in:
zijunchiang
2026-09-05 00:41:00 +08:00
parent 2d3751064f
commit 303e86b475
2 changed files with 58 additions and 17 deletions
@@ -9091,6 +9091,13 @@ class MainActivity : AppCompatActivity() {
} }
val pageOffset = if (mode == "rank") browsePage * browsePageSize() else 0 val pageOffset = if (mode == "rank") browsePage * browsePageSize() else 0
val existing = modernSongAdapter?.takeIf { it.matches(mode, pageOffset) && list.adapter === it } val existing = modernSongAdapter?.takeIf { it.matches(mode, pageOffset) && list.adapter === it }
// 分页网格/排行列表: 行高自适应铺满屏幕(2列目录每页5行, 排行单列每页6行);
// 可滚动的队列/下载/已唱列表保持自然行高(rowsPerPage=0)。
val rowsPerPage = when (mode) {
"catalog" -> 5
"rank" -> 6
else -> 0
}
val listAdapter = existing ?: SongListAdapter( val listAdapter = existing ?: SongListAdapter(
this, this,
visibleSongs, visibleSongs,
@@ -9109,11 +9116,17 @@ class MainActivity : AppCompatActivity() {
}, },
onPauseResume = { song -> toggleDownloadPause(song) }, onPauseResume = { song -> toggleDownloadPause(song) },
), ),
initialRowHeightPx = 0,
rowsPerPage = rowsPerPage,
).also { ).also {
modernSongAdapter = it modernSongAdapter = it
list.adapter = it list.adapter = it
} }
if (existing != null) listAdapter.notifyDataSetChanged() if (existing != null) listAdapter.notifyDataSetChanged()
// 布局完成后按实际可视高度拉满各行, 消除投影/高分屏底部空白。
if (rowsPerPage > 0) {
list.post { if (list.adapter === listAdapter) listAdapter.fitRowHeight(list.height) }
}
val targetPosition = focusedMarker?.let(listAdapter::adapterPositionFor) val targetPosition = focusedMarker?.let(listAdapter::adapterPositionFor)
?: oldSelectedPosition.takeIf { it >= 0 } ?: oldSelectedPosition.takeIf { it >= 0 }
@@ -42,11 +42,32 @@ class SongListAdapter(
private val nextPageFocusId: Int, private val nextPageFocusId: Int,
private val stateFor: (Song) -> SongRowState, private val stateFor: (Song) -> SongRowState,
private val callbacks: SongListCallbacks, private val callbacks: SongListCallbacks,
private val initialRowHeightPx: Int = 0,
private val rowsPerPage: Int = 0,
) : BaseAdapter() { ) : BaseAdapter() {
private val density = context.resources.displayMetrics.density private val density = context.resources.displayMetrics.density
private val columns = if (mode in SINGLE_COLUMN_MODES) 1 else 2 private val columns = if (mode in SINGLE_COLUMN_MODES) 1 else 2
private val cardIds = HashMap<String, Int>() private val cardIds = HashMap<String, Int>()
/** 当前行高(px)。0 = 保持内容自然高度(滚动列表用)。 */
var rowHeightPx: Int = initialRowHeightPx
private set
/**
* 让行高铺满可用高度: 把列表可视高度按每页行数均分(分页列表专用)。
* 超出 min/max 范围时收敛, 避免过小/过大。滚动列表(rowsPerPage<=0)忽略。
*/
fun fitRowHeight(availableHeightPx: Int) {
if (rowsPerPage <= 0 || availableHeightPx <= 0) return
val minPx = dp(52)
val maxPx = dp(160)
val fitted = (availableHeightPx / rowsPerPage).coerceIn(minPx, maxPx)
if (fitted != rowHeightPx) {
rowHeightPx = fitted
notifyDataSetChanged()
}
}
private fun cardId(index: Int): Int = cardIds.getOrPut(songKey(songs[index])) { View.generateViewId() } private fun cardId(index: Int): Int = cardIds.getOrPut(songKey(songs[index])) { View.generateViewId() }
override fun getCount(): Int = ceil(songs.size / columns.toDouble()).toInt() override fun getCount(): Int = ceil(songs.size / columns.toDouble()).toInt()
@@ -64,28 +85,35 @@ class SongListAdapter(
} }
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
if (columns == 1) { val view = if (columns == 1) {
return createSingleRow(songs[position], position).apply { createSingleRow(songs[position], position).apply {
if (position == count - 1) nextFocusDownId = previousPageFocusId if (position == count - 1) nextFocusDownId = previousPageFocusId
} }
} } else {
return LinearLayout(context).apply { LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL gravity = Gravity.CENTER_VERTICAL
val cells = mutableListOf<View>()
repeat(2) { column -> repeat(2) { column ->
val index = position * 2 + column val index = position * 2 + column
val cell = songs.getOrNull(index)?.let { val cell = songs.getOrNull(index)?.let {
createCatalogCell(it, index, column, position == count - 1) createCatalogCell(it, index, column, position == count - 1)
} ?: View(context) } ?: View(context)
cells += cell
addView( addView(
cell, cell,
LinearLayout.LayoutParams(0, dp(60), 1f), LinearLayout.LayoutParams(0, -1, 1f),
) )
} }
} }
} }
// 分页列表: 让整行背景铺满均分行高; 滚动列表保持自然高度。
if (rowHeightPx > 0) {
view.layoutParams = android.widget.AbsListView.LayoutParams(
android.widget.AbsListView.LayoutParams.MATCH_PARENT,
rowHeightPx,
)
}
return view
}
private fun createSingleRow(song: Song, index: Int): View = when (mode) { private fun createSingleRow(song: Song, index: Int): View = when (mode) {
"rank" -> createRankRow(song, index) "rank" -> createRankRow(song, index)