首页 > 其他分享 >管理组件状态

管理组件状态

时间:2024-01-28 21:32:57浏览次数:19  
标签:状态 自定义 ... 视频 管理 组件 弹窗 ets


在应用中,界面通常都是动态的。如图1所示,在子目标列表中,当用户点击目标一,目标一会呈现展开状态,再次点击目标一,目标一呈现收起状态。界面会根据不同的状态展示不一样的效果。

图1 展开/收起目标项

管理组件状态_ide

ArkUI作为一种声明式UI,具有状态驱动UI更新的特点。当用户进行界面交互或有外部事件引起状态改变时,状态的变化会触发组件自动更新。所以在ArkUI中,我们只需要通过一个变量来记录状态。当改变状态的时候,ArkUI就会自动更新界面中受影响的部分。

管理组件状态_子目标_02

ArkUI框架提供了多种管理状态的装饰器来修饰变量,使用这些装饰器修饰的变量即称为状态变量。

在组件范围传递的状态管理常见的场景如下:

场景

装饰器

组件内的状态管理

@State

从父组件单向同步状态

@Prop

与父组件双向同步状态

@Link

跨组件层级双向同步状态

@Provide和@Consume

在组件内使用@State装饰器来修饰变量,可以使组件根据不同的状态来呈现不同的效果。若当前组件的状态需要通过其父组件传递而来,此时需要使用@Prop装饰器;若是父子组件状态需要相互绑定进行双向同步,则需要使用@Link装饰器。使用@Provide和@Consume装饰器可以实现跨组件层级双向同步状态。

在实际应用开发中,应用会根据需要封装数据模型。如果需要观察嵌套类对象属性变化,需要使用@Observed和@ObjectLink装饰器,因为上述表格中的装饰器只能观察到对象的第一层属性变化。@Observed和@ObjectLink装饰器的具体使用方法可参考@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

另外,当状态改变,需要对状态变化进行监听做一些相应的操作时,可以使用@Watch装饰器来修饰状态。

组件内的状态管理:@State

实际开发中由于交互,组件的内容呈现可能产生变化。当需要在组件内使用状态来控制UI的不同呈现方式时,可以使用@State装饰器。以任务管理应用为例,当点击子目标列表的其中一项,列表项会展开。当再次点击同一项,列表项会收起。所以,对于某一个列表项来说,它的呈现方式会受列表项是否展开这个状态影响。

图2 展开/收起目标项

管理组件状态_ide_03

将是否展开这个状态定义为isExpanded变量,当其值为false表示目标项收起,值为true时表示目标项展开。

此时,需要使用@State装饰器修饰isExpanded,使其成为目标项内部的状态变量。通过@State装饰后,框架内部会建立数据与视图间的绑定,

当isExpanded状态变化时,目标项会随之展开或收起。

图3 定义是否展开状态

管理组件状态_子目标_04

其具体实现只要用@State修饰isExpanded变量,定义是否展开状态。然后通过条件渲染,实现是否显示进度调整面板和列表项的高度变化。最后,监听列表项的点击事件,在onClick回调中改变isExpanded状态。

这样就实现了对相同列表项点击时,列表项的展开和收起功能。当用户反复点击同一个列表项时,组件内的isExpanded状态变化,列表项会自动更新。

 

 

