feat(ui): 小窗/分屏自适应播放页

按可用宽高比判断紧凑模式(全屏 3192/1368≈2.33,小窗 2577/1368≈1.88):
- 封面由撑满宽度改为固定方形(可用高度的 30%,上限 340dp)
- 标题 24→20、艺术家 16→14,各段间距收紧
- 歌词页不显示(PageView 两页:信息 / 主页),指示器同步改两段
- 紧凑模式下整页可滚动,避免控件被挤出屏幕

不依赖"记住分辨率":直接读当前 MediaQuery,窗口尺寸变化时自动切换。
This commit is contained in:
2026-11-01 12:06:12 +08:00
parent 432e3b1260
commit 6cb96e95dc
+73 -41
View File
@@ -194,7 +194,7 @@ class _PlayerPageState extends BaseState<PlayerPage> {
onPressed: _closePage,
),
centerTitle: true,
title: _buildPageIndicator(),
title: _buildPageIndicator(_isCompact),
actions: [
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Colors.white54),
@@ -257,17 +257,24 @@ class _PlayerPageState extends BaseState<PlayerPage> {
onPageChanged: (index) => setState(() => _pageIndex = index),
children: [
_buildInfoPage(song),
_buildMainPage(service, song, isPlaying),
_buildLyricsPage(song),
_buildMainPage(service, song, isPlaying, compact: _isCompact),
if (!_isCompact) _buildLyricsPage(song),
],
),
),
);
}
/// 顶部指示器:当前页对应"长条",另外两页是小点。
/// 主页=中间长条,信息页=左长条,歌词页=右长条。
Widget _buildPageIndicator() {
/// 小窗/分屏:宽高比明显小于全屏(1368x3192 → 2.33;1368x2577 → 1.88)
bool get _isCompact {
final size = MediaQuery.of(context).size;
if (size.width <= 0) return false;
return size.height / size.width < 2.05;
}
/// 顶部指示器:当前页对应"长条",另外的页是小点。
/// 全屏三页(信息/主页/歌词),小窗两页(信息/主页)。
Widget _buildPageIndicator(bool compact) {
Widget segment(int index) {
final active = _pageIndex == index;
return GestureDetector(
@@ -293,47 +300,63 @@ class _PlayerPageState extends BaseState<PlayerPage> {
);
}
final indices = compact ? const [0, 1] : const [0, 1, 2];
return Row(
mainAxisSize: MainAxisSize.min,
children: [
segment(0),
const SizedBox(width: 6),
segment(1),
const SizedBox(width: 6),
segment(2),
for (var i = 0; i < indices.length; i++) ...[
if (i > 0) const SizedBox(width: 6),
segment(indices[i]),
],
],
);
}
Widget _buildMainPage(AudioService service, Song song, bool isPlaying) {
return Padding(
// 顶部留 16:与 AppBar/指示器拉开距离,避免封面贴着顶部
padding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
Widget _buildMainPage(AudioService service, Song song, bool isPlaying,
{required bool compact}) {
final screen = MediaQuery.of(context).size;
// 小窗:封面改为固定方形(约 30% 可用高度),给控件留出空间
final coverSide = compact
? (screen.height * 0.30).clamp(140.0, 340.0)
: null;
final cover = ClipRRect(
borderRadius: BorderRadius.circular(compact ? 18 : 24),
child: song.artwork != null
? Image.memory(
song.artwork!,
fit: BoxFit.cover,
gaplessPlayback: true,
)
: Container(
color: const Color(0xFF2A3332),
child: Icon(
Icons.music_note,
size: compact ? 48 : 72,
color: Colors.white24,
),
),
);
final padded = Padding(
// 顶部留白:与 AppBar/指示器拉开距离,避免封面贴着顶部
padding: EdgeInsets.fromLTRB(24, compact ? 8 : 24, 24, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ---- 封面 ----
AspectRatio(
aspectRatio: 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: song.artwork != null
? Image.memory(
song.artwork!,
fit: BoxFit.cover,
gaplessPlayback: true,
)
: Container(
color: const Color(0xFF2A3332),
child: const Icon(
Icons.music_note,
size: 72,
color: Colors.white24,
),
),
if (coverSide != null)
SizedBox(
width: coverSide,
height: coverSide,
child: cover,
)
else
AspectRatio(
aspectRatio: 1,
child: cover,
),
),
const SizedBox(height: 28),
SizedBox(height: compact ? 12 : 28),
// ---- 标题 / 艺术家(收藏心在标题右侧)----
Row(
@@ -342,8 +365,8 @@ class _PlayerPageState extends BaseState<PlayerPage> {
Expanded(
child: Text(
song.title,
style: const TextStyle(
fontSize: 24,
style: TextStyle(
fontSize: compact ? 20 : 24,
fontWeight: FontWeight.w600,
color: Colors.white,
height: 1.25,
@@ -373,7 +396,10 @@ class _PlayerPageState extends BaseState<PlayerPage> {
const SizedBox(height: 2),
Text(
song.artist,
style: TextStyle(fontSize: 16, color: Colors.grey[300]),
style: TextStyle(
fontSize: compact ? 14 : 16,
color: Colors.grey[300],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -405,19 +431,25 @@ class _PlayerPageState extends BaseState<PlayerPage> {
),
],
const Spacer(),
if (!compact) const Spacer(),
if (compact) const SizedBox(height: 12),
// ---- 进度 ----
_buildProgressSection(service),
const SizedBox(height: 18),
SizedBox(height: compact ? 10 : 18),
// ---- 控制 ----
_buildControlButtons(service, song, isPlaying),
// 底部多留 50px:控制行整体上移
const SizedBox(height: 45),
SizedBox(height: compact ? 12 : 45),
],
),
);
// 小窗/横屏:内容超出时允许滚动,避免控件被挤出屏幕
return compact
? SingleChildScrollView(child: padded)
: padded;
}
/// 左页:文件信息