Flutter中ListView.builder内Just Audio滚动自动播放及索引错误修复
解决Flutter中just_audio在ListView.builder的音频播放问题
核心问题分析
- 滚动时音频自动切换/播放:
ListView.builder的itemBuilder会在item进入视图时重建,你在这里调用player2.setUrl(quranaudio),导致滚动时不断切换音频源,甚至触发播放状态异常。 - 暂停后播放重置:如果把
setUrl放在点击事件里,每次点击播放都会重新设置音频URL,直接重置播放进度到开头。
解决方案
1. 重构状态管理
先修改verselist_screen_modal,添加当前播放索引和进度的跟踪变量:
class verselist_screen_modal extends GetxController { RxBool isPlaying1 = false.obs; RxBool isCompleted = false.obs; RxInt currentPlayingIndex = RxInt(-1); // -1表示无音频在播放 RxList<bool> isPlaying2 = List.generate(100, (index) => false).obs; Rx<Duration> currentPosition = Duration.zero.obs; // 记录当前播放进度 }
2. 优化AudioPlayer逻辑
在页面State中,只在必要时切换音频源,同时监听播放状态:
class _verselist_screenState extends State<verselist_screen> { verselist_screen_modal vsm = Get.put(verselist_screen_modal()); AudioPlayer player1 = AudioPlayer(); AudioPlayer player2 = AudioPlayer(); String? currentLoadedAudioUrl; // 记录当前已加载的音频URL @override void initState() { super.initState(); // 监听player2的播放完成事件 player2.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { // 播放完成后重置状态 vsm.currentPlayingIndex.value = -1; vsm.isPlaying2.forEach((element) => element = false); vsm.isPlaying2.refresh(); } }); // 监听播放进度 player2.positionStream.listen((position) { vsm.currentPosition.value = position; }); // player1初始化逻辑保留 player1.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { vsm.isCompleted.value = true; vsm.isPlaying1.value = false; } }); player1.setUrl(widget.qurandata3.audioFull["05"]!); } @override void dispose() { player1.stop(); player1.dispose(); player2.stop(); player2.dispose(); super.dispose(); }
3. 修改ListView的itemBuilder逻辑
移除itemBuilder中的player2.setUrl调用,把音频控制逻辑放在点击事件内,处理切换播放项和暂停/播放逻辑:
Expanded( child: ListView.builder( itemCount: widget.qurandata1.ayahs.length, itemBuilder: (context, index) { var quranaudio = widget.qurandata2.ayahs[index].audio; // 移除原来的player2.setUrl(quranaudio); return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Container( height: MediaQueryu.getScreenHeight(context) * 0.07, width: MediaQueryu.getScreenWidth(context) * .9, decoration: BoxDecoration( color: color5, borderRadius: BorderRadius.circular(10)), child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Padding( padding: const EdgeInsets.only(left: 10), child: CircleAvatar( radius: 15, child: myText('${index + 1}', 14.0, Colors.white, 'PoppinsMedium'), backgroundColor: color1, ), ), mySpacer(1), Image.asset(verseimage4), SizedBox( width: MediaQueryu.getScreenWidth(context) * 0.02, ), Obx(() { bool isCurrentPlaying = vsm.currentPlayingIndex.value == index; return InkWell( onTap: () async { if (isCurrentPlaying) { // 当前项正在播放,切换暂停/播放 if (vsm.isPlaying2[index]) { await player2.pause(); vsm.isPlaying2[index] = false; } else { await player2.play(); vsm.isPlaying2[index] = true; } } else { // 切换到新的播放项 // 重置之前播放项的状态 if (vsm.currentPlayingIndex.value != -1) { vsm.isPlaying2[vsm.currentPlayingIndex.value] = false; } // 更新当前播放索引 vsm.currentPlayingIndex.value = index; vsm.isPlaying2[index] = true; // 仅当音频URL不同时才重新加载 if (currentLoadedAudioUrl != quranaudio) { currentLoadedAudioUrl = quranaudio; await player2.setUrl(quranaudio); await player2.play(); } else { // 同一音频直接恢复播放 await player2.play(); } } }, child: isCurrentPlaying && vsm.isPlaying2[index] ? Icon(Icons.pause, color: color1) : Icon(Icons.play_arrow, color: color1), ); }), SizedBox( width: MediaQueryu.getScreenWidth(context) * 0.02, ), Image.asset(verseimage3), ], ), ), ), // 以下文本、分割线等内容保持不变 SizedBox( height: MediaQueryu.getScreenHeight(context) * 0.04, ), Align( alignment: Alignment.centerRight, child: myText( widget.qurandata2.ayahs[index].text, 18.0, Colors.white, 'AmiriBold')), SizedBox( height: MediaQueryu.getScreenHeight(context) * 0.02, ), Align( alignment: Alignment.centerLeft, child: myText( widget.qurandata1.ayahs[index].text, 16.0, Colors.white, 'PoppinsRegular')), Divider( color: Colors.grey, ) ], ), ); }, ), )
关键优化点
- 不在
itemBuilder中设置音频URL,彻底避免滚动时误触发音频切换。 - 通过
currentLoadedAudioUrl复用已加载的音频,解决暂停后播放重置的问题。 - 用
currentPlayingIndex统一管理播放项,确保UI状态和音频播放状态一致。
内容的提问来源于stack exchange,提问作者umer developer
相关产品推荐
相关产品推荐

