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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:24:54