视频进度滑动条的三种实现方式

视频进度滑动条的三种实现方式

HarmonyOS
2024-05-23 23:05:18
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
椰子的笔记

滑动条和进度滑动条

滑动条是一种常见的图形用户界面的界面控件。在应用开发过程中,由于很多图形应用程序需要对值进行调节,如视频播放进度、音量、亮度和阈值等,因此采用滑动条能够与用户进行高效交互。

滑动条分为

进度滑动条

设置滑动条两种样式:

进度滑动条用于播控内容,例如视频、音频的播放,如图1 进度滑动条所示;

设置滑动条用来快速调节设置值,例如音量、亮度、色彩饱和度以及字体大小等。

图1 进度滑动条

无论是进度滑动条还是设置滑动条,都可以分为连续滑动条和间续滑动条两种类型,本文介绍的视频进度滑动条为连续滑动条。更多HarmonyOS滑动条样式和类型的介绍可参考HarmonyOS官方设计文档—滑动条

视频进度滑动条

视频进度滑动条是视频播放控制器中最重要的功能组件之一。它与时间有着十分密切的关系,可以向用户展示视频当前的播放进度,有明显的进程动态,同时又明确了当前视频的状态,可系统自动或人为手动控制播放进度。视频进度滑动条样式多种多样,需要结合实际的使用场景进行设计,常见的视频进度滑动条如图2所示。

图2 常见视频进度滑动条样式

图2左边常规的滑动条样式常见于简易的视频播放器中(如图3 左),适用于短视频类App,此类App对视频进度滑动条的要求相对弱化,因为短视频耗时原本就比较短,几秒到几分钟不等;右边是带文字提示的气泡进度滑动条(如图3 中),通常以气泡(或者对话框,如图3 右)的形式来展示提示文字,生活中常见的还有提供视频预览功能的,这些集成了丰富功能的视频进度滑动条常见于在线视频媒体软件,因为电视剧/电影耗时较长,用户的操作需求较高,视觉优先级自然更高。

图3 进度滑动条示例 左:普通样式 中:带气泡样式 右:带对话框样式

具备哪些功能与特点

我们已经了解了什么是视频进度滑动条,它的作用以及有哪些常见的样式,那在实际应用开发过程中,一个优秀的视频进度滑动条需要具备哪些功能和特点呢?

试想一下,如果你用移动设备或者电脑观看一段视频,没有视频进度滑动条,无法显示当前播放进度、无法进行快进、回退等基本操作,你是否会感到十分焦虑。同样,如果一个视频播放软件提供的视频进度滑动条,你点击或者滑动滑块,视频画面却与你设定的进度并不一致,或者点击、滑动的灵敏度很差,相信你也会对此软件失去耐心。所以,我们的视频进度滑动条不仅得具备显示和控制视频进度等功能,也需要有合理的及时反馈和好的用户体验,从而在一定程度上降低用户的焦虑、提升用户的耐心。



功能类型




功能描述




特点


基础功能

显示进度:视频播放时,自动显示播放进度位置

合理的及时反馈:灵敏度高,进度提示明显、友好等;

好的用户体验: 触控区域最大化,操作便捷,提供多种操作方式等。

点击:用户点击滑动条,滑块会移动到点击位置。

滑动:手指按住滑块,向左或向右滑动;按住滑块之后,手指可挪到滑动条以外区域向左或向右滑动。

点击并滑动:用户点击滑动条并滑动时,滑块首先会移动到点击位置,同时会随手指位置滑动。

扩展功能

支持展示提示文字或预览画面。

滑块及进度条的样式可进行自定义配置。

支持触摸、滑动等手势事件的联动。


实现一个简单的视频进度滑动条仅需要实现基础功能,能显示进度、点击和滑动来控制进度即可。如果有更高的用户操作和视觉需求,还需要具备支持提示文字或预览画面、自定义滑动条样式、支持手势事件联动等扩展功能。无论是基础功能还是扩展功能,在设计时,都需要遵循合理的及时反馈和好的用户体验原则。

三种实现方式

入门:Video组件默认滑动条

