题记
—— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。
重要消息
本小节讲述:
1 VideoPlayer 视频播放组件使用
2 VideoPlayerController 的使用分析
3 FutureBuilder 的使用分析
4 PageView构建上下滑动的整屏切换页面
5 TabBar 与 TabBarView 构建左右滑动切换的页面
通过 TabBar 与 TabBarView 实现左右切换的页面
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MainFind3Page extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MainFindPage3State();
}
}
class MainFindPage3State extends State with SingleTickerProviderStateMixin {
List<String> tabTextList = ["关注", "推荐"];
List<Tab> tabWidgetList = [];
TabController tabController;
@override
void initState() {
super.initState();
for (var value in tabTextList) {
tabWidgetList.add(Tab(
text: "$value",
));
}
tabController = new TabController(length: tabTextList.length, vsync: this);
}
@override
Widget build(BuildContext context) {
return buildRootBody();
}
Widget buildRootBody() {
return Scaffold(
body: Stack(
children: <Widget>[
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: Container(
color: Colors.black,
),
),
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: buildTableViewWidget(),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
top: 54,
child: buildTabBarWidget(),
),
],
),
);
}
///构建 TabBarView
buildTableViewWidget() {
return TabBarView(
controller: tabController,
children: tabTextList
.map((value) => Container(
alignment: Alignment.center,
child: Text("$value",style: TextStyle(color: Colors.white),),
))
.toList(),
);
}
///构建顶部标签部分
buildTabBarWidget() {
return Container(
///对齐在顶部中间
alignment: Alignment.topCenter,
child: TabBar(
controller: tabController,
tabs: tabWidgetList,
///指示器的颜色
indicatorColor: Colors.white,
///指示器的高度
indicatorWeight: 2.0,
isScrollable: true,
///指示器的宽度与文字对齐
indicatorSize: TabBarIndicatorSize.label,
),
);
}
}
在这里是通过 帧布局 将 TabBar 与 TabBarView 叠在一起的。
效果如下
文章《flutter跨平台开发一点一滴分析系列文章》中 1.3.3 有记录 PageView 的使用案例
我们将上述 【构建 TabBarView】 处代码替换,使用 PageView 来构建 上下整屏页面切换效果
///构建 TabBarView
buildTableViewWidget() {
return TabBarView(
controller: tabController,
children: tabTextList
.map((value) => buildTableViewItemWidget(value))
.toList(),
);
}
/// 用来创建上下滑动的页面
Widget buildTableViewItemWidget(String value) {
List<VideoModel> list =[];
if(value == "推荐"){
list= videoList;
}else{
list = videoList2;
}
return PageView.builder(
/// pageview中 子条目的个数
itemCount:list.length ,
/// 上下滑动
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context,int index){
VideoModel videoModel = list[index];
return buildPageViewItemWidget(value,videoModel);
});
}
这里面用到了 videoList 与 videoList2,保存的数据模型,是在 initState函数中初始化的
///推荐模拟数据
List <VideoModel> videoList =[];
///关注模拟数据
List <VideoModel> videoList2 =[];
@override
void initState() {
super.initState();
...
///创建模拟数据
for (int i = 0; i < 10; i++) {
VideoModel videoModel = new VideoModel();
videoModel.videoName = "推荐测试数据$i";
videoModel.pariseCount = i * 22;
if (i % 3 == 0) {
videoModel.isAttention = true;
videoModel.isLike = true;
} else {
videoModel.isAttention = false;
videoModel.isLike = false;
}
videoModel.videoImag ="";
videoModel.videoUrl ="";
videoList.add(videoModel);
}
for (int i = 0; i < 3; i++) {
VideoModel videoModel = new VideoModel();
videoModel.videoName = "关注测试数据$i";
videoModel.pariseCount = i * 22;
videoModel.isAttention = true;
if (i % 3 == 0) {
videoModel.isLike = true;
} else {
videoModel.isLike = false;
}
videoModel.videoImag ="";
videoModel.videoUrl ="";
videoList2.add(videoModel);
}
}
对于 VideoModel 来讲,就是我们保存视频信息的数据模型了
class VideoModel {
///视频名称
String videoName ='';
///视频链接
String videoUrl ='';
///视频截图
String videoImag ='';
///是否关注
bool isAttention =false;
///关注的个数
num attentCount =0;
///是否喜欢
bool isLike = false;
///点赞的个数
num pariseCount = 0;
///分享的次数
num shareCount=0;
}
在上述代码中我们也使用到了 buildPageViewItemWidget 函数,如下
buildPageViewItemWidget(String value, VideoModel videoModel) {
return FindVideoItemPage(value,videoModel);
}
在这里直接构建 的 FindVideoItemPage ,看如下 FindVideoItemPage 的定义
///播放视频的页面
class FindVideoItemPage extends StatefulWidget {
String tabValue;
VideoModel videoModel;
FindVideoItemPage(this.tabValue, this.videoModel);
@override
State<StatefulWidget> createState() {
return FindVideoItemPageState();
}
}
class FindVideoItemPageState extends State<FindVideoItemPage> {
///创建视频播放控制 器
VideoPlayerController videoPlayerController;
///控制更新视频加载初始化完成状态更新
Future videoPlayFuture;
@override
void initState() {
super.initState();
videoPlayerController =
VideoPlayerController.network(widget.videoModel.videoUrl);
videoPlayFuture = videoPlayerController.initialize().then((_) {
///视频初始完成后
///调用播放
videoPlayerController.play();
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
///播放视频
buildVideoWidget(),
///控制播放视频按钮
buildControllWidget(),
///底部区域的视频介绍
buildBottmFlagWidget(),
///右侧的用户信息按钮区域
buildRightUserWidget(),
],
);
}
@override
void dispose() {
super.dispose();
videoPlayerController.dispose();
}
}
其实 FindVideoItemPage 就是我们 PageView 中构建的子视图了,我们可以看到 在 初始化函数 initState 中 创建了 VideoPlayerController,顾名思义 VideoPlayerController 是用来控制当前页面视频的播放的,在 dispose 中销毁 VideoPlayerController,这个也好理解,就是当前页面都释放掉了,播放的视频当然要停止播放了。
在这里这创建了一个 videoPlayFuture ,是用来监听 VideoPlayerController 初始化状态的,结合 FutureBuilder 来实时更新页面 State,如在方法 buildVideoWidget() 中
///播放视频
buildVideoWidget() {
return FutureBuilder(
future: videoPlayFuture,
builder: (BuildContext contex, value) {
if (value.connectionState == ConnectionState.done) {
///点击事件
return InkWell(
onTap: () {
if (videoPlayerController.value.initialized) {
/// 视频已初始化
if (videoPlayerController.value.isPlaying) {
/// 正播放 --- 暂停
videoPlayerController.pause();
} else {
///暂停 ----播放
videoPlayerController.play();
}
setState(() {});
} else {
///未初始化
videoPlayerController.initialize().then((_) {
videoPlayerController.play();
setState(() {});
});
}
},
///居中
child: Center(
/// AspectRatio 组件用来设定子组件宽高比
child: AspectRatio(
///设置视频的大小 宽高比。长宽比表示为宽高比。例如,16:9宽高比的值为16.0/9.0
aspectRatio: videoPlayerController.value.aspectRatio,
///播放视频的组件
child: VideoPlayer(videoPlayerController),
),
),
);
} else {
return Container(
alignment: Alignment.center,
///圆形加载进度
child: CircularProgressIndicator(),
);
}
},
);
}
FutureBuilder会依赖一个Future,对于FutureBuilder来讲
FutureBuilder({
this.future,
this.initialData,
@required this.builder,
})
future ,FutureBuilder 中依赖的 Future ,通常是一个异步耗时任务,如这里的 videoPlayFuture 是指向 videoPlayerController 的初始化函数initialize(),这是一个异步的耗时操作,
builder ,Widget构建器,该构建器会在Future执行的不同阶段被多次调用,构建格式如下
Function (BuildContext context, AsyncSnapshot snapshot)
/**
* snapshot会包含当前异步任务的状态信息及结果信息 ,
* 比如我们可以通过snapshot.connectionState获取异步任务的状态信息、
* 通过snapshot.hasError判断异步任务是否有错误等等
*/
而通过 snapshot.connectionState 获取的 ConnectionState 状态有以下值:
enum ConnectionState {
/// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
none,
/// 异步任务处于等待状态
waiting,
/// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态。
active,
/// 异步任务已经终止.
done,
}
使用 VideoPlayer,我们首先需要添加依赖
video_player: ^0.6.4
对于 VideoPlayer 来讲,它只接收一个 VideoPlayerController,我们可以通过 VideoPlayerController 来绑定要播放的视频地址
///网络链接
videoPlayerController = VideoPlayerController.network(widget.videoModel.videoUrl);
///本地链接
VideoPlayerController videoPlayerController2 = VideoPlayerController.asset(widget.videoModel.videoUrl);
///File形式的视频
VideoPlayerController videoPlayerController3 = VideoPlayerController.file(File(widget.videoModel.videoUrl));
当 绑定了播放的地址后,可以VideoPlayerController来预加载初始化播放器
videoPlayerController.initialize().then((_) {
///视频初始完成后
///调用播放
videoPlayerController.play();
setState(() {});
});
对于 initialize() 方法来讲,这是一个异步的耗时操作.
VideoPlayerValue 记录了当前视频播放的一些状态信息
VideoPlayerValue videoPlayerValue = videoPlayerController.value;
///是否初始化完成
bool initialized = videoPlayerValue.initialized;
///是否正在播放
bool isPlaying = videoPlayerValue.isPlaying;
///当前播放的视频的宽高比例
double aspectRatio = videoPlayerValue.aspectRatio;
///当前视频是否缓存
bool isBuffer = videoPlayerValue.isBuffering;
///当前视频是否循环
bool isLoop = videoPlayerValue.isLooping;
///当前播放视频的总时长
Duration totalDuration = videoPlayerValue.duration;
///当前播放视频的位置
Duration currentDuration = videoPlayerValue.position;
在这里,我们通过 totalDuration 与 currentDuration 就可实现播放进度的进度条绘制。
完毕