視訊控制器,三方所提供的樣式,有時很難滿足我們的需求,對于此情況,我們不得不在此基礎上自行封裝,今天所分享的文章就是一個很簡單的控制器封裝案例,包含了基本的播放暫停,全屏和退出全屏,以及時間和進度的展示,封裝了事件回調以及各個屬性的控制,基本上可以滿足大部分的業務需求,即便不滿足,大家也可以在此基礎之上拓展。
我們還是按照慣例,簡單羅列一個大綱:
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,用于占有剩餘的空間。
return SizedBox(
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
* */
Widget getPlayIcon() {
if (widget.videoPlayIcon == null) {
return InkWell(
onTap: onPlayClick,
child: Icon(_isPlayed ? Icons.pause : Icons.play_arrow),
);
} else {
return VipImage(
_isPlayed ? widget.videoPlayIcon : widget.videoPauseIcon,
onClick: onPlayClick,
);
}
}
/*
* 擷取全屏Icon
* */
Widget getFullScreenIcon() {
if (widget.videoFullScreenIcon == null) {
return InkWell(
onTap: onFullScreenClick,
child: Icon(_isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen),
);
} else {
return VipImage(
_isFullScreen
? widget.videoFullScreenIcon
: widget.videoExitFullScreenIcon,
onClick: onFullScreenClick,
);
}
}
播放時長和總時長
VipText是之前封裝的文本元件,大家可以檢視以往的分享,需要注意的是傳入的時間需要格式化處理,轉化為對應的時分秒結構。
/*
*擷取播放時長
* */
Widget getPlayTime(String text) {
return VipText(
text,
style: widget.textStyle,
marginLeft: widget.playTimeMarginLeft,
marginRight: widget.playTimeMarginRight,
);
}
/*
*擷取總的播放時長
* */
Widget getVideoTime(String text) {
return VipText(
text,
style: widget.textStyle,
marginLeft: widget.videoTimeMarginLeft,
marginRight: widget.videoTimeMarginRight,
);
}
中間的進度條
進度條使用的是Slider,直接按照原生的Api使用即可,需要注意,最大的進度也就是max,需要和設定的總時長綁定,還有divisions分段,需要以秒作為區分,否則在滑動改變的時候,有可能會和定時造成沖突。
Widget getSliderTheme() {
var divisions = widget.totalTime! / 1000;
return SliderTheme(
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: (double value) {
setState(() {
_progress = value.toInt();
});
//回調目前進度
if (widget.onVideoChanged != null) {
widget.onVideoChanged!(_progress);
}
},
),
);
}
2、時間轉換
前邊有說過使用的是intl國際化插件,主要用到的是dateFormat.format()。
/*
* 時間戳轉換時間
* */
String timeStampToStringDate(int time) {
String format = time < 1000 * 60 * 60 ? TimeUtil.m_s : TimeUtil.h_m_s;
return TimeUtil.getTimeStampToStringDate(time, format: format);
}
/*
* 時間戳轉時間
* */
static String getTimeStampToStringDate(int timeStamp,
{String format = y_M_d}) {
var dateFormat = DateFormat(format);
var dateTime = DateTime.fromMillisecondsSinceEpoch(timeStamp);
return dateFormat.format(dateTime);
}
3、定時操作
定時需要注意的是,在需要定時的時候再開啟,比如定義的屬性為true時,就執行開啟動作,當開啟定時後,我們的進度大于總時長時,就需要暫停定時。
開啟定時場景:1、定義的屬性為true時,進入直接開啟定時。2、當點選開始播放按鈕時,如果使用定時,就要開啟,3、當播放完畢後,再次拖動,也需要開啟定時。
暫停定時場景:1、點選暫停視訊時,關閉定時,2、播放結束時,關閉定時,3、頁面銷毀時,也需要關閉定時。
/*
* 開啟定時
* */
void _startTimer() {
if (widget.isTimer! && _timer == null) {
//開啟定時,一秒執行一次
_timer = Timer.periodic(const Duration(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
源碼中有用到之前封裝的元件,請大家悉知,目前所封裝的元件,樣式和圖示都是可以更換的,但是有一個就是位置還有元件是否顯示沒有封裝,不過源碼已經貼出,大家可以在源碼基礎之上進行更改。