如何在Flutter中创建仿Netflix样式的自定义视频播放器
嘿,刚好我之前做过类似的仿Netflix视频播放器需求,给你分享些实用的方案和代码示例!
推荐的第三方库
首先,最适合的组合是官方的 video_player + chewie:
video_player是Flutter官方提供的视频播放核心库,负责底层的视频加载、播放控制等功能,稳定性有保障。chewie是基于video_player封装的UI层库,提供了常用的播放控件,同时支持高度自定义,能快速搭建出类似Netflix的播放器界面。
你只需要在pubspec.yaml里添加依赖:
dependencies: flutter: sdk: flutter video_player: ^2.8.1 chewie: ^1.7.0
完整示例代码
下面是一个实现了你需求的完整播放器组件,包含播放/暂停按钮、返回按钮、标题显示、时间指示器,并且强制横屏:
import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/services.dart'; class NetflixStyleVideoPlayer extends StatefulWidget { final String videoUrl; final String videoTitle; const NetflixStyleVideoPlayer({ super.key, required this.videoUrl, required this.videoTitle, }); @override State<NetflixStyleVideoPlayer> createState() => _NetflixStyleVideoPlayerState(); } class _NetflixStyleVideoPlayerState extends State<NetflixStyleVideoPlayer> { late VideoPlayerController _videoPlayerController; late ChewieController _chewieController; @override void initState() { super.initState(); // 初始化时强制设置为横屏模式 SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); _setupVideoPlayer(); } Future<void> _setupVideoPlayer() async { _videoPlayerController = VideoPlayerController.network(widget.videoUrl); await _videoPlayerController.initialize(); // 配置Chewie控制器,自定义控制栏 _chewieController = ChewieController( videoPlayerController: _videoPlayerController, aspectRatio: _videoPlayerController.value.aspectRatio, autoPlay: true, looping: false, customControls: const _CustomPlayerControls(), // 替换默认控件 showControlsOnInitialize: true, materialProgressColors: ChewieProgressColors( playedColor: Colors.red, // 进度条已播放部分颜色(Netflix红) handleColor: Colors.red, backgroundColor: Colors.grey[800]!, bufferedColor: Colors.grey[600]!, ), ); setState(() {}); } @override void dispose() { // 销毁控制器,恢复竖屏模式 _videoPlayerController.dispose(); _chewieController.dispose(); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // 视频播放器主体 _videoPlayerController.value.isInitialized ? Chewie(controller: _chewieController) : const Center(child: CircularProgressIndicator(color: Colors.white)), // 顶部返回按钮+标题栏 Positioned( top: MediaQuery.of(context).padding.top + 10, left: 10, right: 10, child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white, size: 28), onPressed: () => Navigator.pop(context), ), const SizedBox(width: 10), Expanded( child: Text( widget.videoTitle, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), ), ], ), ); } } // 自定义播放控制栏:播放/暂停 + 时间指示器 + 进度条 class _CustomPlayerControls extends StatelessWidget { const _CustomPlayerControls(); @override Widget build(BuildContext context) { final chewieController = ChewieController.of(context)!; return Positioned( bottom: 20, left: 0, right: 0, child: Column( children: [ // 自定义进度条 Slider( value: chewieController.videoPlayerController.value.position.inSeconds.toDouble(), max: chewieController.videoPlayerController.value.duration.inSeconds.toDouble(), onChanged: (value) { chewieController.seekTo(Duration(seconds: value.toInt())); }, activeColor: Colors.red, inactiveColor: Colors.grey[700], thumbColor: Colors.red, ), // 播放按钮+时间显示 Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ // 播放/暂停按钮 IconButton( icon: Icon( chewieController.isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 36, ), onPressed: chewieController.togglePlay, ), const SizedBox(width: 12), // 当前播放时间 Text( _formatDuration(chewieController.videoPlayerController.value.position), style: const TextStyle(color: Colors.white, fontSize: 14), ), const SizedBox(width: 8), const Text('/', style: TextStyle(color: Colors.white)), const SizedBox(width: 8), // 总时长 Text( _formatDuration(chewieController.videoPlayerController.value.duration), style: const TextStyle(color: Colors.white, fontSize: 14), ), const Spacer(), // 可以添加全屏按钮(不过这里已经强制横屏,可选) ], ), ), ], ), ); } // 格式化时间为MM:SS或HH:MM:SS String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final hours = twoDigits(duration.inHours); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return duration.inHours > 0 ? '$hours:$minutes:$seconds' : '$minutes:$seconds'; } } // 使用方式示例 // Navigator.push( // context, // MaterialPageRoute( // builder: (context) => NetflixStyleVideoPlayer( // videoUrl: 'https://example.com/your-video.mp4', // videoTitle: '怪奇物语 第四季 第一集', // ), // ), // );
关键细节说明
- 横屏强制处理:在页面初始化时用
SystemChrome.setPreferredOrientations锁定横屏,销毁时恢复竖屏,确保用户体验。 - 自定义控件:通过Chewie的
customControls属性替换默认控制栏,完全按照Netflix的样式调整颜色、布局。 - 标题与返回按钮:用
Stack叠加在视频上方,适配状态栏高度,避免被刘海遮挡。 - 时间格式化:自定义
_formatDuration方法,把Duration转换为用户友好的时间格式。
进阶优化建议
- 可以添加自动隐藏控制栏的功能:用
Timer监听用户点击,无操作3秒后隐藏控件,点击屏幕时显示。 - 加入手势控制:比如左右滑动快进快退,上下滑动调节亮度/音量,更贴近Netflix的交互。
- 支持画中画模式:如果需要后台播放,可以结合相关库实现。
内容的提问来源于stack exchange,提问作者O G