在HarmonyOS媒体组件中,Video组件常用于短视频应用和应用内部视频列表页面,其接口使用介绍参见官网API参考-媒体组件-Video章节。Video组件有自带的视频控制器,是用controls属性来控制自带的视频播放控制栏是否显示。使用Video组件是最易上手的视频播放功能实现方式,适合于HarmonyOS应用开发入门学习人员。



名称




参数类型




默认值




描述


controls

boolean

true

控制视频播放的控制栏是否显示。


Video组件打开默认控制栏的代码和实现效果如下所示。代码中将controls属性设置为true即为开启Video组件自带的视频控制器。其中默认的滑动条满足视频进度滑动条的基础功能,可以点击、滑动以及点击的同时滑动来控制视频播放进度。

// VideoDefault.ets 
 
@Entry 
@Component 
struct VideoDefault { 
  private videoController: VideoController = new VideoController() 
 
  build() { 
    Row() { 
      Column() { 
        Video({ 
          src: $rawfile('videoTest.mp4'), 
          controller: this.videoController 
        }) 
          .controls(true) 
          .objectFit(ImageFit.Contain) 
          .loop(false) 
          .autoPlay(true) 
      } 
      .backgroundColor(Color.Black) 
      .width('100%') 
    } 
    .height('100%') 
  } 
}

图6 Video组件自带滑动条效果图

从上述代码和实现效果不难看出,Video组件的默认控制栏实现比较简单,非常适合初学者快速学习视频播放功能的实现。但此默认的样式比较普通且不可更改,在点击或者滑动过程中也没有时间提示,和实际应用中的视频进度滑动条存在一定的差距。如果开发者对于视频进度滑动条有更高的视觉和功能诉求,Video组件可能无法满足。

初级:基于Slider的视频进度滑动条

视频进度滑动条也可以基于Slider组件来实现,这其实也是自定义方式之一,需要思考如何实现Slider组件与视频的同步,这也是在实现视频进度滑动条的过程中最重要的一点。实现是需要将视频的时长与滑动条的进度进行关联,且必须始终保持视频播放的进度与滑动条的位置一致,具体表现为点击或者滑动滑块时,需要同步更新视频播放的进度;视频自动播放时,也要同步更新滑动条显示的进度。那如何才能实现视频播放进度与滑动条显示的同步呢?

图7 Slider样式(左:OutSet;右:InSet)

首先,我们需要了解Slider组件的基本概念。HarmonyOS提供了滑动条组件Slider,常用于调节设置值场景,如调节音视频进度、设备音量和亮度等。Slider组件有OutSet(滑块在滑轨上)和InSet(滑块在滑轨内)两种样式,如图7所示。在应用开发过程中,可以设置Slider的当前进度值,取值范围,滑动步长和滑动条方向,还可以设置滑块、滑轨以及已滑动部分的颜色,以及设置滑动时显示气泡提示百分比等。其中,部分重要参数如下表所示。



参数名




参数类型




必填




默认值




参数描述


value

number

0

当前进度值。

min

number

0

设置最小值。

max

number

100

设置最大值。

style

SliderStyle

SliderStyle.OutSet

设置Slider的滑块样式。


从前面的介绍中我们知道了Slider组件有当前进度值value,表示滑块当前位置,有最大值max,表示滑动条总长度。而对于一个视频来说,很显然它有其总时长duration(s)和播放的当前进度时间currentTime(s)。接下来我们就要来看下如何才能让Slider组件的参数与视频的参数保持同步。

Slider组件关键参数

我们可以将滑动条的总长max定义为视频总时长duration(s),即:

max = duration

则此时,视频当前进度currentTime与滑块当前位置value的关系是:

currentTime = value

手动操作滑动条时,用滑动条当前值value更新视频进度currentTime;

视频自动播放时,用视频进度值currentTime更新滑动条位置value。

首先,我们来看下Video组件的关键实现:

  // SliderPage.ets 
 
  Video({ 
	src: $rawfile('videoTest.mp4'), 
	controller: this.videoController // VideoController实例 
  }) 
	.controls(false) 
	.autoPlay(true) 
	.loop(true) 
	.objectFit(ImageFit.Contain) 
	.onPrepared((event) => { 
	  this.maxValue = event.duration 
	  this.durationText = convertToTimeFormat(this.maxValue.toFixed()) // 转成时间格式AA:BB 
	}) 
	.onUpdate((event) => { 
	  this.currentValue = event.time 
	})

