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

Flutter ListView点击项切换YoutubePlayer播放视频不生效如何解决

问题解决方法

核心错误原因

  • 控制器作用域错误:你当前的YoutubePlayerController都是在方法内部声明的局部变量,类实例没有持有这个控制器,_changevideoId里修改的是方法内的临时控制器,和build方法里新建的控制器完全无关,修改自然不会生效。
  • 每次build重复新建控制器:你把控制器初始化逻辑放在了build方法里,每次setState触发重绘都会重新创建一个用初始widget.id的控制器,之前的修改直接被覆盖了。
  • 控制器使用方式错误:不需要每次切换视频都重新实例化控制器,youtube_player_flutter库的控制器自带load()方法,可以直接切换视频ID,性能更好。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import 'videolist.dart';
import './models/models.dart';
import 'package:csv/csv.dart' as csv;
import 'package:http/http.dart' as http;

class DisplayVideo extends StatefulWidget {
  final String id;
  const DisplayVideo(this.id, {super.key});

  @override
  _DisplayVideoState createState() => _DisplayVideoState();
}

class _DisplayVideoState extends State<DisplayVideo> {
  // 将控制器声明为类的成员变量
  late YoutubePlayerController _controller;

  @override
  void initState() {
    super.initState();
    // 初始化控制器放在initState里,仅执行一次
    _controller = YoutubePlayerController(
      initialVideoId: widget.id,
      flags: const YoutubePlayerFlags(
        autoPlay: true,
        mute: false,
      ),
    );
  }

  @override
  void dispose() {
    // 页面销毁时释放控制器,避免内存泄漏
    _controller.dispose();
    super.dispose();
  }

  Future<List<YoutubeDetail>> _loadCSV() async {
    Map<String, String> allData = {
      'login': 'JornaldRem',
      'password': 'Aungkokolin7',
    };
    final Uri url = Uri.parse(
        'https://raw.githubusercontent.com/JornaldRem/bedtime_story/main/videoId.csv');
    final response = await http.get(url);
    csv.CsvToListConverter converter =
        csv.CsvToListConverter(eol: '\r\n', fieldDelimiter: ',');
    List<List> listCreated = converter.convert(response.body);
    List<YoutubeDetail> youtubeDetailList = [];
    for (int i = 0; i < listCreated.length; i++) {
      YoutubeDetail temp = YoutubeDetail(
        listCreated[i][0],
        listCreated[i][1],
      );
      youtubeDetailList.add(temp);
    }
    return youtubeDetailList;
  }

  void _changevideoId(String url){
    String id = url.substring(url.length - 11);
    // 直接调用控制器的load方法切换视频,无需额外setState即可生效
    _controller.load(id);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          elevation: 0,
          title: const Text('Fairy Tail Video'),
          toolbarHeight: 60,
          backgroundColor: const Color(0xFF006666),
        ),
        body: Column(
          children: [
            YoutubePlayer(
              controller: _controller,
              liveUIColor: Colors.amber,
            ),
            Expanded(
              child: FutureBuilder(
                  future: _loadCSV(),
                  builder: (BuildContext context,
                      AsyncSnapshot<List<YoutubeDetail>> snapshot) {
                    if (snapshot.hasData) {
                      List<YoutubeDetail> videoDetail = snapshot.data!;
                      return ListView.builder(
                          shrinkWrap: true,
                          scrollDirection: Axis.vertical,
                          itemCount: videoDetail.length,
                          itemBuilder: (_, int index) {
                            if (index > 0) {
                              return GestureDetector(
                                child: SizedBox(
                                  height: 80,
                                  child: DisplayVideoView(
                                      videoDetail[index].url,
                                      videoDetail[index].title),
                                ),
                                onTap: () =>_changevideoId(videoDetail[index].url),
                              );
                            } else {
                              return const SizedBox();
                            }
                          });
                    } else {
                      return const Center(child: CircularProgressIndicator());
                    }
                  }),
            ),
          ],
        ));
  }
}

class DisplayVideoView extends StatelessWidget {
  final String videopath;
  final String title;
  const DisplayVideoView(this.videopath, this.title, {super.key});

  @override
  Widget build(BuildContext context) {
    String url = videopath;
    String id = url.substring(url.length - 11);
    return Card(
      clipBehavior: Clip.antiAlias,
      child: SizedBox(
        height: 150,
        child: Row(children: [
          Expanded(
            flex: 6,
            child: Container(
              decoration: BoxDecoration(
                  image: DecorationImage(
                      image: NetworkImage(
                          'https://img.youtube.com/vi/$id/mqdefault.jpg'),
                      fit: BoxFit.fill)),
            ),
          ),
          const Spacer(flex: 1),
          Expanded(
            flex: 14,
            child: Container(
              padding: const EdgeInsets.only(top: 2),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(title,
                      style: const TextStyle(
                          fontSize: 16.0, fontWeight: FontWeight.bold)),
                ],
              ),
            ),
          ),
        ]),
      ),
    );
  }
}

额外优化点

  • 补充了控制器的dispose逻辑,避免内存泄漏
  • 把空加载页替换为加载指示器,优化用户体验
  • 修正了构造方法、变量声明的规范写法,适配空安全要求
  • 移除了build方法里的控制器初始化逻辑,避免重复创建实例

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 07:54:03