@Component
export default struct TargetListItem {
@State isExpanded: boolean = false;
...

build() {
...
Column() {
...
if (this.isExpanded) {
Blank()
ProgressEditPanel(...)
}
      }
.height(this.isExpanded ? $r('app.float.expanded_item_height')                  
: $r('app.float.list_item_height'))
.onClick(() => {
...
this.isExpanded = !this.isExpanded;
...
)
...
}
}

从父组件单向同步状态:@Prop

当子组件中的状态依赖从父组件传递而来时,需要使用@Prop装饰器,@Prop修饰的变量可以和其父组件中的状态建立单向同步关系。当父组件中状态变化时,该状态值也会更新至@Prop修饰的变量;对@Prop修饰的变量的修改不会影响其父组件中的状态。

图4 列表的编辑模式

管理组件状态_ide_05

如图4所示,在目标管理应用中,当用户点击子目标列表的“编辑”文本,列表进入编辑模式,点击取消,列表退出编辑模式。

整个列表是自定义组件TargetList,顶部是文本显示区域,主要是Text组件,底部是一个Button组件。中间区域则是用来显示每个目标项,目标项是自定义组件TargetListItem。

从图中可以看出,TargetListItem是TargetList的子组件。TargetList是TargetListItem父组件。

图5 TargetList和TargetListItem

管理组件状态_自定义组件_06

对于父组件TargetList,其顶部显示的文本和底部按钮会随编辑模式的变化而变化,因此父组件拥有编辑模式状态。

对于子组件TargetListItem,其最右侧是否预留位置和显示勾选框也会随编辑模式变化,因此子组件也拥有编辑模式状态。

但是是否进入编辑模式,其触发点是在用户点击列表的“编辑”或取消按钮,状态变化的源头仅在于父组件TargetList。当父组件TargetList中的编辑模式变化时,子组件TargetListItem的编辑模式状态需要随之变化。

图6 从父组件单向同步isEditMode状态

管理组件状态_子目标_07

在父组件TargetList中可以定义一个是否进入编辑模式的状态,即用@State修饰isEditMode。@State修饰的变量不仅是组件内部的状态,也可以作为子组件单向或双向同步的数据源。ArkUI提供了@Prop装饰器,@Prop修饰的变量可以和其父组件中的状态建立单向同步关系,所以用@Prop修饰子组件TargetListItem中的isEditMode变量。

在父组件TargetList中,用@State修饰isEditMode,定义编辑模式状态。然后利用条件渲染实现根据是否进入编辑模式,显示不同的文本和按钮。同时,在父组件中需要在用户点击时改变状态,触发界面更新。

当点击“编辑”事件发生时,进入编辑模式,显示取消、全选文本和勾选框,同时显示删除按钮;当点击“取消”事件发生时,退出编辑模式,显示“编辑”文本和“添加子目标”按钮。

 

 

@Component
export default struct TargetList {
@State isEditMode: boolean = false;
  ...
build() {
Column() {
Row() {
        ...
if (this.isEditMode) {
Text($r('app.string.cancel_button'))
.onClick(() => {
this.isEditMode = false;
  ...
})
 ...
Text($r('app.string.select_all_button'))...
Checkbox()...
} else {
Text($r('app.string.edit_button'))
.onClick(() => {
this.isEditMode = true;
})
              ...
}
        ...
      }
      ...
List({ space: CommonConstants.LIST_SPACE }) {
ForEach(this.targetData, (item: TaskItemBean, index: number) => {
ListItem() {
TargetListItem({
isEditMode: this.isEditMode,
              ...
})
}
, (item, index) => JSON.stringify(item) + index)
}
      ...
if (this.isEditMode) {
Button($r('app.string.delete_button'))
} else {
Button($r('app.string.add_task'))
}
    }
    ...
}
}

在子组件TargetListItem中,使用@Prop修饰子组件的isEditMode变量,定义子组件的编辑模式状态。然后同样根据是否进入编辑模式,控制目标项最右侧是否预留位置和显示勾选框。

 

 

@Component
export default struct TargetListItem {
@Prop isEditMode: boolean;
 ...
Column() {
...
 }
.padding({
...
right: this.isEditMode ? $r('app.float.list_edit_padding') 
: $r('app.float.list_padding')
})
...

if (this.isEditMode) {
Row() {
Checkbox()...
}
}
...
}

最后,最关键的一步就是要在父组件中使用子组件时,将父组件的编辑模式状态this.isEditMode传递给子组件的编辑模式状态isEditMode。

 

 

@Component
export default struct TargetList {
@State isEditMode: boolean = false;
  ...
build() {
Column() {
      ...
List({ space: CommonConstants.LIST_SPACE }) {
ForEach(this.targetData, (item: TaskItemBean, index: number) => {
ListItem() {
TargetListItem({
isEditMode: this.isEditMode,
              ...
})
}
, (item, index) => JSON.stringify(item) + index)
}
      ...
    }
    ...
}
}

与父组件双向同步状态:@Link

若是父子组件状态需要相互绑定进行双向同步时,可以使用@Link装饰器。父组件中用于初始化子组件@Link变量的必须是在父组件中定义的状态变量。

图7 切换目标项

管理组件状态_子目标_08

在目标管理应用中,当用户点击同一个目标,目标项会展开或者收起。当用户点击不同的目标项时,除了被点击的目标项展开,同时前一次被点击的目标项会收起。

如图7所示,当目标一展开时,点击目标三,目标三会展开,同时目标一会收起。再点击目标一时,目标一展开,同时目标三会收起。

从目标一切换到目标三的流程中,关键在于最后目标一的收起,当点击目标三时,目标一需要知道点击了目标三,目标一才会收起。

图8 子目标列表目标项位置索引

管理组件状态_子目标_09

在子目标列表中,每个列表项都有其位置索引值index属性,表示目标项在列表中的位置。index从0开始,即第一个目标项的索引值为0,第二个目标项的索引值为1,以此类推。此外,clickIndex用来记录被点击的目标项索引。当点击目标一时,clickIndex为0,点击目标三时,clickIndex为2。

在父组件子目标列表和每个子组件目标项中都拥有clickIndex状态。当目标一展开时,clickIndex为0。此时点击目标三,目标三的clickIndex变为2,只要其父组件子目标列表感知到clickIndex状态变化,同时将此变化传递给目标一。目标一的clickIndex即可同步改变为2,即目标一感知到此时点击了目标三。

图9 与父组件双向同步clickIndex状态

管理组件状态_自定义组件_10

将列表和目标项对应到列表组件TargetList和列表项TargetListItem。首先,需要在父组件TargetList中定义clickIndex状态。

若此时子组件中的clickIndex用@Prop装饰器修饰,当子组件中clickIndex变化时,父组件无法感知,因为@Prop装饰器建立的是从父组件到子组件的单向同步关系。

ArkUI提供了@Link装饰器,用于与父组件双向同步状态。当子组件TargetListItem中的clickIndex用@Link修饰,可与父组件TargetList中的clickIndex建立双向同步关系。

 

 

@Component
export default struct TargetList {
@State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX;
  ...
TargetListItem({
clickIndex: $clickIndex,
...
})
  ...
}

首先,在父组件TargetList中用@State装饰器定义点击的目标项索引状态。然后,在子组件TargetListItem中用@Link装饰器定义clickIndex,当点击目标项时,clickIndex更新为当前目标索引值。

完成在父子组件中定义状态后,最关键的就是要建立父子组件的双向关联关系。在父组件中使用子组件时,将父组件的clickIndex传递给子组件的clickIndex。其中父组件的clickIndex加上$表示传递的是引用。

 

 

@Component
export default struct TargetListItem {
@Link @Watch('onClickIndexChanged') clickIndex: number;
@State isExpanded: boolean = false
...

onClickIndexChanged() {
if (this.clickIndex != this.index) {
this.isExpanded = false;
}
  }

build() {
...
Column() {
...
       }
.onClick(() => {
...
this.clickIndex = this.index;
...
)
...
  }
}

当目标一感知到点击了目标三时,还需要将目标一收起,切换列表项的功能才是完整的。此时,目标一感知到clickIndex变为2,需要判断与目标一本身的位置索引值0不相等,从而将目标一收起。此时,就需要用到ArkUI中监听状态变化@Watch的能力。用@Watch修饰的状态,当状态发生变化时,会触发声明时定义的回调。

我们给TargetListItem的中的clickIndex状态加上@Watch("onClickIndexChanged")。这表示需要监听clickIndex状态的变化。当clickIndex状态变化时,将触发onClickIndexChanged回调:如果点击的列表项索引不等于当前列表项索引,则将isExpanded状态置为false,从而收起该目标项。

跨组件层级双向同步状态:@Provide和@Consume

管理组件状态_ide_11

跨组件层级双向同步状态是指@Provide修饰的状态变量自动对提供者组件的所有后代组件可用,后代组件通过使用@Consume装饰的变量来获得对提供的状态变量的访问。@Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。@Consume在感知到@Provide数据的更新后,会触发当前自定义组件的重新渲染。

使用@Provide的好处是开发者不需要多次将变量在组件间传递。@Provide和@Consume的具体使用方法请参见开发指南:@Provide装饰器和@Consume装饰器:与后代组件双向同步

参考

更多状态管理场景和相关知识请参考开发指南:状态管理

介绍

本篇Codelab将介绍如何使用@State、@Prop、@Link、@Watch、@Provide、@Consume管理页面级变量的状态,实现对页面数据的增加、删除、修改。要求完成以下功能:

实现一个自定义弹窗,完成添加子目标的功能。

实现一个可编辑列表,可点击指定行展开调节工作目标进度,可多选、全选删除指定行。

相关概念

页面状态管理:用于管理页面级变量的状态。

自定义弹窗: 通过CustomDialogController类显示自定义弹窗。

List列表:列表包含一系列相同宽度的列表项。

完整示例

gitee源码地址

源码下载

 

目标管理(ArkTS).zip

 

管理组件状态_ide_12

环境搭建

我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。

软件要求

DevEco Studio版本:DevEco Studio 3.1 Release。

HarmonyOS SDK版本:API version 9。

硬件要求

设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。

HarmonyOS系统:3.1.0 Developer Release。

环境搭建

安装DevEco Studio,详情请参考下载和安装软件

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。

如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

开发者可以参考以下链接,完成设备调试的相关配置:

使用真机进行调试

使用模拟器进行调试

 

管理组件状态_子目标_13

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在源码下载或gitee中提供。

 

 

No Preview

├──entry/src/main/ets // ArkTS代码区

│ ├──common

│ │ ├──constants

│ │ │ └──CommonConstants.ets // 公共常量类

│ │ └──utils

│ │ ├──DateUtil.ets // 获取格式化日期工具

│ │ └──Logger.ets // 日志打印工具类

│ ├──entryability

│ │ └──EntryAbility.ts // 程序入口类

│ ├──pages

│ │ └──MainPage.ets // 主页面

│ ├──view

│ │ ├──TargetInformation.ets // 整体目标详情自定义组件

│ │ ├──AddTargetDialog.ets // 自定义弹窗

│ │ ├──ProgressEditPanel.ets // 进展调节自定义组件

│ │ ├──TargetList.ets // 工作目标列表

│ │ └──TargetListItem.ets // 工作目标列表子项

│ └──viewmodel

│ └──DataModel.ets // 工作目标数据操作类

└──entry/src/main/resources // 资源文件目录

构建主界面

MainPage作为本应用的主界面,从上至下由三个自定义组件组成。

标题titleBar。

目标整体进展详情TargetInformation。

子目标列表TargetList。

MainPage主要维护五个参数:子目标数组targetData、子目标总数totalTasksNumber、已完成子目标数completedTasksNumber、最近更新时间latestUpdateDate、监听数据变化的参数overAllProgressChanged。具体作用有以下三个方面:

子组件TargetInformation接收三个参数totalTasksNumber、completedTasksNumber、latestUpdateDate,渲染整体目标详情。

子组件TargetList接收参数targetData渲染列表。

使用@Watch监听overAllProgressChanged的变化。当overAllProgressChanged改变时,回调onProgressChanged方法,刷新整体进展TargetInformation。

 

 

No Preview
// MainPage.ets
@Entry
@Component
struct MainPage {
// 子目标数组
@State targetData: Array<TaskItemBean> = DataModel.getData();
// 子目标总数
@State totalTasksNumber: number = 0;
// 已完成子目标数
@State completedTasksNumber: number = 0;
// 最近更新时间
@State latestUpdateDate: string = CommonConstants.DEFAULT_PROGRESS_VALUE;
// 监听数据变化的参数
@Provide @Watch('onProgressChanged') overAllProgressChanged: boolean = false; 
...

/**
 * overAllProgressChanged改变时的回调
 */
onProgressChanged() {
this.totalTasksNumber = this.targetData.length;
this.completedTasksNumber = this.targetData.filter((item: TaskItemBean) => {
return item.progressValue === CommonConstants.SLIDER_MAX_VALUE;
}).length;
this.latestUpdateDate = getCurrentTime();
}

build() {
Column() {
// 标题
this.titleBar()
// 目标整体进展详情
TargetInformation({
latestUpdateDate: this.latestUpdateDate,
totalTasksNumber: this.totalTasksNumber,
completedTasksNumber: this.completedTasksNumber
})
// 子目标列表
TargetList({
targetData: $targetData,
onAddClick: (): void => this.dialogController.open()
})
...
}
...
}

@Builder
titleBar() {
Text($r('app.string.title'))
...
}
}

添加任务子目标

本章节主要介绍如何实现一个自定义弹窗,完成添加子目标的功能。效果如图所示:

在MainPage.ets中,创建dialogController对象控制弹窗隐显,传入自定义组件AddTargetDialog和点击确定的回调方法saveTask。

在AddTargetDialog.ets中,参数onClickOk为function类型,接收MainPage传入的saveTask方法。点击确定,调用onClickOk执行saveTask方法,关闭弹窗。

在MainPage.ets中,实现saveTask方法:保存数据至DataModel中,并更新targetData的值,完成添加子目标功能。

 

 

No Preview
// MainPage.ets
@Entry
@Component
struct MainPage {
...
saveTask(taskName: string) {
if (taskName === '') {
promptAction.showToast({
message: $r('app.string.cannot_input_empty'),
duration: CommonConstants.TOAST_TIME,
bottom: CommonConstants.TOAST_MARGIN_BOTTOM
});
return;
}
// 保存数据
DataModel.addData(new TaskItemBean(taskName, 0, getCurrentTime()));
// 更新targetData刷新页面
this.targetData = DataModel.getData();
this.overAllProgressChanged = !this.overAllProgressChanged;
this.dialogController.close();
}
}

实现可编辑列表

本章节主要介绍子目标列表TargetList的实现,包括以下功能:

列表项展开。

列表子项点击下拉,滑动滑块更新进展。

列表进入编辑状态,单选、多选、全选、删除子项。

 

管理组件状态_自定义组件_14

6.1 实现列表项展开

实现以下步骤完成点击列表项展开功能:

使用@State 管理参数isExpanded,表示当前项是否展开,具体表现为自定义组件ProgressEditPanel的显示或隐藏。

使用@Link和@Watch管理参数clickIndex,表示当前点击ListItem的Index索引。clickIndex值的改变将会传递至所有的ListItem。

完成onClick点击事件,将isExpanded 值置反,修改clickIndex值为当前点击的索引。

 

 

No Preview
// TargetListItem.ets
@Component
export default struct TargetListItem {
@State latestProgress?: number = 0;
@Link @Watch('onClickIndexChanged') clickIndex: number;
@State isExpanded: boolean = false;
...
// clickIndex改变的回调方法
onClickIndexChanged() {
if (this.clickIndex !== this.index) {
this.isExpanded = false;
}
}

build() {
...
Column() {
this.TargetItem()
if (this.isExpanded) {
Blank()
// 自定义组件:编辑面板
ProgressEditPanel({
slidingProgress: this.latestProgress,
onCancel: () => this.isExpanded = false,
onClickOK: (progress: number): void => {
this.latestProgress = progress;
this.updateDate = getCurrentTime();
let result = DataModel.updateProgress(this.index, this.latestProgress, this.updateDate);
if (result) {
this.overAllProgressChanged = !this.overAllProgressChanged;
}
this.isExpanded = false;
},
sliderMode: $sliderMode
})
...
}
}
...
.onClick(() => {
...
if (!this.isEditMode) {
animateTo({ duration: CommonConstants.DURATION }, () => {
this.isExpanded = !this.isExpanded;
})
this.clickIndex = this.index;
}
})
}
...
}

6.2 实现更新进展

列表某项被展开后,实现以下步骤完成更新进展功能:

Slider实现滑动条,滑动滑块调节进展,使用slidingProgress保存滑动值。

点击确定调用onClickOK方法,将数据slidingProgress回调至TargetListItem。

在TargetListItem中获取回调的数据并刷新页面。

在TargetListItem.ets中,完成onClickOK方法的实现,将依次完成以下步骤。

重新渲染TargetListItem的进度值和最近更新时间。

更新缓存的数据。

修改overAllProgressChanged的值,通知主页刷新整体进展详情TargetInformation。

 

 

No Preview
// TargetListItem.ets
@Component
export default struct TargetListItem {
...
build() {
...
Column() {
...
if (this.isExpanded) {
Blank()
// 自定义组件:编辑面板
ProgressEditPanel({
...
onClickOK: (progress: number): void => {
this.latestProgress = progress;
this.updateDate = getCurrentTime();
let result = DataModel.updateProgress(this.index, this.latestProgress, this.updateDate);
if (result) {
this.overAllProgressChanged = !this.overAllProgressChanged;
}
this.isExpanded = false;
},
...
})
...
}
}
}
}

6.3 实现列表多选

列表进入编辑模式才可单选、多选。实现以下步骤完成列表多选功能:

维护一个boolean类型的数组selectArray,其长度始终与数据列表的长度相等,且初始值均为false。表示进入编辑状态时列表均未选中。

定义一个boolean类型的值isEditMode,表示是否进入了编辑模式。

TargetListItem选中状态的初始化和点击Checkbox改变TargetListItem的选中状态。

点击全选Checkbox,将selectArray数组的值全赋值true或false,重新渲染列表为全选或者取消全选状态。

在TargetListItem中,实现以下步骤改变ListItem的选中状态:

使用@Link定义selectArr数组接收TargetList传入的selectArray。

在TargetListItem渲染时,使用this.selectArr[this.index]获取初始选中状态。

点击Checkbox时,按照当前ListItem的索引,将选中状态保存至selectArr,重新渲染列表完成单选和多选功能

 

 

No Preview
// TargetListItem.ets
export default struct TargetListItem {

...
@Link selectArr: Array<boolean>;
public index: number = 0;

build() {
Stack({ alignContent: Alignment.Start }) {
...
this.TargetItem()
...
Checkbox()
// 获取初始选中状态
.select(this.selectArr[this.index])
...
.onChange((isCheck: boolean) => {
// 改变被点击项的选中状态
this.selectArr[this.index] = isCheck;
})
...
...
}
}
}

6.4 实现删除选中列表项

当点击“删除”时,调用TargetList.ets的deleteSelected方法,实现以下步骤完成列表项删除功能:

调用DataModel的deleteData方法删除数据。

更新targetData的数据重新渲染列表。

修改overAllProgressChanged的值,通知主页刷新整体进展详情TargetInformation。

在DataModel.ets中,遍历数据列表,删除被选中的数据项。

 

 

 

No Preview
// DataModel.ets
export class DataModel {
...
// 删除选中的数据
deleteData(selectArr: Array<boolean>) {
if (!selectArr) {
Logger.error(TAG, 'Failed to delete data because selectArr is ' + selectArr);
}
let dataLen = this.targetData.length - CommonConstants.ONE_TASK;
for (let i = dataLen; i >= 0; i--) {
if (selectArr[i]) {
this.recordData.splice(i, CommonConstants.ONE_TASK);
}
}
}
...
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

@State、@Prop、@Link、@Watch、@Provide、@Consume的使用。

List组件的使用。

自定义弹窗的使用。

Slider组件的使用。

概述

在手机、平板或是智慧屏这些终端设备上,媒体功能可以算作是我们最常用的场景之一。无论是实现音频的播放、录制、采集,还是视频的播放、切换、循环,亦或是相机的预览、拍照等功能,媒体组件都是必不可少的。以视频功能为例,在应用开发过程中,我们需要通过ArkUI提供的Video组件为应用增加基础的视频播放功能。借助Video组件,我们可以实现视频的播放功能并控制其播放状态。常见的视频播放场景包括观看网络上的较为流行的短视频,也包括查看我们存储在本地的视频内容。

管理组件状态_子目标_15

本文将结合《简易视频播放器(ArkTS)》这个Codelab,对Video组件的参数、属性及事件进行介绍,然后通过组件的属性调用和事件回调阐明Video组件的基本使用方法,最后结合Video组件使用过程中的常见问题讲解自定义控制器的使用。

Video组件用法介绍

Video组件参数介绍

Video组件的接口表达形式为:

 

 

Video(value: {src?: string | Resource, currentProgressRate?: number | string |PlaybackSpeed, previewUri?: string |PixelMap | Resource, controller?: VideoController})

其中包含四个可选参数,src、currentProgressRate、previewUri和controller。

src表示视频播放源的路径,可以支持本地视频路径和网络路径。使用网络地址时,如https,需要注意的是需要在module.json5文件中申请网络权限。在使用本地资源播放时,当使用本地视频地址我们可以使用媒体库管理模块medialibrary来查询公共媒体库中的视频文件,示例代码如下:

 

import mediaLibrary from '@ohos.multimedia.mediaLibrary';

async queryMediaVideo() {
let option = {
// 根据媒体类型检索
selections: mediaLibrary.FileKey.MEDIA_TYPE + '=?',
// 媒体类型为视频
selectionArgs: [mediaLibrary.MediaType.VIDEO.toString()]
};
let media = mediaLibrary.getMediaLibrary(getContext(this));
// 获取资源文件
const fetchFileResult = await media.getFileAssets(option);
// 以获取的第一个文件为例获取视频地址
let fileAsset = await fetchFileResult.getFirstObject();
this.source = fileAsset.uri
}

为了方便功能演示,示例中媒体资源需存放在resources下的rawfile文件夹里。

currentProgressRate表示视频播放倍速,其参数类型为number,取值支持0.75,1.0,1.25,1.75,2.0,默认值为1.0倍速;

previewUri表示视频未播放时的预览图片路径;

controller表示视频控制器。

参数的具体描述如下表:

参数名

参数类型

必填

src

string | Resource

currentProgressRate

number | string | PlaybackSpeed8+

previewUri

string | PixelMap8+ | Resource

controller

VideoController

 

说明

视频支持的规格是:mp4、mkv、webm、TS。

 

下面我们通过具体的例子来说明参数的使用方法,我们选择播放本地视频,视频未播放时的预览图片路径也为本地,代码实现如下:

 

 

@Component
export struct VideoPlayer {
private source: string | Resource;
private controller: VideoController;
private previewUris: Resource = $r('app.media.preview');
...

build() {
Column() {
Video({
src: this.source,
previewUri: this.previewUris,
controller: this.controller
})
...
VideoSlider({ controller: this.controller })
}
}
}

效果如下:

管理组件状态_ide_16

Video组件属性介绍

除了支持组件的尺寸设置、位置设置等通用属性外,Video组件还支持是否静音、是否自动播放、控制栏是否显示、视频显示模式以及单个视频是否循环播放五个私有属性。

名称

参数类型

描述

muted

boolean

是否静音。默认值:false

autoPlay

boolean

是否自动播放。默认值:false

controls

boolean

控制视频播放的控制栏是否显示。默认值:true

objectFit

ImageFit

设置视频显示模式。默认值:Cover

loop

boolean

是否单个视频循环播放。默认值:false

其中,objectFit 中视频显示模式包括Contain、Cover、Auto、Fill、ScaleDown、None 6种模式,默认情况下使用ImageFit.Cover(保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界),其他效果(如自适应显示、保持原有尺寸显示、不保持宽高比进行缩放等)可以根据具体使用场景/设备来进行选择。

在Codelab示例中体现了controls、autoplay和loop属性的配置,示例代码如下:

 

 

@Component
export struct VideoPlayer {
private source: string | Resource;
private controller: VideoController;
...
build() {
Column() {
Video({
controller: this.controller
})
.controls(false) //不显示控制栏 
.autoPlay(false) // 手动点击播放 
.loop(false) // 关闭循环播放 
...
}
}
}

效果如下:

管理组件状态_ide_17

Video组件回调事件介绍

Video组件能够支持常规的点击、触摸等通用事件,同时也支持onStart、onPause、onFinish、onError等事件,具体事件的功能描述见下表:

事件名称

功能描述

onStart(event:() => void)

播放时触发该事件。

onPause(event:() => void)

暂停时触发该事件。

onFinish(event:() => void)

播放结束时触发该事件。

onError(event:() => void)

播放失败时触发该事件。

onPrepared(callback:(event?: { duration: number }) => void)

视频准备完成时触发该事件,通过duration可以获取视频时长,单位为s。

onSeeking(callback:(event?: { time: number }) => void)

操作进度条过程时上报时间信息,单位为s。

onSeeked(callback:(event?: { time: number }) => void)

操作进度条完成后,上报播放时间信息,单位为s。

onUpdate(callback:(event?: { time: number }) => void)

播放进度变化时触发该事件,单位为s,更新时间间隔为250ms。

onFullscreenChange(callback:(event?: { fullscreen: boolean }) => void)

在全屏播放与非全屏播放状态之间切换时触发该事件

在Codelab中我们以更新事件、准备事件、失败事件以及点击事件为回调为例进行演示,代码实现如下:

 

Video({ ... })
.onUpdate((event) => {
this.currentTime = event.time;
this.currentStringTime = changeSliderTime(this.currentTime); //更新事件 
})
.onPrepared((event) => {
prepared.call(this, event); //准备事件 
})
.onError(() => {
prompt.showToast({
duration: COMMON_NUM_DURATION, //播放失败事件 
message: MESSAGE
});
...
})

其中,onUpdate更新事件在播放进度变化时触发,从event中可以获取当前播放进度,从而更新进度条显示事件,比如视频播放时间从24秒更新到30秒。onError事件在视频播放失败时触发,在CommonConstants.ets中定义了常量类MESSAGE,所以在视频播放失败时会显示“请检查网络”。

 

 

const MESSAGE: string = '请检查网络'

自定义控制器的组成与实现

自定义控制器的组成

Video组件的原生控制器样式相对固定,当我们对页面的布局色调的一致性有所要求,或者在拖动进度条的同时需要显示其百分比进度时,原生控制器就无法满足需要了。如下图右侧的效果需要使用自定义控制器实现,接下来我们看一下自定义控制器的组成。

管理组件状态_自定义组件_18

 

为了实现自定义控制器的进度显示等功能,我们需要通过Row容器实现控制器的整体布局,然后借由Text组件来显示视频的播放起始时间、进度时间以及视频总时长,最后通过滑动进度条Slider组件来实现视频进度条的效果,代码如下:

 

 

@Component
export struct VideoSlider {
...
build() {
Row(...) {
Image(...)
Text(...)
Slider(...)
Text(...)
}
...
}
}

自定义控制器的实现

自定义控制器容器内嵌套了视频播放时间Text组件、滑动器Slider组件以及视频总时长Text组件 3个横向排列的组件,其中Text组件在之前的基础组件课程中已经有过详细介绍,这里就不再进行赘述。需要强调的是两个Text组件显示的时长是由Slider组件的onChange(callback: (value: number, mode: SliderChangeMode) => void)回调事件来进行传递的,而Text组件的数值与视频播放进度数值value则是通过@Provide与 @Consume装饰器进行的数据联动,实现效果可见图片下方黑色控制栏部分,具体代码步骤及代码如下:

获取/计算视频时长

 

 

export function prepared(event) {
this.durationTime = event.duration;
let second: number = event.duration % COMMON_NUM_MINUTE;
let min: number = parseInt((event.duration / COMMON_NUM_MINUTE).toString());
let head = min < COMMON_NUM_DOUBLE ? `${ZERO_STR}${min}` : min;
let end = second < COMMON_NUM_DOUBLE ? `${ZERO_STR}${second}` : second;
this.durationStringTime = `${head}${SPLIT}${end}`;
...
};

设置进度条参数及属性

 

 

Slider({
value: this.currentTime,
min: 0,
max: this.durationTime,
step: 1,
style: SliderStyle.OutSet
})
.blockColor($r('app.color.white'))
.width(STRING_PERCENT.SLIDER_WITH)
.trackColor(Color.Gray)
.selectedColor($r('app.color.white'))
.showSteps(true)
.showTips(true)
.trackThickness(this.isOpacity ? SMALL_TRACK_THICK_NESS : BIG_TRACK_THICK_NESS)
.onChange((value: number, mode: SliderChangeMode) => {...})

计算当前进度播放时间及添加onUpdate回调

最后,在我们播放视频时还需要更新显示播放的时间进度,也就是左侧的Text组件。在视频开始播放前,播放时间默认为00:00,随着视频播放,时间需要不断更新为当前进度时间。所以左侧的Text组件我们不仅需要读取时间,还需要为其添加数据联动。这里,我们就是通过为Video组件添加onUpdate事件来实现的,在视频播放过程中会不断调用changeSliderTime方法获取当前的播放时间并进行计算及单位转化,从而不断刷新进度条的值,也就是控制器左侧的播放进度时间Text组件。

 

 

Video({...})
...
.onUpdate((event) => {
this.currentTime = event.time;
this.currentStringTime = changeSliderTime(this.currentTime)
})
 
 
export function changeSliderTime(value: number): string {
let second: number = value % COMMON_NUM_MINUTE;
let min: number = parseInt((value / COMMON_NUM_MINUTE).toString());
let head = min < COMMON_NUM_DOUBLE ? `${ZERO_STR}${min}` : min;
let end = second < COMMON_NUM_DOUBLE ? `${ZERO_STR}${second}` : second;
let nowTime = `${head}${SPLIT}${end}`;
return nowTime;
};

指定视频播放进度及添加onChange事件回调

如需手动进行进度条的拖动,则需要在Slider组件中指定播放进度,并为Slider组件添加onChange事件回调。Slider滑动时就会触发该事件回调,从而实现将视频定位到进度条当前刷新位置,完成时长组件渲染与视频播放进度数据联动。

 

Slider({...})
.onChange((value: number, mode: SliderChangeMode) => {
sliderOnchange.call(this, value, mode);
})
 
 
export function sliderOnchange(value: number, mode: SliderChangeMode) {
this.currentTime = parseInt(value.toString());
this.controller.setCurrentTime(parseInt(value.toString()), SeekMode.Accurate);
...
};

到这里我们就实现了自定义控制器的构建,两个Text组件显示的时长是由Slider组件的onChange回调事件来进行传递的,而Text组件的数值与视频播放进度数值value则通过是onUpdate与onChange事件并借由@Provide @Consume装饰器进行的数据联动。

参考链接

Video组件的更多属性和参数的使用,可以参考API:Video

 

概述

在我们日常使用应用的时候,可能会进行一些敏感的操作,比如删除联系人,这时候我们给应用添加弹窗来提示用户是否需要执行该操作,如下图所示:

管理组件状态_子目标_19

弹窗是一种模态窗口,通常用来展示用户当前需要的或用户必须关注的信息或操作。在弹出框消失之前,用户无法操作其他界面内容。ArkUI为我们提供了丰富的弹窗功能,弹窗按照功能可以分为以下两类:

确认类:例如警告弹窗AlertDialog。

选择类:包括文本选择弹窗TextPickerDialog 、日期滑动选择弹窗DatePickerDialog、时间滑动选择弹窗TimePickerDialog等。

您可以根据业务场景,选择不同类型的弹窗。部分弹窗效果图如下:

管理组件状态_ide_20

此外,如果上述弹窗还不能满足您的需求,或者需要对弹窗的布局和样式进行自定义,您还可以使用自定义弹窗CustomDialog。

下文将分别介绍AlertDialog 、TextPickerDialog 、DatePickerDialog以及CustomDialog的使用。

警告弹窗

警告弹窗AlertDialog由以下三部分区域构成,对应下面的示意图:

标题区:为可选的。

内容区:显示提示消息。

操作按钮区:用户做”确认“或者”取消“等操作。

管理组件状态_ide_21

以下示例代码,演示了如何使用AlertDialog 实现上图所示的警告弹窗。AlertDialog可以设置两个操作按钮,示例代码中分别使用primaryButton和secondaryButton实现了“取消”和“删除”操作按钮,操作按钮可以通过action响应点击事件。

 

Button('点击显示弹窗')
.onClick(() => {
AlertDialog.show(
{
title: '删除联系人', // 标题
message: '是否需要删除所选联系人?', // 内容
autoCancel: false, // 点击遮障层时,是否关闭弹窗。
alignment: DialogAlignment.Bottom, // 弹窗在竖直方向的对齐方式
offset: { dx: 0, dy: -20 }, // 弹窗相对alignment位置的偏移量
primaryButton: {
value: '取消',
action: () => {
console.info('Callback when the first button is clicked');
}
},
secondaryButton: {
value: '删除',
fontColor: '#D94838',
action: () => {
console.info('Callback when the second button is clicked');
}
},
cancel: () => { // 点击遮障层关闭dialog时的回调
console.info('Closed callbacks');
}
}
)
})

此外,您还可以使用AlertDialog,构建只包含一个操作按钮的确认弹窗,使用confirm响应操作按钮回调。

 

 

AlertDialog.show(
{
title: '提示',
message: '提示信息',
autoCancel: true,
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -20 },
confirm: {
value: '确认',
action: () => {
console.info('Callback when confirm button is clicked');
}
},
cancel: () => {
console.info('Closed callbacks')
}
}
)

选择类弹窗

选择类弹窗用于方便用户选择相关数据,比如选择喜欢吃的水果、出生日期等等。下面我们以TextPickerDialog和DatePickerDialog为例,来介绍选择类弹窗的使用。

文本选择弹窗

TextPickerDialog为文本滑动选择器弹窗,根据指定的选择范围创建文本选择器,展示在弹窗上,例如下面这段示例代码使用TextPickerDialog实现了一个水果选择弹窗。示例代码中使用selected指定了弹窗的初始选择项索引为2,对应的数据为“香蕉”。当用户点击“确定”操作按钮后,会触发onAccept事件回调,在回调中将选中的值,传递给宿主中的select变量。

 

@Entry
@Component
struct TextPickerDialogDemo {
@State select: number = 2;
private fruits: string[] = ['苹果', '橘子', '香蕉', '猕猴桃', '西瓜'];

build() {
Column() {
Button('TextPickerDialog')
.margin(20)
.onClick(() => {
TextPickerDialog.show({
range: this.fruits, // 设置文本选择器的选择范围
selected: this.select, // 设置初始选中项的索引值。
onAccept: (value: TextPickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调。
// 设置select为按下确定按钮时候的选中项index,这样当弹窗再次弹出时显示选中的是上一次确定的选项
this.select = value.index;
console.info("TextPickerDialog:onAccept()" + JSON.stringify(value));
},
onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调。
console.info("TextPickerDialog:onCancel()");
},
onChange: (value: TextPickerResult) => { // 滑动弹窗中的选择器使当前选中项改变时触发该回调。
console.info('TextPickerDialog:onChange()' + JSON.stringify(value));
}
})
})
}
.width('100%')
}
}

效果图如下:

管理组件状态_子目标_22

日期选择弹窗

下面我们介绍另一种常用的选择类弹窗DatePickerDialog,它是日期滑动选择器弹窗,根据指定的日期范围创建日期滑动选择器,展示在弹窗上。DatePickerDialog的使用非常广泛,比如当我们需要输入个人出生日期的时候,就可以使用DatePickerDialog。下面的示例代码实现了一个日期选择弹窗:

 

 

@Entry
@Component
struct DatePickerDialogDemo {
selectedDate: Date = new Date('2010-1-1');

build() {
Column() {
Button("DatePickerDialog")
.margin(20)
.onClick(() => {
DatePickerDialog.show({
start: new Date('1900-1-1'), // 设置选择器的起始日期
end: new Date('2023-12-31'), // 设置选择器的结束日期
selected: this.selectedDate, // 设置当前选中的日期
lunar: false,
onAccept: (value: DatePickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调
// 通过Date的setFullYear方法设置按下确定按钮时的日期,这样当弹窗再次弹出时显示选中的是上一次确定的日期
this.selectedDate.setFullYear(value.year, value.month, value.day)
console.info('DatePickerDialog:onAccept()' + JSON.stringify(value))
},
onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调
console.info('DatePickerDialog:onCancel()')
},
onChange: (value: DatePickerResult) => { // 滑动弹窗中的滑动选择器使当前选中项改变时触发该回调
console.info('DatePickerDialog:onChange()' + JSON.stringify(value))
}
})
})
}
.width('100%')
}
}

效果图如下:

管理组件状态_子目标_23

自定义弹窗

自定义弹窗的使用更加灵活,适用于更多的业务场景,在自定义弹窗中您可以自定义弹窗内容,构建更加丰富的弹窗界面。自定义弹窗的界面可以通过装饰器@CustomDialog定义的组件来实现,然后结合CustomDialogController来控制自定义弹窗的显示和隐藏。下面我们通过一个兴趣爱好的选择框来介绍自定义弹窗的使用。

管理组件状态_子目标_24

从上面的效果图可以看出,这个选择框是一个多选的列表弹窗,我们可以使用装饰器@CustomDialog,结合List组件来完成这个弹窗布局,实现步骤如下:

初始化弹窗数据。 先准备好资源文件和数据实体类。其中资源文件stringarray.json创建在resources/base/element目录下,文件根节点为strarray。

{
"strarray": [
{
"name": "hobbies_data",
"value": [
{
"value": "Soccer"
},
{
"value": "Badminton"
},
{
"value": "Travelling"
},
...
]
}
]
}

实体类HobbyBean用来封装自定义弹窗中的"兴趣爱好"数据。

export default class HobbyBean {
label: string;
isChecked: boolean;
}

然后创建一个ArkTS文件CustomDialogWidget,用来封装自定义弹窗,使用装饰器@CustomDialog修饰CustomDialogWidget表示这是一个自定义弹窗。使用资源管理对象manager获取数据,并将数据封装到hobbyBeans。

@CustomDialog
export default struct CustomDialogWidget {
@State hobbyBeans: HobbyBean[] = [];

aboutToAppear() {
let context: Context = getContext(this);
let manager = context.resourceManager;
manager.getStringArrayValue($r('app.strarray.hobbies_data'), (error, hobbyResult) => {
...
hobbyResult.forEach((hobbyItem: string) => {
let hobbyBean = new HobbyBean();
hobbyBean.label = hobbyItem;
hobbyBean.isChecked = false;
this.hobbyBeans.push(hobbyBean);
});
});
}

build() {...}
}

创建弹窗组件。 controller对象用于控制弹窗的控制和隐藏,hobbies表示弹窗选中的数据结果。setHobbiesValue方法用于筛选出被选中的数据,赋值给hobbies。

@CustomDialog
export default struct CustomDialogWidget {
@State hobbyBeans: HobbyBean[] = [];
@Link hobbies: string;
private controller?: CustomDialogController;

aboutToAppear() {...}

setHobbiesValue(hobbyBeans: HobbyBean[]) {
let hobbiesText: string = '';
hobbiesText = hobbyBeans.filter((isCheckItem: HobbyBean) =>
isCheckItem?.isChecked)
.map((checkedItem: HobbyBean) => {
return checkedItem.label;
}).join(',');
this.hobbies = hobbiesText;
}

build() {
Column() {
Text($r('app.string.text_title_hobbies'))...
List() {
ForEach(this.hobbyBeans, (itemHobby: HobbyBean) => {
ListItem() {
Row() {
Text(itemHobby.label)...
Toggle({ type: ToggleType.Checkbox, isOn: false })...
.onChange((isCheck) => {
itemHobby.isChecked = isCheck;
})
}
}
}, itemHobby => itemHobby.label)
}

Row() {
Button($r('app.string.cancel_button'))...
.onClick(() => {
this.controller?.close();
})
Button($r('app.string.definite_button'))...
.onClick(() => {
this.setHobbiesValue(this.hobbyBeans);
this.controller?.close();
})
}
}
}
}

使用自定义弹窗。 在自定义弹窗的使用页面HomePage中先定义一个变量hobbies,使用装饰器@State修饰,和自定义弹窗中的@Link 装饰器修饰的变量进行双向绑定。然后我们使用alignment和offset设置弹窗的位置在屏幕底部,并且距离底部20vp。最后我们在自定义组件TextCommonWidget(具体实现可以参考《构建多种样式弹窗》Codelab源码)的点击事件中,调用customDialogController的open方法,用于显示弹窗。

@Entry
@Component
struct HomePage {
customDialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogWidget({
onConfirm: this.setHobbiesValue.bind(this),
}),
alignment: DialogAlignment.Bottom,
customStyle: true,
offset: { dx: 0,dy: -20 }
});

setHobbiesValue(hobbyArray: HobbyBean[]) {...}

build() {
...
TextCommonWidget({
...
title: $r('app.string.title_hobbies'),
content: $hobby,
onItemClick: () => {
this.customDialogController.open();
}
})
...
}
}

