视频控制器,三方所提供的样式,有时很难满足我们的需求,对于此情况,我们不得不在此基础上自行封装,今天所分享的文章就是一个很简单的控制器封装案例,包含了基本的播放暂停,全屏和退出全屏,以及时间和进度的展示,封装了事件回调以及各个属性的控制,基本上可以满足大部分的业务需求,即便不满足,大家也可以在此基础之上拓展。
我们还是按照惯例,简单罗列一个大纲:
1、基本的效果展示
2、具体使用和相关属性介绍
3、控制器封装考虑因素
4、控制器部分功能代码刨析
5、总结及源码地址
一、基本的效果展示
具体的效果,没什么好说的,都是大众常见的样式,依次从左到右为:播放暂停按钮,播放时间,播放进度,总的时间,全屏及退出全屏按钮。
可以实现的功能有,图标的动态设置,时间进度的颜色及大小控制,以及定时器的开启,具体的可以看第二项。
二、具体使用和相关属性介绍
1、具体使用
作为一个Widget,大家可以随意使用,单独亦或者和视频播放器绑定使用。
VipVideoController( totalTime: 1000*60, backgroundColor: Colors.red, progressColor: Colors.amber, thumbColor: Colors.red, textStyle: TextStyle(color: Colors.red), onVideoPlayClick: (isPlay) { print("当前播放按钮状态$isPlay"); }, onVideoFullScreenClick: (isFullScreen) { print("当前全屏按钮状态$isFullScreen"); }, onVideoChanged: (position) { //返回毫秒print("当前拖拽的进度$position"); } )
2、相关属性
属性 |
类型 |
概述 |
height |
double |
设置控制器高度 |
progressHeight |
double |
进度条高度 |
videoPlayIcon |
String |
视频播放Icon |
videoPauseIcon |
String |
视频暂停Icon |
videoFullScreenIcon |
String |
视频全屏Icon |
videoExitFullScreenIcon |
String |
退出全屏Icon |
textStyle |
TextStyle |
文本样式 |
backgroundColor |
Color |
背景颜色 |
progressColor |
Color |
进度颜色 |
thumbColor |
Color |
拖动颜色 |
thumbRadius |
double |
thumb大小 |
playTimeMarginLeft |
double |
播放时间距离左边的距离 |
playTimeMarginRight |
double |
播放时间距离左边的距离 |
videoTimeMarginLeft |
double |
视频时间距离左边的距离 |
videoTimeMarginRight |
double |
视频时间距离左边的距离 |
totalTime |
int |
总时长 |
changeTime |
int |
改变时长 |
isTimer |
bool |
是否需要定时 |
onVideoPlayClick |
ValueChanged<bool> |
视频播放点击 |
onVideoFullScreenClick |
ValueChanged<bool> |
视频全屏点击点击 |
onVideoChanged |
ValueChanged<int> |
滑动回调 |
onVideoChangeStart |
ValueChanged<int> |
拖动开始 |
onVideoChangeEnd |
ValueChanged<int> |
拖动结束 |
isPlayed |
bool |
播放控制状态,暂停还是开始 |
isFullScreen |
bool |
是否是全屏 |
三、控制器封装考虑因素
视频控制器虽然说简单,但需要考虑的因素还是比较多的,比如点击播放和暂停,全屏和退出全屏的事件回调,拖动进度除了更改自身进度也要更改时间进度,传递的时间换算,定时的开启和关闭等等都是需要解决的。
1、基本的UI设定
控制器的UI一定是基于设计同学所定的UI稿,否则就要以技术驱动设计更改,一般很难,不过也有特殊的案例存在。所以在封装的时候,要么基于UI稿,要么就是动态可配置,通过属性更改基本的样式或者位置。
2、拖动进度的实现
拖动进度就比较简单了,使用的是原生提供的Slider,也就是滑杆,类似于Android中的SeekBar,需要注意的是,颜色等属性的动态配置。
3、时间的换算和进度的绑定
时间的换算,需要把传入的时间戳,转化为我们所需要的时间格式,也就是时分秒的格式,这里使用了intl国际化的插件,主要用到到格式转换DateFormat。
4、定时器的控制
定时器很简单,实例化一个Timer即可,但是,什么时候开始,什么时候暂停都是我们需要考虑的,一般情况下,直接和视频播放器进行绑定,直接更改进度即可,就不用这个定时,如果要用,可以用一个属性控制;在需要定时的情况下,点击暂停,需要暂停定时,除此之外播放完毕后也需要暂停定时;当拖动完毕后,需要开启定时,点击播放,也需要开启定时,所以,对于定时器控制这一块,一定要缕清楚。
四、控制器部分功能代码刨析
1、基本的布局
很简单,一个横向的组件Row,包裹了5个子组件,进度条使用Expanded,用于占有剩余的空间。
returnSizedBox( height: widget.height, child: Row( children: [ getPlayIcon(), //开始和暂停getPlayTime(timeStampToStringDate(_progress)), //时间Expanded(child: getSliderTheme()), //进度getVideoTime(timeStampToStringDate(widget.totalTime!)), //时间getFullScreenIcon() //全屏 ], ));
播放Icon和全屏Icon
未传Icon情况下,直接使用默认的Icon,如果传递了,那么直接使用传递的,需要根据播放状态展示播放按钮还是暂停按钮,全屏Icon需要根据是否全屏状态,来展示对应的图标,同时回调点击事件,VipImage是之前封装的图片组件,大家可以查看以往的分享。
/** 获取播放Icon* */WidgetgetPlayIcon() { if (widget.videoPlayIcon==null) { returnInkWell( onTap: onPlayClick, child: Icon(_isPlayed?Icons.pause : Icons.play_arrow), ); } else { returnVipImage( _isPlayed?widget.videoPlayIcon : widget.videoPauseIcon, onClick: onPlayClick, ); } } /** 获取全屏Icon* */WidgetgetFullScreenIcon() { if (widget.videoFullScreenIcon==null) { returnInkWell( onTap: onFullScreenClick, child: Icon(_isFullScreen?Icons.fullscreen_exit : Icons.fullscreen), ); } else { returnVipImage( _isFullScreen?widget.videoFullScreenIcon : widget.videoExitFullScreenIcon, onClick: onFullScreenClick, ); } }
播放时长和总时长
VipText是之前封装的文本组件,大家可以查看以往的分享,需要注意的是传入的时间需要格式化处理,转化为对应的时分秒结构。
/**获取播放时长* */WidgetgetPlayTime(Stringtext) { returnVipText( text, style: widget.textStyle, marginLeft: widget.playTimeMarginLeft, marginRight: widget.playTimeMarginRight, ); } /**获取总的播放时长* */WidgetgetVideoTime(Stringtext) { returnVipText( text, style: widget.textStyle, marginLeft: widget.videoTimeMarginLeft, marginRight: widget.videoTimeMarginRight, ); }
中间的进度条
进度条使用的是Slider,直接按照原生的Api使用即可,需要注意,最大的进度也就是max,需要和设置的总时长绑定,还有divisions分段,需要以秒作为区分,否则在滑动改变的时候,有可能会和定时造成冲突。
WidgetgetSliderTheme() { vardivisions=widget.totalTime!/1000; returnSliderTheme( data: SliderThemeData( //高度trackHeight: widget.progressHeight, //去掉长按光晕overlayColor: Colors.transparent, //背景颜色inactiveTrackColor: widget.backgroundColor, activeTrackColor: widget.progressColor, thumbColor: widget.thumbColor, thumbShape: RoundSliderThumbShape(enabledThumbRadius: widget.thumbRadius)), child: Slider( min: 0, max: widget.totalTime!.toDouble(), value: _progress.toDouble(), divisions: divisions.toInt(), onChangeStart: (progress) { if (widget.onVideoChangeStart!=null) { widget.onVideoChangeStart!(progress.toInt()); } }, onChangeEnd: (progress) { if (widget.onVideoChangeEnd!=null) { widget.onVideoChangeEnd!(progress.toInt()); } if (_isPlayed) { //播放状态下,如果定时,才会执行_startTimer(); } }, onChanged: (doublevalue) { setState(() { _progress=value.toInt(); }); //回调当前进度if (widget.onVideoChanged!=null) { widget.onVideoChanged!(_progress); } }, ), ); }
2、时间转换
前边有说过使用的是intl国际化插件,主要用到的是dateFormat.format()。
/** 时间戳转换时间* */StringtimeStampToStringDate(inttime) { Stringformat=time<1000*60*60?TimeUtil.m_s : TimeUtil.h_m_s; returnTimeUtil.getTimeStampToStringDate(time, format: format); } /** 时间戳转时间* */staticStringgetTimeStampToStringDate(inttimeStamp, {Stringformat=y_M_d}) { vardateFormat=DateFormat(format); vardateTime=DateTime.fromMillisecondsSinceEpoch(timeStamp); returndateFormat.format(dateTime); }
3、定时操作
定时需要注意的是,在需要定时的时候再开启,比如定义的属性为true时,就执行开启动作,当开启定时后,我们的进度大于总时长时,就需要暂停定时。
开启定时场景:1、定义的属性为true时,进入直接开启定时。2、当点击开始播放按钮时,如果使用定时,就要开启,3、当播放完毕后,再次拖动,也需要开启定时。
暂停定时场景:1、点击暂停视频时,关闭定时,2、播放结束时,关闭定时,3、页面销毁时,也需要关闭定时。
/** 开启定时* */void_startTimer() { if (widget.isTimer!&&_timer==null) { //开启定时,一秒执行一次_timer=Timer.periodic(constDuration(seconds: 1), (timer) { if (_progress>=widget.totalTime!) { _pauseTimer(); } else { setState(() { _progress+=1000; }); widget.onVideoChanged!(_progress); } }); } } /** 暂停定时* */void_pauseTimer() { if (_timer!=null) { _timer!.cancel(); //取消计时器_timer=null; } }
五、总结及源码地址
源码是一个简单的文件,地址:https://github.com/AbnerMing888/flutter_widget/blob/master/lib/ui/widget/vip_video_controller.dart
源码中有用到之前封装的组件,请大家悉知,目前所封装的组件,样式和图标都是可以更换的,但是有一个就是位置还有组件是否显示没有封装,不过源码已经贴出,大家可以在源码基础之上进行更改。