1. 自定义实现滑动条的方案都需要将.controls设置为false,关闭Video组件的自带控制栏;

2. 在视频准备onPrepared回调中,获取视频总时长event.duration,将其赋值给滑动条的最大值maxValue,并将其转换成时间文本durationText用于后面显示滑动条末尾的总时间;

3. 在视频进度变化(如自动播放)的onUpdate回调中用当前时间更新currentValue,同步滑块的进度。

其中,this.maxValue、this.durationText、this.currentValue均为Video组件所在页面的成员变量,分别表示滑动条的总长度、视频总时长时间文本和滑动条当前进度值。

接下来,我们来看下Slider组件的关键实现:

	// SliderPage.ets 
 
  Slider({ 
	value: this.currentValue, //当前进度值 
	min: 0, 
	max: this.maxValue, 
	style: SliderStyle.OutSet 
  }) 
	.width('75%') 
	.blockColor(Color.White) 
	.trackColor($r('app.color.trackColor')) 
	.selectedColor($r('app.color.selectedColor')) 
	.showSteps(false) 
	.showTips(true) 
	.onChange((value: number, mode: SliderChangeMode) => { 
		this.videoController.setCurrentTime(parseInt(value.toString()), SeekMode.Accurate) 
	})

1. 初始化Slider的当前值、最大最小值、滑动步长等;

2. 滑动滑块时,在Slider的onChange回调中根据Slider当前进度值value同步更新视频播放的进度。

Slider组件实现的视频进度滑动条是可以用气泡显示视频播放百分比的,这里是用到了Slider组件的showTips属性,将其设置为true时即可实现图中的气泡百分比效果。

除了气泡外,开发者还可以通过trackThickness属性设置Slider组件滑轨的宽度(粗细),此设置常用于在用户没有操作仅观看视频时,可以将滑轨宽度降低,若是在用户滑动过程中,将滑轨宽度增加,同时可结合透明度的变化设置从而实现动态显示滑动条的效果。本文示例中暂未给出滑动条的宽度变化、透明度变化等动态效果,开发者若感兴趣,可加以思考研究如何实现。

基于Slider组件实现的视频进度滑动条基本满足了视频进度控制的基础功能和扩展功能要求。相较于Video组件自带控制栏中的滑动条,Slider组件实现的滑动条要更加美观、灵活,可以设置气泡提示、滑动条的颜色、方向等样式。但Slider组件的灵活性也是有限的,例如,气泡格式中只能显示百分比,OutSet类型滑动条的滑块也是固定为圆形等。

气泡里文本显示时间、用体现某个品牌的图标作为滑块等,这些都是在实际生活中很常见的场景。如果开发者希望实现更加个性化的视频进度滑动条,则需要通过结合通用组件和事件等灵活性更高的自定义方式实现。

进阶: 自定义视频进度滑动条

自定义实现视频进度滑动条其实有很多方式,这里介绍的是结合Row组件、触摸事件和拖动手势的方式,实现思路和代码可作为参考。开发者也可尝试其他自定义方式,能满足视频进度滑动条的功能要求皆可。

本文前面已经提到过,视频进度滑动条最重要的就是保持视频进度和滑动条位置的同步。我们在自定义滑动条过程中,需要理清视频时长、视频当前进度时间、滑动条总长度、滑动条当前位置这几个数据之间的关系,自定义滑动条的关键参数如图9所示。视频还是有其总时长duration(s)和播放的当前进度时间currentTime(s)。

图9 自定义滑动条关键参数

手动操作滑动条时,视频当前进度更新:

currentTime = positionX / maxSliderLength * duration

视频自动播放时,更新滑动条位置:

positionX = event.time / duration * this.maxSliderLength

首先,我们还是来看下Video组件的实现:

// CustomVideoPage.ets 
 
