Flutter 小知识:ListView播放视频列表(一)

誓言用来拴骚动的心,终就拴住了虚空。山林不向四季起誓,荣枯随缘;海洋不需对沙岸承诺,遇合尽兴。连语言都应该舍弃,你我之间,只有干干净净的缄默,与存在。

免费参考:

Flutter 轮子:视频广告倒计时页面

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播放视频列表(二)

涉及内容:

Flutter 轮子:视频广告倒计时页面

Flutter ListView使用

完整代码

原创不易,您的点赞就是对我最大的支持,留下您的点赞吧~



评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

s10g

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值