這兩天用Flutter實現(xiàn)了仿抖音視頻滑動播放的功能
代碼運行環(huán)境 flutter SDK 3.10.2
老規(guī)矩先上效果(使用LICEcap截gif圖總是有問題,來張靜態(tài)的吧)
video_player: ^2.4.1
一. video_player基本使用
/// 聲明控制器
late VideoPlayerController _controller;
/// 初始化控制器
_controller = VideoPlayerController.network(list[0]['video_url'])
///設(shè)置視頻循環(huán)播放
..setLooping(true)
///設(shè)置監(jiān)聽
..addListener(() {
setState(() {
});
})
///初始化
..initialize().then((_) async {
///初始化完成更新狀態(tài),不然播放器不會播放
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 完后一定要更新播放器的狀態(tài),不然是widget拿不到狀態(tài)改變,是不會播放的
二. 視頻播放,暫停
/// 判斷播放和暫停
void playOrPauseVideo() {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
// If the video is paused, play it.
_controller.play();
}
});
}
三. 視頻全屏
視頻全屏借助于auto_orientation
來實現(xiàn)
auto_orientation: ^2.3.1
代碼實現(xiàn)
void _toggleFullScreen() {
setState(() {
if (_isFullScreen) {
/// 如果是全屏就切換豎屏
AutoOrientation.portraitAutoMode();
///顯示狀態(tài)欄苛骨,與底部虛擬操作按鈕
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;
///關(guān)閉狀態(tài)欄檐束,與底部虛擬操作按鈕
SystemChrome.setEnabledSystemUIOverlays([]);
}
_isFullScreen = !_isFullScreen;
});
}
四. 顯示加載進度
加載進度video_play已經(jīng)封裝好了VideoProgressIndicator
,直接使用即可,將controller等其他參數(shù)設(shè)置好了就行.
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), // 為緩存的顏色
),
),
))
五. 循環(huán)播放視頻,在設(shè)置的控制器的時候使用級聯(lián)操作符設(shè)置下就可以了
..setLooping(true)
六. 實現(xiàn)抖音滑動效果
核心原理就是使用PageView
來實現(xiàn)的,需要注意的是每次滑動的時候需要將上一個_controller釋放掉以后再重新創(chuàng)建一個,不然上個視頻還是會播放的.具體代碼如下:
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[i]['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), // 為緩存的顏色
),
),
))
],
);
},
)
實現(xiàn)循環(huán)的效果就是當PageView滑動到最后一個時然后jumpToPage(0)
來實現(xiàn)循環(huán)的.
if (index == list.length - 1) {
Future.delayed(
const Duration(milliseconds: 200)).then((lwh) {
_pageController.jumpToPage(0);
});
}
七. 遺留問題待解決
當頁面滑動到中間位置時視頻播放會有問題,可以通過重寫B(tài)ouncingScrollPhysics的方法來解決.
八. 思考探索
之前用iOS來實現(xiàn)這個功能的時候采用的是使用ScrollerView,然后創(chuàng)建三個子View,依次循環(huán)顯示視頻播放器View來實現(xiàn)這種效果,這樣做的原因就是為了性能來考慮,不需要根據(jù)數(shù)據(jù)源去創(chuàng)建更多的View,flutter實現(xiàn)的話代碼如下.
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方法
是懶加載的并不是一開始就創(chuàng)建大量的view,思路和iOS的tableView大同小異.但是這兩種方式哪種的效率更高,還有待考證.今天先寫到這里,有時間再試下第二種思路,檢測下哪種更好吧.
九. 結(jié)束
demo地址請移步: 項目地址