You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Spotify克隆应用:点击歌曲卡片同步迷你播放器数据

实现Spotify克隆迷你播放器同步歌曲信息方案

核心思路

通过Navigator跳转时传递歌曲数据,从详情页返回时携带选中的歌曲信息,在主页的StatefulWidget中用setState更新迷你播放器的状态。

步骤1:定义歌曲数据模型

先创建统一的歌曲类管理信息:

class Song {
  final String id;
  final String title;
  final String artist;
  final String coverUrl;

  Song({
    required this.id,
    required this.title,
    required this.artist,
    required this.coverUrl,
  });
}

步骤2:改造主页为StatefulWidget

主页需要维护当前选中歌曲的状态,因此改为StatefulWidget:

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // 初始化当前播放歌曲(可设默认值或null)
  Song? currentPlayingSong;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // 歌曲Grid列表
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(16),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 16,
                mainAxisSpacing: 16,
              ),
              itemCount: songsList.length,
              itemBuilder: (context, index) {
                final song = songsList[index];
                return GridTile(
                  child: GestureDetector(
                    onTap: () async {
                      // 跳转详情页并等待返回数据
                      final returnedSong = await Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => SongDetailPage(song: song),
                        ),
                      );
                      // 返回有效数据时更新状态
                      if (returnedSong != null && returnedSong is Song) {
                        setState(() {
                          currentPlayingSong = returnedSong;
                        });
                      }
                    },
                    child: Image.network(song.coverUrl, fit: BoxFit.cover),
                  ),
                  footer: GridTileBar(
                    title: Text(song.title),
                    subtitle: Text(song.artist),
                  ),
                );
              },
            ),
          ),
          // 迷你播放器(有选中歌曲时显示)
          if (currentPlayingSong != null)
            Container(
              height: 60,
              color: Colors.grey[800],
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              child: Row(
                children: [
                  Image.network(currentPlayingSong!.coverUrl, width: 48, height: 48, fit: BoxFit.cover),
                  const SizedBox(width: 12),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(currentPlayingSong!.title, style: const TextStyle(color: Colors.white, fontSize: 14)),
                      Text(currentPlayingSong!.artist, style: TextStyle(color: Colors.grey[400], fontSize: 12)),
                    ],
                  ),
                  const Spacer(),
                  IconButton(onPressed: () {}, icon: const Icon(Icons.play_arrow, color: Colors.white)),
                ],
              ),
            ),
        ],
      ),
    );
  }

  // 模拟歌曲列表数据
  final List<Song> songsList = [
    Song(
      id: '1',
      title: 'Blinding Lights',
      artist: 'The Weeknd',
      coverUrl: 'https://example.com/cover1.jpg',
    ),
    Song(
      id: '2',
      title: 'Levitating',
      artist: 'Dua Lipa',
      coverUrl: 'https://example.com/cover2.jpg',
    ),
  ];
}

步骤3:实现歌曲详情页

详情页接收歌曲数据,返回时将数据回传给主页:

class SongDetailPage extends StatelessWidget {
  final Song song;

  const SongDetailPage({super.key, required this.song});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(song.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.network(song.coverUrl, width: 200, height: 200, fit: BoxFit.cover),
            const SizedBox(height: 24),
            Text(song.title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
            Text(song.artist, style: const TextStyle(fontSize: 18, color: Colors.grey)),
            const SizedBox(height: 32),
            ElevatedButton(
              onPressed: () {
                // 返回主页时携带当前歌曲数据
                Navigator.pop(context, song);
              },
              child: const Text('返回主页'),
            ),
          ],
        ),
      ),
    );
  }
}

关键知识点说明

  • StatefulWidget:主页需要动态更新UI,因此必须用StatefulWidget,其内部的State类负责维护可变状态(如currentPlayingSong)。
  • setState:调用setState(() {})会通知Flutter框架状态变更,触发页面重新构建,从而更新迷你播放器的内容。
  • Navigator返回传值:通过await Navigator.push(...)等待详情页返回的数据,拿到后更新主页状态,实现数据同步。

内容的提问来源于stack exchange,提问作者Warrior

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 05:03:20