Android?Flutter控件封裝之視頻進(jìn)度條的實(shí)現(xiàn)
視頻控制器,三方所提供的樣式,有時(shí)很難滿足我們的需求,對(duì)于此情況,我們不得不在此基礎(chǔ)上自行封裝,今天所分享的文章就是一個(gè)很簡(jiǎn)單的控制器封裝案例,包含了基本的播放暫停,全屏和退出全屏,以及時(shí)間和進(jìn)度的展示,封裝了事件回調(diào)以及各個(gè)屬性的控制,基本上可以滿足大部分的業(yè)務(wù)需求,即便不滿足,大家也可以在此基礎(chǔ)之上拓展。
我們還是按照慣例,簡(jiǎn)單羅列一個(gè)大綱:
1、基本的效果展示
2、具體使用和相關(guān)屬性介紹
3、控制器封裝考慮因素
4、控制器部分功能代碼刨析
5、總結(jié)及源碼地址
一、基本的效果展示
具體的效果,沒什么好說(shuō)的,都是大眾常見的樣式,依次從左到右為:播放暫停按鈕,播放時(shí)間,播放進(jìn)度,總的時(shí)間,全屏及退出全屏按鈕。

可以實(shí)現(xiàn)的功能有,圖標(biāo)的動(dòng)態(tài)設(shè)置,時(shí)間進(jìn)度的顏色及大小控制,以及定時(shí)器的開啟,具體的可以看第二項(xiàng)。

