Flutter ListView播放视频列表①
誓言用来拴骚动的心,终就拴住了虚空。山林不向四季起誓,荣枯随缘;海洋不需对沙岸承诺,遇合尽兴。连语言都应该舍弃,你我之间,只有干干净净的缄默,与存在。
免费参考:
今日完成最终效果:
效果图(1.1)
:
分析:
- ListView加载文字和视频
- ListView上的Item有一层透明的’开始’文字按钮
- 点击’开始’文字按钮视频开始,'开始’文字按钮消失
- '开始’文字按钮点击与消失有渐变的效果
- ListView滑动的时候,显示"加载中…"停下来的时候加载动画
ListView布局
VideoPage类 ListView代码:
@override
Widget build(BuildContext context) {
return Container(
child: buildListView(),
);
}
Widget buildListView() {
return ListView.builder(
//不缓存
cacheExtent: 0,
//加载20条数据
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return buildListViewItems();
},
);
VideoPage类 ListView Item布局:
Widget buildListViewItems() {
return Container(
height: 200,
child: Stack(
children: [
Positioned.fill(
child: Text(
"正在蜕变的CV工程师",
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.all(20),
child: VideoItemPageWidget(
//只需要传递当前的路径即可
url: 'assets/video/kfc.mp4',
),
),
],
),
);
}
这段代码比较简单,就是一个简单的ListView布局,视频播放参考这里
视频播放器加’开始’文字
(正常情况是加的开始播放图片,这里为了醒目我就加文字了)
VideoItemPageWidget类:
class VideoItemPageWidget extends StatefulWidget {
String url;
///VideoPageEnum.asset 本地视屏
/// VideoPageEnum.network 网络视频
VideoPageEnum type;
VideoItemPageWidget({
@required this.url, //路径
this.type = VideoPageEnum.asset, //默认为传递本地视频
});
@override
_VideoItemPageWidgetState createState() => _VideoItemPageWidgetState();
}
class _VideoItemPageWidgetState extends State<VideoItemPageWidget> {
@override
Widget build(BuildContext context) {
return buildLayout();
}
/*
* 视频播放器和控制器布局
*/
Widget buildLayout() {
return Stack(
children: [
//占满全屏 并 播放视频
Positioned.fill(
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
),
//编辑透明文字
buildtext(),
],
);
}
bool isCheck = false; //true 播放 false不播放
//编辑透明文字
Widget buildtext(){
Positioned.fill(
//透明动画
child: AnimatedOpacity(
//当 按钮文字问"开始"时,不显示当前文字
opacity: isCheck ? 0 : 1,
duration: new Duration(seconds: 1),
child: Container(
color: Colors.grey.withOpacity(0.5),
child: Center(
child: Text(
"开始",
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
),
),
),
}
}
分析:
- 通过AnimatedOpacity()来根据isCheck变量来改变当前状态并改变透明度
- AnimatedOpacity()中opacity参数1不透明 0透明
- AnimatedOpacity()中duration设置动画时间
- 视频的代码大家不用很在意,都是复制官方的代码
效果图(1.2)
:
现在点击开始并没有效果,因为咋们还没有改变isCheck的状态.
点击’开始’播放视频
接下来咋们对开始外层的Container按钮监听,其实就是对视频外层0.5透明度的灰色界面监听.
bool isCheck = false; //true 播放 false不播放
GestureDetector(
onTap: () {
isCheck = !isCheck;
if (isCheck) {
//点击开始按钮开始播放
_controller.play();
} else {
//暂停播放
_controller.pause();
}
setState(() {});
},
child: Container(
......
),
)
分析:
- 先改变isCheck的状态,若为true则变为false,若为false则为true
- 然后当为true是则让它播放视频,反之暂停视频
- 刷新改变isCheck的状态.
效果图(1.3)
:
存在问题:
- 当点击开始播放A,在点击开始播放B时,A的状态没有改变成暂停
- A的状态不但没有改变,还需要重新点击一下,使他的状态由暂停恢复到开始,才可以播放
解决思路:
- 创建全局控制器,当点击视频播放器时,吧当前状态赋值给全局控制器,
- 然后通过判断当前状态与传过来的状态是否一致,
- 一致说明点击的同一个,不一致说明点击的不同,若点击的不同视屏,
- 则吧上一个视屏关闭掉(pause())
全局控制器修改点击BUG
VideoPage类:
//创建一个多订阅流
final StreamController<VideoPlayerController> _streamController = new StreamController.broadcast();
//播放控制器
VideoPlayerController _videoPlayerController;
@override
void initState() {
super.initState();
_streamController.stream.listen((event) {
//当前控制器ID != 传递过来的标识 说明当前传递的是新的标识
if (_videoPlayerController != null &&
_videoPlayerController.textureId != event.textureId) {
//暂停
_videoPlayerController.pause();
}
_videoPlayerController = event;
LogUtil.Log(
tagging: "streaminitState", title: "接收到消息${_videoPlayerController.textureId}");
});
}
VideoItemPageWidget类:
//全局流控制器,用来关闭上一个播放视频
StreamController streamController;
VideoItemPageWidget({
@required this.url, //路径
this.streamController,
this.type = VideoPageEnum.asset, //默认为传递本地视频
});
在点击开始按钮时,吧当前的状态传递给StreamController,让它通过ID去判断是否点击了下一个.
VideoItemPageWidget类:
GestureDetector(
onTap: () {
isCheck = !isCheck;
if (isCheck) {
//点击开始按钮开始播放
if (widget.streamController != null) {
widget.streamController.add(_controller);
}
_controller.play();
} else {
// isCheck = false;
//暂停播放
_controller.pause();
}
setState(() {});
},
child: Container(
child: Text("开始")
.....
),
),
通过 widget.streamController.add(_controller);将当前视频播放器状态传递给全局播放状态.
然后通过对视频播放控制器的监听,判断当前是否为播放状态
@override
void initState() {
super.initState();
//视频播放初始化
_controller = .....();
_controller.addListener(() {
// isCheck 当前是否是播放状态
// _controller.value.isPlaying 如果正在播放视频,则为True。如果暂停,则为False。
// 如果当前标识是播放状态,但是播放器已经不播放了 则吧标识改变成不播放状态 跟随播放器改变而改变
if (isCheck && !_controller.value.isPlaying) {
isCheck = false;
LogUtil.Log(tagging: "ischeck", title: '$isCheck');
setState(() {});
}
});
}
如果当前的标识是播放状态,但是播放器已经不播放了 则吧标识改变成不播放状态 跟随播放器的状态改变而改变
效果图(1.4)
:
ListView优化.
对ListView监听,监听当前ListView是否滑动,是否停止
VideoPage类:
bool isScroll = false; //是否滑动
onNotification: (notification) {
///通知类型
switch (notification.runtimeType) {
case ScrollStartNotification:
print("开始滚动");
isScroll = false;
break;
case ScrollUpdateNotification:
print("正在滚动");
break;
case ScrollEndNotification:
print("滚动停止");
isScroll = true;
setState(() {});
break;
case OverscrollNotification:
print("滚动到边界");
break;
}
return true;
},
child:ListView.builder(.....)
}
VideoItemPageWidget(
url: 'assets/video/kfc.mp4',
isScroll: isScroll,
streamController: _streamController,
),
VideoItemPageWidget类:
//全局流控制器,用来监听控制视频播放器
StreamController streamController;
//ListView是否在滑动
bool isScroll;
VideoItemPageWidget({
@required this.url, //路径
this.streamController,
this.isScroll, //是否播放
this.type = VideoPageEnum.asset, //默认为传递本地视频
});
@override
Widget build(BuildContext context) {
/*
widget.isScroll 当前是否是滚动状态
*/
return widget.isScroll
? Center(
child: Text("加载中..."),
)
//视频播放器页面
: buildLayout();
}
这段代码很简单,通过对ListView滑动的监听,然后通过isScroll变量来标识当前是否滑动,将变量传递给布局来控制,如果正在滑动则显示’加载中’布局,
如果没有在滑动则显示视频播放器页面布局
效果图(1.5)
:
下一章:Flutter 小知识:ListView播放视频列表(二)
涉及内容:
原创不易,您的点赞就是对我最大的支持,留下您的点赞吧~