Flutter控件封装之视频进度条

视频控制器,三方所提供的样式,有时很难满足我们的需求,对于此情况,我们不得不在此基础上自行封装,今天所分享的文章就是一个很简单的控制器封装案例,包含了基本的播放暂停,全屏和退出全屏,以及时间和进度的展示,封装了事件回调以及各个属性的控制,基本上可以满足大部分的业务需求,即便不满足,大家也可以在此基础之上拓展。

我们还是按照惯例,简单罗列一个大纲:

1、基本的效果展示

2、具体使用和相关属性介绍

3、控制器封装考虑因素

4、控制器部分功能代码刨析

5、总结及源码地址

一、基本的效果展示

具体的效果,没什么好说的,都是大众常见的样式,依次从左到右为:播放暂停按钮,播放时间,播放进度,总的时间,全屏及退出全屏按钮。

可以实现的功能有,图标的动态设置,时间进度的颜色及大小控制,以及定时器的开启,具体的可以看第二项。

二、具体使用和相关属性介绍

1、具体使用

作为一个Widget,大家可以随意使用,单独亦或者和视频播放器绑定使用。

VipVideoController(
        totalTime: 1000 * 60,
        backgroundColor: Colors.red,
        progressColor: Colors.amber,
        thumbColor: Colors.red,
        textStyle: TextStyle(color: Colors.red),
        onVideoPlayClick: (isPlay) {
          print("当前播放按钮状态$isPlay");
        },
        onVideoFullScreenClick: (isFullScreen) {
          print("当前全屏按钮状态$isFullScreen");
        },
        onVideoChanged: (position) {
          //返回毫秒
          print("当前拖拽的进度$position");
        }
      )

2、相关属性

属性

类型

概述

height

double

设置控制器高度

progressHeight

double

进度条高度

videoPlayIcon

String

视频播放Icon

videoPauseIcon

String

视频暂停Icon

videoFullScreenIcon

String

视频全屏Icon

videoExitFullScreenIcon

String

退出全屏Icon

textStyle

TextStyle

文本样式

backgroundColor

Color

背景颜色

progressColor

Color

进度颜色

thumbColor

Color

拖动颜色

thumbRadius

double

thumb大小

playTimeMarginLeft

double

播放时间距离左边的距离

playTimeMarginRight

double

播放时间距离左边的距离

videoTimeMarginLeft

double

视频时间距离左边的距离

videoTimeMarginRight

double

视频时间距离左边的距离

totalTime

int

总时长

changeTime

int

改变时长

isTimer

bool

是否需要定时

onVideoPlayClick

ValueChanged<bool>

视频播放点击

onVideoFullScreenClick

ValueChanged<bool>

视频全屏点击点击

onVideoChanged

ValueChanged<int>

滑动回调

onVideoChangeStart

ValueChanged<int>

拖动开始

onVideoChangeEnd

ValueChanged<int>

拖动结束

isPlayed

bool

播放控制状态,暂停还是开始

isFullScreen

bool

是否是全屏

三、控制器封装考虑因素

视频控制器虽然说简单,但需要考虑的因素还是比较多的,比如点击播放和暂停,全屏和退出全屏的事件回调,拖动进度除了更改自身进度也要更改时间进度,传递的时间换算,定时的开启和关闭等等都是需要解决的。

1、基本的UI设定

控制器的UI一定是基于设计同学所定的UI稿,否则就要以技术驱动设计更改,一般很难,不过也有特殊的案例存在。所以在封装的时候,要么基于UI稿,要么就是动态可配置,通过属性更改基本的样式或者位置。

2、拖动进度的实现

拖动进度就比较简单了,使用的是原生提供的Slider,也就是滑杆,类似于Android中的SeekBar,需要注意的是,颜色等属性的动态配置。

3、时间的换算和进度的绑定

时间的换算,需要把传入的时间戳,转化为我们所需要的时间格式,也就是时分秒的格式,这里使用了intl国际化的插件,主要用到到格式转换DateFormat。

4、定时器的控制

定时器很简单,实例化一个Timer即可,但是,什么时候开始,什么时候暂停都是我们需要考虑的,一般情况下,直接和视频播放器进行绑定,直接更改进度即可,就不用这个定时,如果要用,可以用一个属性控制;在需要定时的情况下,点击暂停,需要暂停定时,除此之外播放完毕后也需要暂停定时;当拖动完毕后,需要开启定时,点击播放,也需要开启定时,所以,对于定时器控制这一块,一定要缕清楚。

四、控制器部分功能代码刨析

1、基本的布局

很简单,一个横向的组件Row,包裹了5个子组件,进度条使用Expanded,用于占有剩余的空间。

 return SizedBox(
        height: widget.height,
        child: Row(
          children: [
            getPlayIcon(), //开始和暂停
            getPlayTime(timeStampToStringDate(_progress)), //时间
            Expanded(child: getSliderTheme()), //进度
            getVideoTime(timeStampToStringDate(widget.totalTime!)), //时间
            getFullScreenIcon() //全屏
          ],
        ));

播放Icon和全屏Icon