import { CustomSlider } from './CustomSlider'; 
import hilog from '@ohos.hilog'; 
import {convertToTimeFormat} from '../utils/DataConversion' 
 
@Entry 
@Component 
struct CustomVideoPage { 
  @State maxSliderLength: number = 0 
  @State positionX: number = 0 
  @State duration: number = 0; 
  @State currentTime: number = 0 
 
  @State showTips: boolean = false 
 
  private videoController: VideoController = new VideoController() 
 
  aboutToAppear() { 
    let windowClass = null; 
    globalThis.windowStage.getMainWindow((err, data) => { 
      windowClass = data; 
 
      let promise = windowClass.getProperties(); 
      promise.then((data) => { 
        this.maxSliderLength = px2vp(data.windowRect.width) * 0.9 
      }) 
    }) 
  } 
 
  build() { 
    Column() { 
      Stack() { 
        Video({ 
          src: $rawfile('videoTest.mp4'), 
          controller: this.videoController 
        }) 
          .controls(false) 
          .autoPlay(true) 
          .objectFit(ImageFit.Contain) 
          .loop(true) 
          .onPrepared((event) => { 
            // 获取视频资源时长,单位(秒) 
            this.duration = event.duration 
          }) 
          .onUpdate((event) => { 
            if (!this.showTips) { 
              // 更新滑块进度值 
              this.positionX = event.time / this.duration * this.maxSliderLength 
              hilog.info(1, 'demo', 'on update modify currentValue ' + this.positionX) 
            } 
          }) 
 
        CustomSlider({ 
          positionX: $positionX, 
          maxSliderLength: $maxSliderLength, 
          duration: $duration, 
          currentTime: $currentTime, 
 
          videoController: this.videoController, 
          showTipsChange: (showTips) => { 
            this.showTips = showTips 
          } 
        }) 
      } 
      .backgroundColor(Color.Black) 
 
      if (this.showTips) { 
        // 用Row画出显示框,提示当前时间 
        Row() { 
          Text(convertToTimeFormat(this.currentTime).toString()) // 转成时间格式AA:BB 
            .fontSize(20) 
            .fontWeight(FontWeight.Bold) 
        } 
        .justifyContent(FlexAlign.Center) 
        .width(100) 
        .height(80) 
        .backgroundColor('#ffe6bbee') 
        .borderRadius(20) 
        .position({ x: '40%', y: '50%' }) 
        .opacity(0.5) 
      } 
    } 
    .justifyContent(FlexAlign.Center) 
    .width('100%') 
    .height('100%') 
  } 
}

Video组件的重要实现部分还是在onPrepared和onUpdate中:

1. 首先,在页面加载时获取屏幕宽度,从而定义滑动条的总长度maxSliderLength;

2. 其次,在onPrepared回调中定义滑动条总长度duration;

3. 最后,视频自动播放时,在onUpdate回调中更新滑动条位置positionX。

接下来我们看下自定义滑动条组件的实现:

// CustomSlider.ets 
 
import hilog from '@ohos.hilog'; 
 
@Component 
export struct CustomSlider { 
  @Link maxSliderLength: number 
  @Link positionX: number 
  @Link duration: number 
  @Link currentTime: number 
 
  @State @Watch('changeShowTips') showTips: boolean = false 
 
  private videoController: VideoController 
  private lastOffset: number = 0 
 
  private showTipsChange: (showTips: boolean) => void; 
 
  changeShowTips() { 
    this.showTipsChange(this.showTips) 
  } 
 