参考

关于更多弹窗,您可以参考:

警告弹窗

列表选择弹窗

自定义弹窗

日期滑动选择弹窗

时间滑动选择弹窗

文本滑动选择弹窗

 

 

介绍

本篇Codelab使用ArkTS语言实现视频播放器,主要包括主界面和视频播放界面,我们将一起完成以下功能:

主界面顶部使用Swiper组件实现视频海报轮播。

主界面下方使用List组件实现视频列表。

播放界面使用Video组件实现视频播放。

在不使用视频组件默认控制器的前提下,实现自定义控制器。

播放界面底部使用图标控制视频播放/暂停。

播放界面底部使用Slider组件控制和实现视频播放进度。

播放界面使用Stack容器组件的Z序控制在视频播放画面上展示开始/暂停/加载图标。

主界面中最近播放和为你推荐列表播放网络视频,需将CommonConstants.ets中的NET属性修改为网络视频地址。

相关概念

Swiper组件:滑块视图容器,提供子组件滑动轮播显示的能力。

List组件:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。

Video组件:用于播放视频文件并控制其播放状态的组件。

Navigator组件:路由容器组件,提供路由跳转能力。

ForEach组件:ForEach基于数组类型数据执行循环渲染。

完整示例

gitee源码地址

源码下载

 

