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

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,
        ),
      ),
    );
  }
}

关键修改说明

  1. 状态管理优化:

    • 把currentPlayingIndex(当前播放索引)定义在_AppBarExampleState的成员变量中,确保状态可以持久化。
    • 用currentPlayingIndex == index来判断当前Tile是否正在播放,替代原来的单个布尔值,能精准区分每个Tile的状态。
  2. 音频播放逻辑优化:

    • 将全局的audioPlayer移到_AppBarExampleState中,并在initState里监听播放完成事件,播放结束自动重置currentPlayingIndex,恢复文本颜色。
    • 在dispose方法中释放音频资源,避免内存泄漏。
  3. 子组件简化:

    • 把AudioPlayerWidget改为StatelessWidget,因为播放状态完全由父组件控制,不需要自身维护状态,减少状态冗余。
    • 父组件传递audioPlayer给子组件,统一管理音频实例,避免多个实例冲突。

这样修改后,点击任意Tile会立即切换播放状态,同时更新对应文本颜色;播放完成后颜色自动恢复;点击其他Tile时,前一个Tile的颜色会自动重置,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Lawrence

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 18:44:52