題記
—— 執劍天涯,從你的點滴積累開始,所及之處,必精益求精,即是折騰每一天。
重要消息
本小節講述:
1 VideoPlayer 視頻播放組件使用
2 VideoPlayerController 的使用分析
3 FutureBuilder 的使用分析
4 PageView構建上下滑動的整屏切換頁面
5 TabBar 與 TabBarView 構建左右滑動切換的頁面
1 首先我們來實現頁面的主體部分
通過 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 疊在一起的。
效果如下
2 通過 PageView 來實現上下整屏切換效果
文章《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,
}
3 通過 VideoPlayer 播放視頻
使用 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 就可實現播放進度的進度條繪制。
完畢