简易视频播放器(ArkTS).zip

 

管理组件状态_ide_25

环境搭建

我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。

软件要求

DevEco Studio版本:DevEco Studio 3.1 Release。

HarmonyOS SDK版本:API version 9。

硬件要求

设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。

HarmonyOS系统:3.1.0 Developer Release。

环境搭建

安装DevEco Studio,详情请参考下载和安装软件

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。

如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

开发者可以参考以下链接,完成设备调试的相关配置:

使用真机进行调试

使用模拟器进行调试

 

管理组件状态_ide_26

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在源码下载或gitee中提供。

 

 

No Preview

├──entry/src/main/ets // 代码区

│ ├──common

│ │ └──constants

│ │ └──CommonConstants.ets // 样式常量类

│ ├──entryability

│ │ └──EntryAbility.ts // 程序入口类

│ ├──model

│ │ └──VideoControll.ets // 视频播放控制相关方法类

│ ├──pages

│ │ ├──SimpleVideoIndex.ets // 主界面

│ │ └──SimpleVideoPlay.ets // 视频播放界面

│ ├──view

│ │ ├──IndexModule.ets // 自定义首页List模块组件文件

│ │ ├──IndexSwiper.ets // 自定义首页Swiper组件文件

│ │ ├──VideoPlayer.ets // 自定义播放界面视频组件文件

