From 632713d1335beed3a8040d14b968a929aace71c4 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Sun, 1 Nov 2026 12:15:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E4=B8=BB=E9=A1=B5=E6=94=B6?= =?UTF-8?q?=E8=97=8F/=E6=AD=8C=E5=8D=95=E6=94=B9=E4=B8=BA=E8=B7=9F?= =?UTF-8?q?=E6=89=8B=20PageView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前是"横向手势 + 淡入切换",与播放页的跟手滑动不一致。 - 列表区改为 SliverFillRemaining + PageView,直接跟手左右滑 - 媒体库/按钮保持固定,外层 CustomScrollView 不再滚动 - 收藏/歌单各自是独立 ListView,可正常纵向滚动 - 去掉原来的 GestureDetector 和淡入动画 - 标题行指示器继续跟随当前页 真机验证:左右滑动切换正常,指示器跟随。 --- lib/pages/home_page.dart | 352 +++++++++++++++++---------------------- 1 file changed, 154 insertions(+), 198 deletions(-) diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 433fdcf..69bf117 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -162,6 +162,7 @@ class _HomePageState extends BaseState { /// 0 = 我的收藏,1 = 歌单列表(左右滑动切换) int _homeTab = 0; + final PageController _homePageController = PageController(); List _playlistSummaries = []; // ========== 滚动控制 ========== @@ -208,6 +209,7 @@ class _HomePageState extends BaseState { @override void dispose() { + _homePageController.dispose(); _scrollController.dispose(); super.dispose(); } @@ -392,200 +394,165 @@ class _HomePageState extends BaseState { ); } - Widget _buildHomeSliver() { + Widget _buildFavoritesTab() { if (_isLoading) { - return const SliverFillRemaining( - child: Center( - child: CircularProgressIndicator(color: Color(0xFFB8D4D0)), - ), + return const Center( + child: CircularProgressIndicator(color: Color(0xFFB8D4D0)), ); } - return _homeTab == 0 ? _buildFavoritesSliver() : _buildPlaylistsSliver(); - } - - Widget _buildFavoritesSliver() { if (_favorites.isEmpty) { - return SliverFillRemaining( - child: AnimatedOpacity( - opacity: 1.0 - _listReveal * 0.3, - duration: const Duration(milliseconds: 100), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.favorite_border, size: 48, color: Colors.grey[600]), - const SizedBox(height: 16), - Text( - '还没有收藏歌曲\n在音乐库中点击 ♡ 添加', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Colors.grey[500], - height: 1.6, - ), - ), - ], + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.favorite_border, size: 48, color: Colors.grey[600]), + const SizedBox(height: 16), + Text( + '还没有收藏歌曲\n在音乐库中点击 ♡ 添加', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Colors.grey[500], + height: 1.6, + ), ), - ), + ], ), ); } - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final song = _favorites[index]; - return TweenAnimationBuilder( - key: ValueKey('fav-$_homeTab-$index'), - tween: Tween(begin: 0.0, end: 1.0), - duration: const Duration(milliseconds: 240), - curve: Curves.easeOutCubic, - builder: (context, t, child) => Opacity( - opacity: t, - child: Transform.translate( - offset: Offset(0, (1 - t) * 10), - child: child, - ), + return ListView.builder( + padding: const EdgeInsets.fromLTRB( + 20, 0, 20, UIConstants.miniPlayerBottomSpace), + itemCount: _favorites.length, + itemBuilder: (context, index) { + final song = _favorites[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: ListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + leading: SongArtwork( + filePath: song.artworkPath, + size: 44, + fallbackIcon: Icons.music_note, ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: ListTile( - contentPadding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - leading: SongArtwork( - filePath: song.artworkPath, - size: 44, - fallbackIcon: Icons.music_note, - ), - title: Text( - song.displayTitle, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - song.displaySubtitle, - style: TextStyle(fontSize: 13, color: Colors.grey[400]), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - trailing: IconButton( - icon: const Icon( - Icons.favorite, - color: Color(0xFFE05C5C), - size: 20, - ), - onPressed: () async { - await SongDatabase().removeFavorite(song.path); - await _loadFavoritesFromDb(); - if (mounted) safeSetState(() {}); - }, - ), - onTap: () => _playFavorites(index), + title: Text( + song.displayTitle, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Colors.white, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ); - }, - childCount: _favorites.length, - ), - ); - } - - Widget _buildPlaylistsSliver() { - if (_playlistSummaries.isEmpty) { - return SliverFillRemaining( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.queue_music, size: 48, color: Colors.grey[600]), - const SizedBox(height: 16), - Text( - '还没有歌单\n在文件列表长按歌曲即可加入', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Colors.grey[500], - height: 1.6, - ), - ), - ], - ), - ), - ); - } - - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final playlist = _playlistSummaries[index]; - return TweenAnimationBuilder( - key: ValueKey('pl-$_homeTab-$index'), - tween: Tween(begin: 0.0, end: 1.0), - duration: const Duration(milliseconds: 240), - curve: Curves.easeOutCubic, - builder: (context, t, child) => Opacity( - opacity: t, - child: Transform.translate( - offset: Offset(0, (1 - t) * 10), - child: child, - ), + subtitle: Text( + song.displaySubtitle, + style: TextStyle(fontSize: 13, color: Colors.grey[400]), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: ListTile( - contentPadding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - leading: Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: const Color(0xFF1E2A28), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.queue_music, - color: Color(0xFFB8D4D0), - size: 22, - ), + trailing: IconButton( + icon: const Icon( + Icons.favorite, + color: Color(0xFFE05C5C), + size: 20, ), - title: Text( - playlist.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - '${playlist.songCount} 首', - style: TextStyle(fontSize: 13, color: Colors.grey[400]), - ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => PlaylistDetailPage( - playlistId: playlist.id, - playlistName: playlist.name, - ), - ), - ); - await _loadPlaylistSummaries(); + onPressed: () async { + await SongDatabase().removeFavorite(song.path); + await _loadFavoritesFromDb(); if (mounted) safeSetState(() {}); }, ), + onTap: () => _playFavorites(index), ), ); - }, - childCount: _playlistSummaries.length, - ), + }, + ); + } + + Widget _buildPlaylistsTab() { + if (_isLoading) { + return const Center( + child: CircularProgressIndicator(color: Color(0xFFB8D4D0)), + ); + } + if (_playlistSummaries.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.queue_music, size: 48, color: Colors.grey[600]), + const SizedBox(height: 16), + Text( + '还没有歌单\n在文件列表长按歌曲即可加入', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Colors.grey[500], + height: 1.6, + ), + ), + ], + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.fromLTRB( + 20, 0, 20, UIConstants.miniPlayerBottomSpace), + itemCount: _playlistSummaries.length, + itemBuilder: (context, index) { + final playlist = _playlistSummaries[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: ListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + leading: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: const Color(0xFF1E2A28), + borderRadius: BorderRadius.circular(10), + ), + child: const Icon( + Icons.queue_music, + color: Color(0xFFB8D4D0), + size: 22, + ), + ), + title: Text( + playlist.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + '${playlist.songCount} 首', + style: TextStyle(fontSize: 13, color: Colors.grey[400]), + ), + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => PlaylistDetailPage( + playlistId: playlist.id, + playlistName: playlist.name, + ), + ), + ); + await _loadPlaylistSummaries(); + if (mounted) safeSetState(() {}); + }, + ), + ); + }, ); } @@ -725,23 +692,9 @@ class _HomePageState extends BaseState { ), ), Expanded( - child: GestureDetector( - onHorizontalDragEnd: (details) { - final v = details.primaryVelocity ?? 0; - if (v < -200 && _homeTab == 0) { - safeSetState(() => _homeTab = 1); - } else if (v > 200 && _homeTab == 1) { - safeSetState(() => _homeTab = 0); - } - }, - child: CustomScrollView( + child: CustomScrollView( controller: _scrollController, - physics: _mediaLibraryHeight > 0 - ? MagneticScrollPhysics( - snapPoint: _mediaLibraryHeight, - magneticZoneStart: 0.20, - ) - : const ClampingScrollPhysics(), + physics: const NeverScrollableScrollPhysics(), slivers: [ SliverToBoxAdapter( child: Opacity( @@ -961,17 +914,20 @@ class _HomePageState extends BaseState { ), ), ), - SliverPadding( - padding: const EdgeInsets.only( - left: 20, - right: 20, - bottom: UIConstants.miniPlayerBottomSpace, + SliverFillRemaining( + hasScrollBody: true, + child: PageView( + controller: _homePageController, + onPageChanged: (index) => + safeSetState(() => _homeTab = index), + children: [ + _buildFavoritesTab(), + _buildPlaylistsTab(), + ], ), - sliver: _buildHomeSliver(), ), ], ), - ), ), ], ),