未传Icon情况下,直接使用默认的Icon,如果传递了,那么直接使用传递的,需要根据播放状态展示播放按钮还是暂停按钮,全屏Icon需要根据是否全屏状态,来展示对应的图标,同时回调点击事件,VipImage是之前封装的图片组件,大家可以查看以往的分享。

  /*
  * 获取播放Icon
  * */
  Widget getPlayIcon() {
    if (widget.videoPlayIcon == null) {
      return InkWell(
        onTap: onPlayClick,
        child: Icon(_isPlayed ? Icons.pause : Icons.play_arrow),
      );
    } else {
      return VipImage(
        _isPlayed ? widget.videoPlayIcon : widget.videoPauseIcon,
        onClick: onPlayClick,
      );
    }
  }


/*
  * 获取全屏Icon
  * */
  Widget getFullScreenIcon() {
    if (widget.videoFullScreenIcon == null) {
      return InkWell(
        onTap: onFullScreenClick,
        child: Icon(_isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen),
      );
    } else {
      return VipImage(
        _isFullScreen
            ? widget.videoFullScreenIcon
            : widget.videoExitFullScreenIcon,
        onClick: onFullScreenClick,
      );
    }
  }

播放时长和总时长

VipText是之前封装的文本组件,大家可以查看以往的分享,需要注意的是传入的时间需要格式化处理,转化为对应的时分秒结构。

  /*
   *获取播放时长
   * */
  Widget getPlayTime(String text) {
    return VipText(
      text,
      style: widget.textStyle,
      marginLeft: widget.playTimeMarginLeft,
      marginRight: widget.playTimeMarginRight,
    );
  }

 /*
   *获取总的播放时长
   * */
  Widget getVideoTime(String text) {
    return VipText(
      text,
      style: widget.textStyle,
      marginLeft: widget.videoTimeMarginLeft,
      marginRight: widget.videoTimeMarginRight,
    );
  }

中间的进度条

进度条使用的是Slider,直接按照原生的Api使用即可,需要注意,最大的进度也就是max,需要和设置的总时长绑定,还有divisions分段,需要以秒作为区分,否则在滑动改变的时候,有可能会和定时造成冲突。

Widget getSliderTheme() {
    var divisions = widget.totalTime! / 1000;
    return SliderTheme(
      data: SliderThemeData(
          //高度
          trackHeight: widget.progressHeight,
          //去掉长按光晕
          overlayColor: Colors.transparent,
          //背景颜色
          inactiveTrackColor: widget.backgroundColor,
          activeTrackColor: widget.progressColor,
          thumbColor: widget.thumbColor,
          thumbShape:
              RoundSliderThumbShape(enabledThumbRadius: widget.thumbRadius)),
      child: Slider(
        min: 0,
        max: widget.totalTime!.toDouble(),
        value: _progress.toDouble(),
        divisions: divisions.toInt(),
        onChangeStart: (progress) {
          if (widget.onVideoChangeStart != null) {
            widget.onVideoChangeStart!(progress.toInt());
          }
        },
        onChangeEnd: (progress) {
          if (widget.onVideoChangeEnd != null) {
            widget.onVideoChangeEnd!(progress.toInt());
          }
          if (_isPlayed) {
            //播放状态下,如果定时,才会执行
            _startTimer();
          }
        },
        onChanged: (double value) {
          setState(() {
            _progress = value.toInt();
          });
          //回调当前进度
          if (widget.onVideoChanged != null) {
            widget.onVideoChanged!(_progress);
          }
        },
      ),
    );
  }

2、时间转换

前边有说过使用的是intl国际化插件,主要用到的是dateFormat.format()。

  /*
  * 时间戳转换时间
  * */
  String timeStampToStringDate(int time) {
    String format = time < 1000 * 60 * 60 ? TimeUtil.m_s : TimeUtil.h_m_s;
    return TimeUtil.getTimeStampToStringDate(time, format: format);
  }


  /*
  * 时间戳转时间
  * */
  static String getTimeStampToStringDate(int timeStamp,
      {String format = y_M_d}) {
    var dateFormat = DateFormat(format);
    var dateTime = DateTime.fromMillisecondsSinceEpoch(timeStamp);
    return dateFormat.format(dateTime);
  }

3、定时操作

定时需要注意的是,在需要定时的时候再开启,比如定义的属性为true时,就执行开启动作,当开启定时后,我们的进度大于总时长时,就需要暂停定时。

开启定时场景:1、定义的属性为true时,进入直接开启定时。2、当点击开始播放按钮时,如果使用定时,就要开启,3、当播放完毕后,再次拖动,也需要开启定时。

暂停定时场景:1、点击暂停视频时,关闭定时,2、播放结束时,关闭定时,3、页面销毁时,也需要关闭定时。

/*
  * 开启定时
  * */
  void _startTimer() {
    if (widget.isTimer! && _timer == null) {
      //开启定时,一秒执行一次
      _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
        if (_progress >= widget.totalTime!) {
          _pauseTimer();
        } else {
          setState(() {
            _progress += 1000;
          });
          widget.onVideoChanged!(_progress);
        }
      });
    }
  }

  /*
  * 暂停定时
  * */
  void _pauseTimer() {
    if (_timer != null) {
      _timer!.cancel(); //取消计时器
      _timer = null;
    }
  }

五、总结及源码地址

源码是一个简单的文件,地址:https://github.com/AbnerMing888/flutter_widget/blob/master/lib/ui/widget/vip_video_controller.dart

源码中有用到之前封装的组件,请大家悉知,目前所封装的组件,样式和图标都是可以更换的,但是有一个就是位置还有组件是否显示没有封装,不过源码已经贴出,大家可以在源码基础之上进行更改。

猜你喜欢

转载自blog.csdn.net/ming_147/article/details/131409818
今日推荐