│ │ └──VideoPlaySlider.ets // 自定义播放界面视频进度条组件文件

│ └──viewmodel

│ ├──HorizontalVideoItem.ets // 水平视频类

│ ├──ParamItem.ets // 参数类

│ ├──SwiperVideoItem.ets // banner视频类

│ └──VideoData.ets // 首页相关数据

└──entry/src/main/resource // 应用静态资源目录

构建主界面

主界面由视频轮播模块和多个视频列表模块组成,效果图如图:

VideoData.ets中定义的视频轮播图数组SWIPER_VIDEOS和视频列表图片数组HORIZONTAL_VIDEOS。

IndexSwiper.ets文件中定义的轮播图子组件SwiperVideo,点击轮播图片,页面跳转到视频播放页面,并携带本地视频flag,效果图如图:

IndexModule.ets文件中定义的视频列表图片子组件VideoModule,点击子组件中的图片,页面跳转到视频播放页面,并携带网络视频flag,效果图如图:

在SimpleVideoIndex.ets主界面中引用SwiperVideo和VideoModule子组件。

 

 

No Preview

// SimpleVideoIndex.ets
@Entry
@Component
struct SimpleVideoIndex {
build() {
Column({ space: MARGIN_FONT_SIZE.FOURTH_MARGIN }) {
// 视频轮播组件
SwiperVideo()
List() {
ForEach(LIST, (item: string) => {
ListItem() {
VideoModule({ moduleName: item })
.margin({ top: MARGIN_FONT_SIZE.FIRST_MARGIN })
}
}, (item: string) => JSON.stringify(item))
}
.listDirection(Axis.Vertical)
.margin({ top: MARGIN_FONT_SIZE.THIRD_MARGIN })
}
...
}
}

