如何在Flutter多页面应用中导航时停止音频播放
问题
我有一个主页面ItemsScreen,内部通过ListView.builder生成多个ItemCard组件。现在需要实现:无论用户从ItemCard内部跳转页面,还是从主页面跳转,只要有音频在播放,就自动停止。使用的音频播放包是audioplayers: ^5.1.0。
原ItemsScreen代码:
class _ItemsScreenState extends State<ItemsScreen> { @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: 3, itemBuilder: (context, index) { return ItemCard(audioUrl: 'audio_url_for_YourWidget'); }, ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (_) => SettingScreen()), ); }, child: Icon(Icons.settings), ), ); } }
原ItemCard代码:
class ItemCard extends StatefulWidget { final String audioUrl; ItemCard({required this.audioUrl}); @override _ItemCardState createState() => _ItemCardState(); } class _ItemCardState extends State<ItemCard> { bool isPlaying = false; bool loading = false; bool isStarted = false; final AudioPlayer audioPlayer = AudioPlayer(); void _loadAudio() async { if (!isPlaying) { setState(() { loading = true; isStarted = true; }); await audioPlayer.play( UrlSource( 'https://firebasestorage.googleapis.com/v0/b/voice-feed.appspot.com/o/audio%2F1694248558321?alt=media&token=20194024-0060-43e5-b66c-dd10ec4537a0'), ); setState(() { loading = false; }); } else { await audioPlayer.pause(); } setState(() => isPlaying = !isPlaying); } @override Widget build(BuildContext context) { return Container( child: Column( children: [ ElevatedButton( onPressed: _loadAudio, child: Text(isPlaying ? 'Pause' : 'Play'), ), ElevatedButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (_) => ItemViewScreen()), ); }, child: Text('View the item'), ), ], ), ); } }
解决方案
核心思路是统一管理音频播放实例,并在页面导航时触发停止操作,兼顾主动触发和全局监听两种方式。
1. 创建全局音频管理器
用单例类统一管理音频播放状态,避免每个ItemCard单独维护播放器,方便全局控制:
import 'package:audioplayers/audioplayers.dart'; class AudioManager { static final AudioManager _instance = AudioManager._internal(); factory AudioManager() => _instance; AudioManager._internal(); final AudioPlayer audioPlayer = AudioPlayer(); bool isPlaying = false; Future<void> togglePlay(String url) async { if (!isPlaying) { await audioPlayer.play(UrlSource(url)); isPlaying = true; } else { await audioPlayer.pause(); isPlaying = false; } } Future<void> stop() async { await audioPlayer.stop(); isPlaying = false; } }
2. 修改ItemCard使用全局管理器
替换原有播放器实例,同时在内部导航时主动停止音频:
class ItemCard extends StatefulWidget { final String audioUrl; const ItemCard({required this.audioUrl, super.key}); @override _ItemCardState createState() => _ItemCardState(); } class _ItemCardState extends State<ItemCard> { final AudioManager _audioManager = AudioManager(); bool loading = false; bool isStarted = true; void _toggleAudio() async { if (!_audioManager.isPlaying) { setState(() { loading = true; isStarted = true; }); await _audioManager.togglePlay(widget.audioUrl); setState(() => loading = false); } else { await _audioManager.togglePlay(widget.audioUrl); } setState(() {}); } @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.symmetric(vertical: 8), child: Column( children: [ ElevatedButton( onPressed: _toggleAudio, child: Text(_audioManager.isPlaying ? 'Pause' : 'Play'), ), ElevatedButton( onPressed: () { // 导航前停止音频 _audioManager.stop(); Navigator.push( context, MaterialPageRoute(builder: (_) => ItemViewScreen()), ); }, child: const Text('View the item'), ), ], ), ); } }
3. 修改主页面处理导航并添加全局监听
在主页面中,既可以在跳转前主动停止,也可以通过RouteObserver监听所有导航事件自动停止:
class _ItemsScreenState extends State<ItemsScreen> with RouteAware { final AudioManager _audioManager = AudioManager(); static final RouteObserver<PageRoute> _routeObserver = RouteObserver<PageRoute>(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute); }); } // 监听跳转至下一页的事件,自动停止音频 @override void didPushNext() { _audioManager.stop(); super.didPushNext(); } @override void dispose() { _routeObserver.unsubscribe(this); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( itemCount: 3, itemBuilder: (context, index) { return ItemCard(audioUrl: 'audio_url_for_YourWidget'); }, ), floatingActionButton: FloatingActionButton( onPressed: () { // 主动停止后跳转 _audioManager.stop(); Navigator.push( context, MaterialPageRoute(builder: (_) => SettingScreen()), ); }, child: const Icon(Icons.settings), ), ); } }
4. 注册RouteObserver到全局
在MaterialApp中添加导航观察者,使全局监听生效:
MaterialApp( navigatorObservers: [_routeObserver], home: ItemsScreen(), // 其他配置 )
效果说明
- ItemCard内导航:点击"View the item"时,主动调用
AudioManager.stop()停止音频,再跳转页面 - 主页面导航:点击设置按钮时主动停止,同时通过
didPushNext监听所有跳转事件,确保任何页面跳转都能自动停止音频
内容的提问来源于stack exchange,提问作者Harshana
相关产品推荐
相关产品推荐

