这两天用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[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), // 为缓存的颜色
),
),
))
],
);
},
)
实现循环的效果就是当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地址请移步: 项目地址