构建视频播放界面

VideoPlayer.ets其中定义了视频播放子组件VideoPlayer ,onPrepared回调方法中可以获取视频总时长,onUpdate回调方法中可实时获取到视频播放的当前时间戳,onFinish是视频播放结束后的回调方法,onError是视频播放出错的回调方法。

在自定义组件VideoPlayer底部使用了自定义子组件VideoSlider,VideoSlider自定义组件中显示和控制视频播放进度,效果图如图:

在VideoController.ets中的视频控制和回调的相关方法。

在SimpleVideoPlay.ets播放界面,引用VideoPlayer子组件,并在视频播放页面使用堆叠容器,在视频播放画面中心堆叠控制、视频加载图标,效果图如图:

 

 

Preview

管理组件状态_ide_27

// SimpleVideoPlay.ets
@Entry
@Component
struct Play {
// 取到Index页面跳转来时携带的source对应的数据。
private source: string = (router.getParams() as Record<string, Object>).source as string;
private startIconResource: Resource = $r('app.media.ic_public_play');
private backIconResource: Resource = $r('app.media.ic_back');
@Provide isPlay: boolean = false;
@Provide isOpacity: boolean = false;
controller: VideoController = new VideoController();
@Provide isLoading: boolean = false;
@Provide progressVal: number = 0;
@Provide flag: boolean = false;

...
onPageHide() {
this.controller.pause();
}

build() {
Column() {
// 顶部返回以及标题
    ...
Stack() {
// 不同的播放状态渲染不同得控制图片
if (!this.isPlay && !this.isLoading) {
Image(this.startIconResource)
.width(MARGIN_FONT_SIZE.FIFTH_MARGIN)
.height(MARGIN_FONT_SIZE.FIFTH_MARGIN)
// 同一容器中兄弟组件显示层级关系,z值越大,显示层级越高 用于控制图片在视频上。
.zIndex(STACK_STYLE.IMAGE_Z_INDEX)
}
if (this.isLoading) {
Progress({
value: STACK_STYLE.PROGRESS_VALUE,
total: STACK_STYLE.PROGRESS_TOTAL,
type: ProgressType.ScaleRing
})
.color(Color.Grey)
.value(this.progressVal)
.width(STACK_STYLE.PROGRESS_WIDTH)
.style({
strokeWidth: STACK_STYLE.PROGRESS_STROKE_WIDTH,
scaleCount: STACK_STYLE.PROGRESS_SCALE_COUNT,
scaleWidth: STACK_STYLE.PROGRESS_SCALE_WIDTH
})
.zIndex(STACK_STYLE.PROGRESS_Z_INDEX)
}
VideoPlayer({
source: this.source,
controller: this.controller
})
.zIndex(0)
}
}
.height(ALL_PERCENT)
.backgroundColor(Color.Black)
}
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

Swiper组件的使用。

List组件的使用。

Video组件的使用。

Slider组件的使用。

如何实现自定义视频控制器。

介绍

本篇Codelab将介绍如何使用弹窗功能,实现四种类型弹窗。分别是:警告弹窗、自定义弹窗、日期滑动选择器弹窗、文本滑动选择器弹窗。需要完成以下功能:

点击左上角返回按钮展示警告弹窗。

点击出生日期展示日期滑动选择器弹窗。

点击性别展示文本滑动选择器弹窗。

点击兴趣爱好(多选)展示自定义弹窗。

相关概念

警告弹窗:显示警告弹窗组件,可设置文本内容与响应回调。

自定义弹窗: 通过CustomDialogController类显示自定义弹窗。

日期滑动选择器弹窗:根据指定范围的Date创建可以选择日期的滑动选择器,展示在弹窗上。

文本滑动选择器弹窗:根据指定的选择范围创建文本选择器,展示在弹窗上。

完整示例

gitee源码地址

源码下载

 

构建多种样式弹窗(ArkTS).zip

 

管理组件状态_子目标_28

环境搭建

我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。

软件要求

DevEco Studio版本:DevEco Studio 3.1 Release。

HarmonyOS SDK版本:API version 9。

硬件要求

设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。

HarmonyOS系统:3.1.0 Developer Release。

环境搭建

安装DevEco Studio,详情请参考下载和安装软件

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。

如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

开发者可以参考以下链接,完成设备调试的相关配置:

使用真机进行调试

使用模拟器进行调试

 

管理组件状态_ide_29

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在源码下载或gitee中提供。

 

 

No Preview

├──entry/src/main/ets // 代码区

│ ├──common

│ │ ├──constants

│ │ │ └──CommonConstants.ets // 常量类

│ │ └──utils

│ │ ├──CommonUtils.ets // 弹窗操作工具类

│ │ └──Logger.ets // 日志打印工具类

│ ├──entryability

│ │ └──EntryAbility.ets // 程序入口类

│ ├──pages

│ │ └──HomePage.ets // 主页面

│ ├──view

│ │ ├──CustomDialogWidget.ets // 自定义弹窗组件

│ │ ├──TextCommonWidget.ets // 自定义Text组件

│ │ └──TextInputWidget.ets // 自定义TextInput组件

│ └──viewmodel

│ └──HobbyItem.ets // 兴趣爱好类

└──entry/src/main/resources // 资源文件目录

构建主页面

应用主页面采用Column容器嵌套自定义组件形式完成页面整体布局,效果如图所示:

从上面效果图可以看出,主界面由2个相同样式的文本输入框和3个相同样式的文本布局组成。我们可以将文本输入框抽取成TextInputWidget子组件。再将文本布局抽取成TextCommonWidget子组件。

在ets目录下,点击鼠标右键 > New > Directory,新建名为view的自定义子组件目录。然后在view目录下,点击鼠标右键 > New > ArkTS File,新建两个ArkTS文件,分别为TextInputWidget子组件、TextCommonWidget子组件。

文本输入框抽取成TextInputWidget子组件,效果如图所示:

文本布局抽取成TextCommonWidget子组件,效果如图所示:

在HomePage主界面引用TextInputWidget和TextCommonWidget子组件,然后初始化出生日期、性别、兴趣爱好默认数据。

 

 

No Preview
// HomePage.ets
@Entry
@Component
struct HomePage {
@State birthdate: string = '';
@State sex: string = '';
@State hobbies: string = '';
...

build() {
Column() {
...
TextInputWidget({
inputImage: $r('app.media.ic_nickname'),
hintText: $r('app.string.text_input_hint')
})
TextCommonWidget({
textImage: $r('app.media.ic_birthdate'),
title: $r('app.string.title_birthdate'),
content: $birthdate,
onItemClick: () => {
CommonUtils.datePickerDialog((birthValue: string) => {
this.birthdate = birthValue;
});
}
})
TextCommonWidget({
textImage: $r('app.media.ic_sex'),
title: $r('app.string.title_sex'),
content: $sex,
onItemClick: () => {
CommonUtils.textPickerDialog(this.sexArray, (sexValue: string) => {
this.sex = sexValue;
});
}
})
TextInputWidget({
inputImage: $r('app.media.ic_signature'),
hintText: $r('app.string.text_input_signature')
})
TextCommonWidget({
textImage: $r('app.media.ic_hobbies'),
title: $r('app.string.title_hobbies'),
content: $hobbies,
onItemClick: () => {
this.customDialogController.open();
}
})
}
...
}
}

警告弹窗

点击主页面左上角返回按钮,通过CommonUtils.alertDialog方法弹出警告弹窗,提醒用户是否进行当前操作,效果如图所示:

 

 

Preview

管理组件状态_自定义组件_30

// CommonUtils.ets
alertDialog(context: Context.UIAbilityContext) {
AlertDialog.show({
// 提示信息
message: $r('app.string.alert_dialog_message'), 
// 弹窗显示位置
alignment: DialogAlignment.Bottom,
// 弹窗偏移位置
offset: {
dx: 0,
dy: CommonConstants.DY_OFFSET
},
primaryButton: {
value: $r('app.string.cancel_button'),
action: () => {
...
}
},
secondaryButton: {
value: $r('app.string.definite_button'),
action: () => {
// 退出应用
context.terminateSelf();
...
}
}
});
}

日期滑动选择器弹窗

点击出生日期选项,通过CommonUtils.datePickerDialog方法弹出日期选择器弹窗,根据需要选择相应时间,效果如图所示:

 

 

Preview

管理组件状态_ide_31

// CommonUtils.ets
datePickerDialog(dateCallback: Function) {
DatePickerDialog.show({
// 开始时间
start: new Date(CommonConstants.START_TIME),
// 结束时间
end: new Date(), 
// 当前选中时间
selected: new Date(CommonConstants.SELECT_TIME),
// 是否显示农历
lunar: false,
onAccept: (value: DatePickerResult) => {
let year: number = Number(value.year);
let month: number = Number(value.month) + CommonConstants.PLUS_ONE;
let day: number = Number(value.day);
let birthdate: string = this.getBirthDateValue(year, month, day);
dateCallback(birthdate);
}
});
}

// 获取出生日期值
getBirthDateValue(year: number, month: number, day: number): string {
let birthdate: string = `${year}${CommonConstants.DATE_YEAR}${month}` +
`${CommonConstants.DATE_MONTH}${day}${CommonConstants.DATE_DAY}`;
return birthdate;
}

// HomePage.ets
build() {
Column() {
...
TextCommonWidget({
textImage: $r('app.media.ic_birthdate'),
title: $r('app.string.title_birthdate'),
content: $birthdate,
onItemClick: () => {
CommonUtils.datePickerDialog((birthValue: string) => {
this.birthdate = birthValue;
});
}
})
...
}
...
}

文本滑动选择器弹窗

点击性别选项,通过CommonUtils.textPickerDialog方法弹出性别选择器弹窗,根据需要选择相应性别,效果如图所示:

 

 

Preview

管理组件状态_自定义组件_32

// CommonUtils.ets
textPickerDialog(sexArray: Resource, sexCallback: Function) {
...
TextPickerDialog.show({
range: sexArray,
selected: 0,
onAccept: (result: TextPickerResult) => {
sexCallback(result.value);
},
onCancel: () => {
...
}
});
}

// HomePage.ets
build() {
Column() {
...
TextCommonWidget({
textImage: $r('app.media.ic_sex'),
title: $r('app.string.title_sex'),
content: $sex,
onItemClick: () => {
CommonUtils.textPickerDialog(this.sexArray, (sexValue: string) => {
this.sex = sexValue;
});
}
})
...
}
...
}

自定义弹窗

点击兴趣爱好选项,通过customDialogController.open方法弹出自定义弹窗,根据需要选择相应的兴趣爱好,效果如图所示:

在view目录下,点击鼠标右键 > New > ArkTS File,新建一个ArkTS文件,然后命名为CustomDialogWidget子组件。

在CustomDialogWidget的aboutToAppear方法,通过manager.getStringArrayValue方法获取本地资源数据进行初始化。

当用户点击确定按钮时,通过setHobbiesValue方法处理自定义弹窗选项结果。

通过@Link修饰的hobbies把值赋给HomePage的hobbies,然后hobbies刷新显示内容。

 

 

No Preview
// HomePage.ets
@State hobbies: string = '';
customDialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogWidget({
hobbies: $hobbies
}),
alignment: DialogAlignment.Bottom,
customStyle: true,
offset: {
dx: 0,
dy: CommonConstants.DY_OFFSET
}
});

