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
相关产品推荐
相关产品推荐

