天天看點

Flutter控件封裝之視訊進度條

作者:程式員一鳴

視訊控制器,三方所提供的樣式,有時很難滿足我們的需求,對于此情況,我們不得不在此基礎上自行封裝,今天所分享的文章就是一個很簡單的控制器封裝案例,包含了基本的播放暫停,全屏和退出全屏,以及時間和進度的展示,封裝了事件回調以及各個屬性的控制,基本上可以滿足大部分的業務需求,即便不滿足,大家也可以在此基礎之上拓展。

我們還是按照慣例,簡單羅列一個大綱:

1、基本的效果展示

2、具體使用和相關屬性介紹

3、控制器封裝考慮因素

4、控制器部分功能代碼剖析

5、總結及源碼位址

一、基本的效果展示

具體的效果,沒什麼好說的,都是大衆常見的樣式,依次從左到右為:播放暫停按鈕,播放時間,播放進度,總的時間,全屏及退出全屏按鈕。

Flutter控件封裝之視訊進度條

可以實作的功能有,圖示的動态設定,時間進度的顔色及大小控制,以及定時器的開啟,具體的可以看第二項。

Flutter控件封裝之視訊進度條

二、具體使用和相關屬性介紹

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

源碼中有用到之前封裝的元件,請大家悉知,目前所封裝的元件,樣式和圖示都是可以更換的,但是有一個就是位置還有元件是否顯示沒有封裝,不過源碼已經貼出,大家可以在源碼基礎之上進行更改。