laitimes

The Flutter control encapsulates the video progress bar

author:Programmer sing

Video controller, the style provided by the three parties, sometimes it is difficult to meet our needs, for this situation, we have to encapsulate ourselves on this basis, the article shared today is a very simple controller encapsulation case, including the basic playback pause, full screen and exit full screen, as well as time and progress display, encapsulated event callbacks and the control of various properties, basically can meet most of the business needs, even if not satisfied, you can also expand on this basis.

Let's still follow the convention and briefly list an outline:

1. Basic effect display

2. Specific use and introduction of related attributes

3. Controller packaging considerations

4. Analysis of the functional code of the controller part

5. Summary and source code address

First, the basic effect display

The specific effects, there is nothing to say, are the common styles of the public, from left to right: playback pause button, playback time, playback progress, total time, full screen and exit full screen button.

The Flutter control encapsulates the video progress bar

The functions that can be realized are, the dynamic setting of the icon, the color and size control of the time progress, and the opening of the timer, you can see the second item for details.

The Flutter control encapsulates the video progress bar

2. Introduction to specific use and related attributes

1. Specific use

As a widget, you can use it at will, alone or in combination with a video player.

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. Related attributes

attribute type overview
height double Set the controller height
progressHeight double Progress bar height
videoPlayIcon String Video playback Icon
videoPauseIcon String Video Pause Icon
videoFullScreenIcon String Video full-screen icon
videoExitFullScreenIcon String Exit the full-screen Icon
textStyle TextStyle Text style
backgroundColor Color Background color
progressColor Color Progress color
thumbColor Color Drag the color
thumbRadius double Thumb size
playTimeMarginLeft double The distance from the left of the playback time
playTimeMarginRight double The distance from the left of the playback time
videoTimeMarginLeft double The distance from the left of the video time
videoTimeMarginRight double The distance from the left of the video time
totalTime int Total duration
changeTime int Change the duration
isTimer bool Whether timing is required
onVideoPlayClick ValueChanged<bool> Video playback clicks
onVideoFullScreenClick ValueChanged<bool> Click Click to click the video in full screen
onVideoChanged ValueChanged<int> Slide callbacks
onVideoChangeStart ValueChanged<int> Drag to start
onVideoChangeEnd ValueChanged<int> End of dragging
isPlayed bool Play controls status, pause or start
isFullScreen bool Whether it is full screen

Third, controller packaging considerations

Although the video controller is simple, there are still many factors to consider, such as click play and pause, full-screen and exit full-screen event callbacks, drag progress in addition to changing its own progress but also change the time progress, time conversion of transmission, timing on and off, etc. are all needs to be solved.

1. Basic UI settings

The UI of the controller must be based on the UI draft set by the design students, otherwise it is difficult to drive design changes with technology, but there are special cases. So when encapsulating, it is either based on the UI draft or dynamically configurable, changing the basic style or position through properties.

2. Implementation of drag progress

The drag progress is relatively simple, using the native provided Slider, that is, the slider, similar to SeekBar in Android, it should be noted that the dynamic configuration of attributes such as color.

3. Time conversion and progress binding

Time conversion, need to convert the incoming timestamp, into the time format we need, that is, the format of hours, minutes and seconds, here the use of intl internationalization plug-in, mainly used to format conversion DateFormat.

4. Control of timer

The timer is very simple, instantiate a timer can be, however, when to start, when to pause are we need to consider, in general, directly and the video player to bind, directly change the progress can be, do not use this timing, if you want to use, you can use a property control; In the case of timekeeping, click pause, you need to pause the timing, in addition, you also need to pause the timing after playing; When the drag is completed, you need to turn on the timing, click play, and also need to turn on the timing, so for the timer control block, be clear.

Fourth, the controller part of the function code analysis

1. Basic layout

Quite simply, a horizontal component Row, wrapped 5 sub-components, and the progress bar uses Expanded to occupy the remaining space.

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

Play Icon and Full Screen Icon

In the case of not uploading an icon, directly use the default icon, if passed, then directly use the passed, you need to show the play button or pause button according to the playback status, the full-screen Icon needs to show the corresponding icon according to whether the full-screen state, and at the same time call back click event, VipImage is a previously encapsulated picture component, you can view the previous sharing.

/*
  * 获取播放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,
      );
    }
  }           

Duration and total duration

VipText is a previously encapsulated text component, you can view the past sharing, it should be noted that the incoming time needs to be formatted and converted into the corresponding hour, minute and second structure.

/*
   *获取播放时长
   * */
  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,
    );
  }           

Progress bar in the middle

The progress bar uses Slider, which can be used directly according to the native API, it should be noted that the maximum progress is max, which needs to be bound to the total duration of the setting, and divisions segmentation, which needs to be distinguished by seconds, otherwise when the slide changes, it may cause conflicts with the timing.

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. Time conversion

As mentioned earlier, the intl internationalization plugin is used, and the main use is 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. Timing operation

Timing needs to be noted that when the timing needs to be turned on, such as when the defined attribute is true, the opening action is executed, and when the timing is turned on, our progress is greater than the total duration, we need to pause the timing.

Enable timing scenarios: 1. When the defined attribute is true, enter to directly enable timing. 2. When you click the start playback button, if you use the timing, you need to turn it on, 3. When the playback is finished, drag it again, you also need to turn on the timing.

Pause the timing scenario: 1. Turn off the timer when you click to pause the video, 2. Turn off the timer when the playback ends, 3. When the page is destroyed, you also need to turn off the timing.

/*
  * 开启定时
  * */
  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;
    }
  }           

5. Summary and source code address

The source code is a simple file with an address: https://github.com/AbnerMing888/flutter_widget/blob/master/lib/ui/widget/vip_video_controller.dart

The source code has the previously packaged components, please know that the currently packaged components, styles and icons can be replaced, but there is a position and whether the components show that there is no encapsulation, but the source code has been posted, you can change it on the basis of the source code.