build() {
Column() {
...
TextCommonWidget({
textImage: $r('app.media.ic_hobbies'),
title: $r('app.string.title_hobbies'),
content: $hobbies,
onItemClick: () => {
// 打开自定义弹窗
this.customDialogController.open();
}
})
}
...
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

使用CustomDialogController实现自定义弹窗。

使用AlertDialog实现警告弹窗。

使用DatePickerDialog实现日期滑动选择弹窗。

使用TextPickerDialog实现文本滑动选择弹窗。




标签:状态,自定义,...,视频,管理,组件,弹窗,ets
From: https://blog.51cto.com/u_15726470/9454233

相关文章

  • Flink 中的状态管理
    1.Flink中的状态1.概述在Flink中,算子任务可以分为无状态和有状态两种情况。无状态的算子任务只需要观察每个独立事件,根据当前输入的数据直接转换输出结果。我们之前讲到的基本转换算子,如map、filter、flatMap,计算时不依赖其他数据,就都属于无状态的算子。而有状态的算子任务,......
  • 实现一个状态机
    问题的描述最近在改仿真软件的状态切换,什么意思呢,这东西有点像个播放器,但是不仅仅是播放暂停那么简单。首先我们画一个图,以一个图说明:  通过以前的面向对象设计经验我们知道有一种叫状态机的东西,简而言之就是把每个状态通过节点对象包装,节点是什么类型就是当前处于什么状态,......
  • Windows 堆管理机制 [2] Windows 2000 – Windows XP SP1版本
    2.Windows2000–WindowsXPSP12.1环境准备环境环境准备虚拟机32位Windows2000SP4调试器OllyDbg、WinDbg编译器VC6.0++、VS20082.2堆的结构​ 在该阶段,整个堆空间主要由4个结构来维护,分别是段表(segmentlist)、虚表(VirtualAllocationlist)、......
  • Windows 堆管理机制 [3] Windows XP SP2 – Windows 2003 版本
    3.WindowsXPSP2–Windows20033.1环境准备环境环境准备虚拟机32位WindowsXPSP2\32位WindowsXPSP3调试器OllyDbg、WinDbg编译器VC6.0++、VS20083.2堆的结构(Windbg详细分析)​ 在该阶段,堆块的数据结构基本继承于Windows2000–WindowsXPSP1......
  • Windows 堆管理机制 [1] 堆基础
    声明:这篇文章在写的时候,是最开始学习这个堆管理机制,所以写得有些重复和琐碎,基于笔记的目的想写得全一些,这篇文章写的时候参考了很多前辈的文章,已在末尾标出,某些未提及到的可以在评论补充基于分享的目的,之前把所有部分都放出来了,但是全篇有八万词,pdf版本长达两百多页,全部放出看着......
  • 浅谈主数据管理项目建设思路
    主数据是数据之源,是数据资产管理的核心,是信息系统互联互通的基石,是信息化和数字化的重要基础。——《主数据管理实践白皮书》 近期,国家印发《数字中国建设整体布局规划》,提出数字中国建设的整体框架,标志着数字经济被放到更重要的位置。而主数据管理作为数据治理的重要一环,科......
  • 如何管理好一个一盘散沙的新团队?
    如何管理好一个一盘散沙的新团队?**面对这样一道大题:很多人都在谈人,但缺少事人是管不住的;很多人都只关心方法,但真正能让80%的人通过80%的努力达到80分的不是方法,而是方法论!**如何盘活并管理好一个一盘散沙的新团队?或许“六步走”的方法论,才能帮助局中人找到捉刀的正确思路与姿势。......
  • [职场] 食品安全管理员的就业前景
    由于人们对食品质量和安全的日益关注,食品安全管理员的就业前景变得越来越明亮。本文将从各个方面全面解析这一职业。一、食品安全管理员是什么食品安全管理员是一种非常重要的职业,负责确保食品在生产、加工、储存、运输和销售过程中的安全。食品安全管理员的工作职责包括制定食品安......
  • linux内存管理(三)进程地址空间(上)v5.0
    每个进程都有自己的虚拟机地址空间。在task_struct数据结构中有一个mm_struct专门用来描述进程的虚拟地址空间。structtask_struct{...structmm_struct*mm;...}structmm_struct{struct{structvm_area_struct*mmap;/*listofVMAs*......
  • 鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之CheckboxGroup组件
    鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之CheckboxGroup组件一、操作环境操作系统: Windows10专业版、IDE:DevEcoStudio3.1、SDK:HarmonyOS3.1+编辑二、CheckboxGroup组件提供多选框组件,通常用于某选项的打开或关闭。子组件无。接口CheckboxGroup(options?:{group?:string})创......