这两天用Flutter实现了仿抖音视频滑动播放的功能
代码运行环境 flutter SDK 3.10.2
老例子先上效果(使用LICEcap截gif图总是有题目,来张静态的吧)
video_player: ^2.4.1一. video_player根本使用
/// 声明控制器late VideoPlayerController _controller;/// 初始化控制器_controller = VideoPlayerController.network(list[0]['video_url']) ///设置视频循环播放 ..setLooping(true) ///设置监听 ..addListener(() { setState(() { }); }) ///初始化 ..initialize().then((_) async { ///初始化完成更新状态,否则播放器不会播放 setState(() { playOrPauseVideo(); }); }).catchError((err) { ///播放堕落 print(err); });/// 体现视频SizedBox( height: 240, width: MediaQuery.of(context).size.width, child: _controller.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ) : const Text( "没有要播放的视频", style: TextStyle(color: Colors.red), ), ),注意点:在播放器initialize 完后肯定要更新播放器的状态,否则是widget拿不到状态改变,是不会播放的
二. 视频播放,停息
/// 判定播放和停息 void playOrPauseVideo() { setState(() { if (_controller.value.isPlaying) { _controller.pause(); } else { // If the video is paused, play it. _controller.play(); } }); }三. 视频全屏
视频全屏借助于auto_orientation来实现
auto_orientation: ^2.3.1代码实现
void _toggleFullScreen() { setState(() { if (_isFullScreen) { /// 假如是全屏就切换竖屏 AutoOrientation.portraitAutoMode(); ///体现状态栏,与底部假造操纵按钮 SystemChrome.setEnabledSystemUIOverlays( [SystemUiOverlay.top, SystemUiOverlay.bottom]); _appbar = AppBar( //标题居中 centerTitle: true, title: const Text( '仿抖音效果', style: TextStyle(overflow: TextOverflow.ellipsis, color: Colors.white), ), elevation: 0, //去掉Appbar底部阴影 //配景颜色 backgroundColor: Colors.blue, ); } else { AutoOrientation.landscapeAutoMode(); _appbar = null; ///关闭状态栏,与底部假造操纵按钮 SystemChrome.setEnabledSystemUIOverlays([]); } _isFullScreen = !_isFullScreen; }); }四. 体现加载进度
加载进度video_play已经封装好了VideoProgressIndicator,直接使用即可,将controller等其他参数设置好了就行.
Positioned( bottom: MediaQuery.of(context).padding.bottom, child: SizedBox( width: MediaQuery.of(context).size.width, height: 1, child: VideoProgressIndicator( _controller, allowScrubbing: true, padding: const EdgeInsets.all(0), colors: const VideoProgressColors( playedColor: Colors.white, // 已播放的颜色 bufferedColor: Color.fromRGBO(255, 255, 255, .5), // 缓存中的颜色 backgroundColor: Color.fromRGBO(255, 255, 255, .3), // 为缓存的颜色 ), ), ))五. 循环播放视频,在设置的控制器的时间使用级联操纵符设置下就可以了
..setLooping(true)六. 实现抖音滑动效果
焦点原理就是使用PageView来实现的,须要注意的是每次滑动的时间须要将上一个_controller释放掉以后再重新创建一个,否则上个视频照旧会播放的.详细代码如下:
PageView.builder( physics: const QuickerScrollPhysics(), controller: _pageController, scrollDirection: Axis.vertical, itemCount: list.length, onPageChanged: (index) { _controller.dispose(); _controller = VideoPlayerController.network(list[index]['video_url']) ..setLooping(true) ..addListener(() { setState(() { }); }) ..initialize().then((_) async { setState(() { playOrPauseVideo(); }); }).catchError((err) { print(err); }); if (index == list.length - 1) { Future.delayed( const Duration(milliseconds: 200)).then((lwh) { _pageController.jumpToPage(0); }); } }, itemBuilder: (context, i) { return Stack( children: [ /// 播放器view Container( color: Colors.black, child: Center( child: Stack( children: [ AppNetImage( fit: BoxFit.fitWidth, imageUrl: list['image_url'], height: 240, width: MediaQuery.of(context).size.width, ), Positioned( child: Stack( children: [ InkWell( child: SizedBox( height: 240, width: MediaQuery.of(context).size.width, child: _controller.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ) : const Text( "没有要播放的视频", style: TextStyle(color: Colors.red), ), ), onTap: () { playOrPauseVideo(); }, ), ], )), Positioned( left: MediaQuery.of(context).size.width / 2 - 30, top: 90, child: _controller.value.isPlaying ? const SizedBox() : const Icon( Icons.play_arrow, color: Colors.white, size: 60, ), ), ], ), ), ), /// 体现全屏按钮 Positioned( bottom: MediaQuery.of(context).padding.bottom + 100, right: 8, child: InkWell( child: const Icon( Icons.aspect_ratio, color: Colors.white, size: 30, ), onTap: () { _toggleFullScreen(); }, )), /// 体现进度条 Positioned( bottom: MediaQuery.of(context).padding.bottom, child: SizedBox( width: MediaQuery.of(context).size.width, height: 1, child: VideoProgressIndicator( _controller, allowScrubbing: true, padding: const EdgeInsets.all(0), colors: const VideoProgressColors( playedColor: Colors.white, // 已播放的颜色 bufferedColor: Color.fromRGBO(255, 255, 255, .5), // 缓存中的颜色 backgroundColor: Color.fromRGBO(255, 255, 255, .3), // 为缓存的颜色 ), ), )) ], ); }, )实现循环的效果就是当PageView滑动到末了一个时然后jumpToPage(0)来实现循环的.
if (index == list.length - 1) { Future.delayed( const Duration(milliseconds: 200)).then((lwh) { _pageController.jumpToPage(0); }); }七. 遗留题目待办理
当页面滑动到中心位置时视频播放会有题目,可以通过重写BouncingScrollPhysics的方法来办理.
八. 思索探索
之前用iOS来实现这个功能的时间接纳的是使用ScrollerView,然后创建三个子View,依次循环体现视频播放器View来实现这种效果,这样做的缘故起因就是为了性能来思量,不须要根据数据源去创建更多的View,flutter实现的话代码如下.
PageView( scrollDirection: Axis.horizontal, children: [ Container( height: 300, color: Colors.pink, child: const Center( child: Text("This is a page1"), ), ), Container( color: Colors.teal, child: const Center( child: Text("This is a page2"), ), ), Container( color: Colors.amber, child: const Center( child: Text("This is a page3"), ), ), ], );但是在flutter中PageView 提供了 builder方法,builder方法是懒加载的并不是一开始就创建大量的view,思绪和iOS的tableView大同小异.但是这两种方式哪种的服从更高,另有待考证.本日先写到这里,偶尔间再试下第二种思绪,检测下哪种更好吧.
九. 竣事
demo地点请移步: 项目地点 |