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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 15:55:08