Flutter音频播放时切换Tile文本颜色?onPlay触发异常排查
问题分析与修复方案
核心问题
你的代码存在三个关键问题导致颜色切换失效:
isCurrentlyPlaying定义在build方法内部,每次setState触发重建时都会被重新初始化,状态根本无法持久化。- 传给每个
AudioPlayerWidget的isCurrentlyPlaying固定为false,不管点击哪个Tile,子组件拿到的都是false,颜色自然不会变。 - 单个布尔值无法区分哪一个Tile正在播放,无法实现切换时自动重置前一个Tile的颜色。
修复后的完整代码
import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; List<String> titles = <String>[ 'class1', 'class2', 'class3', ]; List<String> sentences = <String>[ 'sentence1', 'sentence2', 'sentence3', 'sentence4' ]; List<String> audio = <String>[ '1.mp3', '2.mp3', '3.mp3', '4.mp3' ]; void main() => runApp(const AppBarApp()); class AppBarApp extends StatelessWidget { const AppBarApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), home: const AppBarExample(), ); } } class AppBarExample extends StatefulWidget { const AppBarExample({super.key}); @override State<AppBarExample> createState() => _AppBarExampleState(); } class _AppBarExampleState extends State<AppBarExample> { // 用索引记录当前正在播放的Tile,null表示没有播放 int? currentPlayingIndex; final audioPlayer = AudioPlayer(); @override void initState() { super.initState(); // 监听音频播放完成事件,结束后重置状态 audioPlayer.onPlayerComplete.listen((_) { setState(() { currentPlayingIndex = null; }); }); } @override void dispose() { audioPlayer.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // 保留你原来的样式变量 const oddItemColor = Color(0xfff5f5f5); const evenItemColor = Colors.white; return DefaultTabController( length: titles.length, child: Scaffold( appBar: AppBar( bottom: TabBar( tabs: titles.map((title) => Tab(text: title)).toList(), ), ), body: TabBarView( children: <Widget>[ ListView.builder( itemCount: sentences.length, itemBuilder: (BuildContext context, int index) { return ListTile( tileColor: index.isOdd ? oddItemColor : evenItemColor, title: AudioPlayerWidget( index: index, isCurrentlyPlaying: currentPlayingIndex == index, onPlay: () { setState(() { // 更新当前播放的索引 currentPlayingIndex = index; }); }, audioPlayer: audioPlayer, ), ); }, ), // 其他Tab页面内容,保留你原来的代码 const Center(child: Text('Class 2 Content')), const Center(child: Text('Class 3 Content')), ], ), ), ); } } class AudioPlayerWidget extends StatelessWidget { final int index; final VoidCallback onPlay; final bool isCurrentlyPlaying; final AudioPlayer audioPlayer; const AudioPlayerWidget({ super.key, required this.index, required this.onPlay, required this.isCurrentlyPlaying, required this.audioPlayer, }); void _playAudio() async { // 先停止当前播放的音频 await audioPlayer.stop(); // 播放当前音频 await audioPlayer.play(AssetSource('audio/${index + 1}.mp3')); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () { widget.onPlay(); _playAudio(); }, child: Text( sentences[index], style: TextStyle( color: isCurrentlyPlaying ? Colors.indigo.shade900 : Colors.black, ), ), ); } }
关键修改说明
状态管理优化:
- 把
currentPlayingIndex(当前播放索引)定义在_AppBarExampleState的成员变量中,确保状态可以持久化。 - 用
currentPlayingIndex == index来判断当前Tile是否正在播放,替代原来的单个布尔值,能精准区分每个Tile的状态。
- 把
音频播放逻辑优化:
- 将全局的
audioPlayer移到_AppBarExampleState中,并在initState里监听播放完成事件,播放结束自动重置currentPlayingIndex,恢复文本颜色。 - 在
dispose方法中释放音频资源,避免内存泄漏。
- 将全局的
子组件简化:
- 把
AudioPlayerWidget改为StatelessWidget,因为播放状态完全由父组件控制,不需要自身维护状态,减少状态冗余。 - 父组件传递
audioPlayer给子组件,统一管理音频实例,避免多个实例冲突。
- 把
这样修改后,点击任意Tile会立即切换播放状态,同时更新对应文本颜色;播放完成后颜色自动恢复;点击其他Tile时,前一个Tile的颜色会自动重置,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Lawrence
相关产品推荐
相关产品推荐