二、具體使用和相關(guān)屬性介紹
1、具體使用
作為一個(gè)Widget,大家可以隨意使用,單獨(dú)亦或者和視頻播放器綁定使用。
VipVideoController(
totalTime: 1000 * 60,
backgroundColor: Colors.red,
progressColor: Colors.amber,
thumbColor: Colors.red,
textStyle: TextStyle(color: Colors.red),
onVideoPlayClick: (isPlay) {
print("當(dāng)前播放按鈕狀態(tài)$isPlay");
},
onVideoFullScreenClick: (isFullScreen) {
print("當(dāng)前全屏按鈕狀態(tài)$isFullScreen");
},
onVideoChanged: (position) {
//返回毫秒
print("當(dāng)前拖拽的進(jìn)度$position");
}
)2、相關(guān)屬性
| 屬性 | 類型 | 概述 |
|---|---|---|
| height | double | 設(shè)置控制器高度 |
| progressHeight | double | 進(jìn)度條高度 |
| videoPlayIcon | String | 視頻播放Icon |
| videoPauseIcon | String | 視頻暫停Icon |
| videoFullScreenIcon | String | 視頻全屏Icon |
| videoExitFullScreenIcon | String | 退出全屏Icon |
| textStyle | TextStyle | 文本樣式 |
| backgroundColor | Color | 背景顏色 |
| progressColor | Color | 進(jìn)度顏色 |
| thumbColor | Color | 拖動(dòng)顏色 |
| thumbRadius | double | thumb大小 |
| playTimeMarginLeft | double | 播放時(shí)間距離左邊的距離 |
| playTimeMarginRight | double | 播放時(shí)間距離右邊的距離 |
| videoTimeMarginLeft | double | 視頻時(shí)間距離左邊的距離 |
| videoTimeMarginRight | double | 視頻時(shí)間距離右邊的距離 |
| totalTime | int | 總時(shí)長(zhǎng) |
| changeTime | int | 改變時(shí)長(zhǎng) |
| isTimer | bool | 是否需要定時(shí) |
| onVideoPlayClick | ValueChanged<bool> | 視頻播放點(diǎn)擊 |
| onVideoFullScreenClick | ValueChanged<bool> | 視頻全屏點(diǎn)擊點(diǎn)擊 |
| onVideoChanged | ValueChanged<int> | 滑動(dòng)回調(diào) |
| onVideoChangeStart | ValueChanged<int> | 拖動(dòng)開始 |
| onVideoChangeEnd | ValueChanged<int> | 拖動(dòng)結(jié)束 |
| isPlayed | bool | 播放控制狀態(tài),暫停還是開始 |
| isFullScreen | bool | 是否是全屏 |
三、控制器封裝考慮因素
視頻控制器雖然說(shuō)簡(jiǎn)單,但需要考慮的因素還是比較多的,比如點(diǎn)擊播放和暫停,全屏和退出全屏的事件回調(diào),拖動(dòng)進(jìn)度除了更改自身進(jìn)度也要更改時(shí)間進(jìn)度,傳遞的時(shí)間換算,定時(shí)的開啟和關(guān)閉等等都是需要解決的。
1、基本的UI設(shè)定
控制器的UI一定是基于設(shè)計(jì)同學(xué)所定的UI稿,否則就要以技術(shù)驅(qū)動(dòng)設(shè)計(jì)更改,一般很難,不過(guò)也有特殊的案例存在。所以在封裝的時(shí)候,要么基于UI稿,要么就是動(dòng)態(tài)可配置,通過(guò)屬性更改基本的樣式或者位置。
2、拖動(dòng)進(jìn)度的實(shí)現(xiàn)
拖動(dòng)進(jìn)度就比較簡(jiǎn)單了,使用的是原生提供的Slider,也就是滑桿,類似于Android中的SeekBar,需要注意的是,顏色等屬性的動(dòng)態(tài)配置。
3、時(shí)間的換算和進(jìn)度的綁定
時(shí)間的換算,需要把傳入的時(shí)間戳,轉(zhuǎn)化為我們所需要的時(shí)間格式,也就是時(shí)分秒的格式,這里使用了intl國(guó)際化的插件,主要用到到格式轉(zhuǎn)換DateFormat。
4、定時(shí)器的控制
定時(shí)器很簡(jiǎn)單,實(shí)例化一個(gè)Timer即可,但是,什么時(shí)候開始,什么時(shí)候暫停都是我們需要考慮的,一般情況下,直接和視頻播放器進(jìn)行綁定,直接更改進(jìn)度即可,就不用這個(gè)定時(shí),如果要用,可以用一個(gè)屬性控制;在需要定時(shí)的情況下,點(diǎn)擊暫停,需要暫停定時(shí),除此之外播放完畢后也需要暫停定時(shí);當(dāng)拖動(dòng)完畢后,需要開啟定時(shí),點(diǎn)擊播放,也需要開啟定時(shí),所以,對(duì)于定時(shí)器控制這一塊,一定要縷清楚。
四、控制器部分功能代碼刨析
1、基本的布局
很簡(jiǎn)單,一個(gè)橫向的組件Row,包裹了5個(gè)子組件,進(jìn)度條使用Expanded,用于占有剩余的空間。
return SizedBox(
height: widget.height,
child: Row(
children: [
getPlayIcon(), //開始和暫停
getPlayTime(timeStampToStringDate(_progress)), //時(shí)間
Expanded(child: getSliderTheme()), //進(jìn)度
getVideoTime(timeStampToStringDate(widget.totalTime!)), //時(shí)間
getFullScreenIcon() //全屏
],
));播放Icon和全屏Icon
未傳Icon情況下,直接使用默認(rèn)的Icon,如果傳遞了,那么直接使用傳遞的,需要根據(jù)播放狀態(tài)展示播放按鈕還是暫停按鈕,全屏Icon需要根據(jù)是否全屏狀態(tài),來(lái)展示對(duì)應(yīng)的圖標(biāo),同時(shí)回調(diào)點(diǎn)擊事件,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,
);
}
}播放時(shí)長(zhǎng)和總時(shí)長(zhǎng)
VipText是之前封裝的文本組件,大家可以查看以往的分享,需要注意的是傳入的時(shí)間需要格式化處理,轉(zhuǎn)化為對(duì)應(yīng)的時(shí)分秒結(jié)構(gòu)。
/*
*獲取播放時(shí)長(zhǎng)
* */
Widget getPlayTime(String text) {
return VipText(
text,
style: widget.textStyle,
marginLeft: widget.playTimeMarginLeft,
marginRight: widget.playTimeMarginRight,
);
}
/*
*獲取總的播放時(shí)長(zhǎng)
* */
Widget getVideoTime(String text) {
return VipText(
text,
style: widget.textStyle,
marginLeft: widget.videoTimeMarginLeft,
marginRight: widget.videoTimeMarginRight,
);
}中間的進(jìn)度條
進(jìn)度條使用的是Slider,直接按照原生的Api使用即可,需要注意,最大的進(jìn)度也就是max,需要和設(shè)置的總時(shí)長(zhǎng)綁定,還有divisions分段,需要以秒作為區(qū)分,否則在滑動(dòng)改變的時(shí)候,有可能會(huì)和定時(shí)造成沖突。
Widget getSliderTheme() {
var divisions = widget.totalTime! / 1000;
return SliderTheme(
data: SliderThemeData(
//高度
trackHeight: widget.progressHeight,
//去掉長(zhǎng)按光暈
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) {
//播放狀態(tài)下,如果定時(shí),才會(huì)執(zhí)行
_startTimer();
}
},
onChanged: (double value) {
setState(() {
_progress = value.toInt();
});
//回調(diào)當(dāng)前進(jìn)度
if (widget.onVideoChanged != null) {
widget.onVideoChanged!(_progress);
}
},
),
);
}2、時(shí)間轉(zhuǎn)換
前邊有說(shuō)過(guò)使用的是intl國(guó)際化插件,主要用到的是dateFormat.format()。
/*
* 時(shí)間戳轉(zhuǎn)換時(shí)間
* */
String timeStampToStringDate(int time) {
String format = time < 1000 * 60 * 60 ? TimeUtil.m_s : TimeUtil.h_m_s;
return TimeUtil.getTimeStampToStringDate(time, format: format);
}
/*
* 時(shí)間戳轉(zhuǎn)時(shí)間
* */
static String getTimeStampToStringDate(int timeStamp,
{String format = y_M_d}) {
var dateFormat = DateFormat(format);
var dateTime = DateTime.fromMillisecondsSinceEpoch(timeStamp);
return dateFormat.format(dateTime);
}3、定時(shí)操作
定時(shí)需要注意的是,在需要定時(shí)的時(shí)候再開啟,比如定義的屬性為true時(shí),就執(zhí)行開啟動(dòng)作,當(dāng)開啟定時(shí)后,我們的進(jìn)度大于總時(shí)長(zhǎng)時(shí),就需要暫停定時(shí)。
開啟定時(shí)場(chǎng)景:1、定義的屬性為true時(shí),進(jìn)入直接開啟定時(shí)。2、當(dāng)點(diǎn)擊開始播放按鈕時(shí),如果使用定時(shí),就要開啟,3、當(dāng)播放完畢后,再次拖動(dòng),也需要開啟定時(shí)。
暫停定時(shí)場(chǎng)景:1、點(diǎn)擊暫停視頻時(shí),關(guān)閉定時(shí),2、播放結(jié)束時(shí),關(guān)閉定時(shí),3、頁(yè)面銷毀時(shí),也需要關(guān)閉定時(shí)。
/*
* 開啟定時(shí)
* */
void _startTimer() {
if (widget.isTimer! && _timer == null) {
//開啟定時(shí),一秒執(zhí)行一次
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_progress >= widget.totalTime!) {
_pauseTimer();
} else {
setState(() {
_progress += 1000;
});
widget.onVideoChanged!(_progress);
}
});
}
}
/*
* 暫停定時(shí)
* */
void _pauseTimer() {
if (_timer != null) {
_timer!.cancel(); //取消計(jì)時(shí)器
_timer = null;
}
}五、總結(jié)及源碼地址
源碼是一個(gè)簡(jiǎn)單的文件,地址:github.com/AbnerMing888/flutter_widget/blob/master/lib/ui/widget/vip_video_controller.dart
源碼中有用到之前封裝的組件,請(qǐng)大家悉知,目前所封裝的組件,樣式和圖標(biāo)都是可以更換的,但是有一個(gè)就是位置還有組件是否顯示沒有封裝,不過(guò)源碼已經(jīng)貼出,大家可以在源碼基礎(chǔ)之上進(jìn)行更改。
以上就是Android Flutter控件封裝之視頻進(jìn)度條的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter視頻進(jìn)度條的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)多級(jí)列表中的新建功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多級(jí)列表中的新建功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
Android ViewDragHelper實(shí)現(xiàn)京東、淘寶拖拽詳情功能的實(shí)現(xiàn)
這篇文章主要介紹了Android ViewDragHelper實(shí)現(xiàn)京東、淘寶拖拽詳情,實(shí)現(xiàn)這種效果大概分為三種方式,具體哪三種方式大家通過(guò)本文了解下吧2018-04-04
Android 自定義view之畫圖板實(shí)現(xiàn)方法
本文重在對(duì)自定義view,以及其常用類,常用方法的初步了解,提供一個(gè)思路,效果是其次,畫板只是例子,需要的朋友可以參考下2018-01-01
Android 自定義驗(yàn)證碼輸入框的實(shí)例代碼(支持粘貼連續(xù)性)
這篇文章主要介紹了Android 自定義驗(yàn)證碼輸入框的實(shí)例代碼(支持粘貼連續(xù)性),代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
Android中TabLayout結(jié)合ViewPager實(shí)現(xiàn)頁(yè)面切換
這篇文章主要為大家詳細(xì)介紹了Android中TabLayout結(jié)合ViewPager實(shí)現(xiàn)頁(yè)面切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12

