feat(ui): 小窗/分屏自适应播放页
按可用宽高比判断紧凑模式(全屏 3192/1368≈2.33,小窗 2577/1368≈1.88): - 封面由撑满宽度改为固定方形(可用高度的 30%,上限 340dp) - 标题 24→20、艺术家 16→14,各段间距收紧 - 歌词页不显示(PageView 两页:信息 / 主页),指示器同步改两段 - 紧凑模式下整页可滚动,避免控件被挤出屏幕 不依赖"记住分辨率":直接读当前 MediaQuery,窗口尺寸变化时自动切换。
This commit is contained in:
+73
-41
@@ -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;
|
||||
}
|
||||
|
||||
/// 左页:文件信息
|
||||
|
||||
Reference in New Issue
Block a user