  build() { 
    // 1.用Row组件和Circle画出滑动条及滑块 
    Row() { 
      Row().width(this.positionX).height(10).backgroundColor('#ffee75c6') 
      Row().width(this.maxSliderLength - this.positionX).height(10).backgroundColor('#AFEEEE') 
      Row() { 
        Circle().height(20).width(20).position({ x: -10, y: -5 }).fill(Color.Orange) 
      } 
      .width(30).height(35).position({ x: this.positionX }) 
    } 
    .onTouch((event: TouchEvent) => { 
      // 2.定义其触摸事件onTouch 
      if (event.type === TouchType.Down) { 
        this.showTips = true 
 
        var tmpVal = event.touches[0].x / this.maxSliderLength * this.duration 
        this.positionX = Math.floor(tmpVal) * (this.maxSliderLength / this.duration) 
 
        this.currentTime = this.positionX / this.maxSliderLength * this.duration 
        this.videoController.setCurrentTime(parseInt(this.currentTime.toString()), SeekMode.Accurate); 
      } 
      hilog.info(1, 'demo', 'on change modify currentValue ' + this.positionX) 
 
      if (event.type === TouchType.Up) { 
        this.showTips = false 
      } 
    }) 
    .width('95%') 
    .position({ x: '5%', y: '88%' }) 
    .gesture( 
      // 3.拖动手势PanGesture,更新滑块当前位置和视频当前进度时间 
    PanGesture({}) 
      .onActionUpdate((event: GestureEvent) => { 
        this.showTips = true // 4.在点击滑块或滑块拖动过程中,实时显示当前时间。 
 
        if (this.positionX < 0) { 
          this.positionX = 0 
        } else if (this.positionX > this.maxSliderLength) { 
          this.positionX = this.maxSliderLength 
        } else { 
          this.positionX = this.positionX + event.offsetX - this.lastOffset 
          this.lastOffset = event.offsetX 
        } 
 
        // 更新视频的播放时间 
        this.currentTime = this.positionX / this.maxSliderLength * this.duration 
        this.videoController.setCurrentTime(parseInt(this.currentTime.toString()), SeekMode.Accurate); 
      }) 
      .onActionEnd(() => { 
        this.lastOffset = 0 
        this.showTips = false 
      }) 
    ) 
  } 
}

自定义滑动条组件的实现主要分为以下几个步骤:

1. 用Row组件和Circle画出滑动条及滑块;

2. 定义其触摸事件onTouch;

3. 拖动手势PanGesture,更新滑块当前位置和视频当前进度时间;

4. 用Row组件画出显示框,在点击滑块或滑块拖动过程中,实时显示提示当前时间。

具体代码和实现效果如下所示。

图10 自定义视频进度滑动条效果图

自定义的方式是比较推荐的视频进度滑动条实现方式,它不仅可以满足视频进度滑动条的基本功能和扩展功能要求,而且滑动条的大小、颜色、滑块的样式、时间提示框的样式和内容等都是可以根据开发者的需求定义的,灵活性和美观相较于起前面两种实现方式都要好很多,例如可以将滑块设置成图片,根据需要调整显示时间弹框的大小和颜色,如图11所示。本文仅提供了一种结合Row组件、触摸事件和拖动手势的方式,开发者也可考虑使用其他自定义方式,比如采用绘制组件Line、画布组件以及拖拽事件等,也可以实现视频进度滑动条。

图11 自定义视频进度滑动条样式

总结

本文重点介绍了视频进度滑动条的基础知识和三种实现方式:

1.Video组件自带控制器的默认滑动条方式,它适用于初学者学习HarmonyOS视频应用开发;

2.基于Slider组件的视频进度滑动条方式,它适用于短视频类对视频进度滑动条要求相对弱化的应用开发;

3.完全自定义方式,如果是复杂度较高的视频应用开发,推荐使用自定义方式实现视频进度滑动条。

已于2024-5-24 22:46:27修改
分享
微博
QQ
微信
回复
2024-05-24 22:45:20
相关问题
解码后数据帧送显三种方式
234浏览 • 1回复 待解决
candidate 会在哪三种情况下退出?
3843浏览 • 1回复 待解决
如何实现带刻度进度
489浏览 • 1回复 待解决
如何实现带图片进度
704浏览 • 1回复 待解决
弧形进度实现,有人知道方法吗?
659浏览 • 1回复 待解决
Progress进度如何实现渐变色?
581浏览 • 1回复 待解决
基于Progress组件进度
429浏览 • 1回复 待解决
实现一个发送进度通知方法
293浏览 • 1回复 待解决
如何实现一个月食样式进度
270浏览 • 1回复 待解决
使用Promise实现串行调用方式
939浏览 • 1回复 待解决
服务卡片进度如何停止动画
8752浏览 • 1回复 待解决