flutter开发仿抖音首页面上下滑动切换播放视频效果

题记
—— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。

重要消息


本小节讲述:
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 就可实现播放进度的进度条绘制。


完毕

发布了360 篇原创文章 · 获赞 214 · 访问量 47万+

猜你喜欢

转载自blog.csdn.net/zl18603543572/article/details/104592835