diff --git a/ETSUI/LayoutAlignmentDemo/AppScope/app.json5 b/ETSUI/LayoutAlignmentDemo/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..3111e30171e081f54cd5d009f8af2cb005b56c7f
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/AppScope/app.json5
@@ -0,0 +1,11 @@
+{
+ "app": {
+ "bundleName": "com.example.layoutalignmentdemo",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name",
+ "distributedNotificationEnabled": true
+ }
+}
diff --git a/ETSUI/LayoutAlignmentDemo/AppScope/resources/base/element/string.json b/ETSUI/LayoutAlignmentDemo/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..4162bb353aa07d9c2373ceddc20c354280f3a0ef
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "LayoutAlignmentDemo"
+ }
+ ]
+}
diff --git a/ETSUI/LayoutAlignmentDemo/AppScope/resources/base/media/app_icon.png b/ETSUI/LayoutAlignmentDemo/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/AppScope/resources/base/media/app_icon.png differ
diff --git a/ETSUI/LayoutAlignmentDemo/LICENSE b/ETSUI/LayoutAlignmentDemo/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..7c357dc828cf7d8c783f10ed6bb1bac8a1e903c1
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/LICENSE
@@ -0,0 +1,78 @@
+ Copyright (c) 2021 Huawei Device Co., Ltd.
+
+ Licensed under the Apache License,Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Apache License, Version 2.0
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+1.You must give any other recipients of the Work or Derivative Works a copy of this License; and
+2.You must cause any modified files to carry prominent notices stating that You changed the files; and
+3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/README.md b/ETSUI/LayoutAlignmentDemo/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..372f477d6505cc65abe0752a8c2f95b844b6b4f9
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/README.md
@@ -0,0 +1,570 @@
+# 概述
+
+OpenHarmony 基于TS扩展的声明式开发范式框架提供了丰富的布局容器组件,其中常用的布局容器包括Flex、Column、Row和Stack。本篇Codelab主要介绍这四种布局容器对齐方式的设置方法。通过本Codelab您将学会在不同的容器内如何灵活的使用alignItems、justifyContent、alignContent、alignSelf和align这5种设置对齐方式的属性,实现自己想要的UI布局。
+
+主界面效果图如下:
+
+
+
+# 相关概念
+
+**主轴**:在布局容器中,默认存在两根轴,分别是主轴和交叉轴,不同的容器中主轴的方向是不一样的。在Column容器中主轴的方向是垂直方向,在Row容器中主轴的方向是水平方向,在Flex容器中可以通过direction参数设置主轴的方向。Stack容器中没有明确主轴与交叉轴,通过设置alignContent参数来改变容器内组件的对齐方式。
+
+**交叉轴**:与主轴垂直相交的轴线,如果主轴是垂直方向,则交叉轴就是水平方向;如果主轴是水平方向,则交叉轴是垂直方向。
+
+# 搭建OpenHarmony环境
+
+完成本篇Codelab我们首先要完成开发环境的搭建,本示例以**RK3568**开发板为例,参照以下步骤进行:
+
+1. [获取OpenHarmony系统版本](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md#%E8%8E%B7%E5%8F%96%E6%96%B9%E5%BC%8F3%E4%BB%8E%E9%95%9C%E5%83%8F%E7%AB%99%E7%82%B9%E8%8E%B7%E5%8F%96):标准系统解决方案(二进制)。
+
+ 以3.1版本为例:
+
+ 
+
+2. 搭建烧录环境。
+ 1. [完成DevEco Device Tool的安装](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-standard-env-setup.md)
+ 2. [完成RK3568开发板的烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-ide-standard-running-rk3568-burning.md)
+
+3. 搭建开发环境。
+ 1. 开始前请参考[工具准备](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-overview.md#%E5%B7%A5%E5%85%B7%E5%87%86%E5%A4%87),完成DevEco Studio的安装和开发环境配置。
+ 2. 开发环境配置完成后,请参考[使用工程向导](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E5%88%9B%E5%BB%BAets%E5%B7%A5%E7%A8%8B)创建工程(模板选择“Empty Ability”),选择JS或者eTS语言开发。
+ 3. 工程创建完成后,选择使用[真机进行调测](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E4%BD%BF%E7%94%A8%E7%9C%9F%E6%9C%BA%E8%BF%90%E8%A1%8C%E5%BA%94%E7%94%A8)。
+
+# 代码结构解读
+
+
+
+文件说明如下:
+
+```
+└── ets // ets代码区
+ └── Application
+ │ └── AbilityStage.ts // Hap包运行时类
+ ├── MainAbility
+ │ └── MainAbility.ts // Ability,提供对Ability生命周期、上下文环境等调用管理
+ └── pages
+ ├── ColumnComponent.ets // Column容器内对齐方式设置界面
+ ├── FlexComponent.ets // Flex容器内对齐方式设置界面
+ ├── index.ets // 主页面
+ └── RowComponent.ets // Row容器内对齐方式设置界面
+ └── RowComponent.ets // Stack容器内对齐方式设置界面
+```
+
+# Flex容器中对齐方式设置
+
+在主界面中有四个模块名称,分别对应四个布局容器,点击模块,则跳转到相对应的布局容器对齐方式设置界面。
+
+[Flex布局容器](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)的参数如下:
+
+**direction**:子组件在Flex容器上排列的方向,即主轴的方向。
+
+**wrap**:Flex容器是单行/列,还是多行/列排列。
+
+**justifyContent**:子组件在Flex容器主轴上的对齐格式。
+
+**alignItems**:子组件在Flex容器交叉轴上的对齐格式。
+
+**alignContent**:交叉轴中有额外的空间时,多行内容的对齐方式。仅在wrap参数设置为Wrap或WrapReverse下生效。
+
+在本章节中,更改Flex布局容器中的5个参数配置,从而更改页面UI的对齐方式,可以更直观体现在不同的参数配置下,页面UI的对齐方式。
+
+FlexComponent.ets详细代码如下:
+
+```
+// FlexComponent.ets
+@Entry
+@Component
+struct FlexComponent {
+ @Provide list: number[]= [1, 2, 3, 4, 5, 6, 7]
+ // 主轴的方向
+ @Provide currentFlexDirection: FlexDirection = FlexDirection.Row
+ // 元素排列方式
+ @Provide currentFlexWrap: FlexWrap = FlexWrap.NoWrap
+ // 主轴对齐方式
+ @Provide currentJustifyContent: FlexAlign = FlexAlign.Start
+ // 单行/列交叉轴对齐方式
+ @Provide currentAlignItems: ItemAlign = ItemAlign.Start
+ // 多行/列交叉轴对齐方式
+ @Provide currentAlignContent: FlexAlign = FlexAlign.Start
+
+ build() {
+ Column({ space: 10 }) {
+ // 设置主轴方向组件
+ FlexDirectionAttribute()
+ // 设置元素排列方式组件
+ FlexWrapAttribute()
+ // 设置主轴对齐方向组件
+ JustifyContentAttribute()
+ // 设置交叉轴对齐方向组件
+ AlignItemsAttribute()
+ // 返回按钮
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .width('100%')
+ .height('100%')
+ .padding(10)
+ }
+}
+```
+
+其中FlexDirectionAttribute组件内包含了元素对齐方式的设置,详细代码如下:
+
+```
+// 设置主轴方向组件
+@Component
+struct FlexDirectionAttribute {
+ @Consume list: number[]
+ @Consume currentFlexDirection: FlexDirection
+ @Consume currentJustifyContent: FlexAlign
+ @Consume currentAlignItems: ItemAlign
+ @Consume currentFlexWrap: FlexWrap
+ @Consume currentAlignContent: FlexAlign
+
+ build() {
+ Column() {
+ // Flex中元素对齐方式布局
+ Flex({
+ alignItems: this.currentAlignItems,
+ direction: this.currentFlexDirection,
+ justifyContent: this.currentJustifyContent,
+ wrap: this.currentFlexWrap,
+ alignContent: this.currentAlignContent
+ }) {
+ // Flex布局容器中的元素
+ ForEach(this.list, (item) => {
+ Text(item.toString())
+ .fontSize(28)
+ .width('40')
+ .height('40')
+ .textAlign(TextAlign.Center)
+ .backgroundColor(0xF9CF93)
+ .borderColor(Color.White)
+ .borderWidth(1)
+ }, item => item)
+ }
+ .padding(5)
+ .width('100%')
+ .height('50%')
+ .backgroundColor(0xFAEEE0)
+ .margin({ top: 20 })
+
+ Text("主轴的方向")
+ .fontSize(20)
+ .margin({ top: 20 })
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ // 点击后设置主轴方向未Row,并改变背景颜色
+ Text("Row")
+ .onClick(() => {
+ this.currentFlexDirection = FlexDirection.Row
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexDirection == FlexDirection.Row ? Color.Orange : '')
+ // 点击后设置主轴方向未Column,并改变背景颜色
+ Text("Column")
+ .onClick(() => {
+ this.currentFlexDirection = FlexDirection.Column
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexDirection == FlexDirection.Column ? Color.Orange : '')
+ // 点击后设置主轴方向未ColumnReverse,并改变背景颜色
+ Text("ColumnReverse")
+ .onClick(() => {
+ this.currentFlexDirection = FlexDirection.ColumnReverse
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexDirection == FlexDirection.ColumnReverse ? Color.Orange : '')
+ }
+ .padding(5)
+ }
+ .borderWidth(1)
+ .width('100%')
+ }
+}
+```
+
+1. 元素排列方式wrap参数设置为单行/列(NoWrap)
+ 1. 当direction设置为Row时,主轴为水平方向,交叉轴为垂直方向。justifyContent参数控制水平方向对齐方式,alignItems参数控制垂直方向对齐方式,设置不同的主轴和交叉轴对齐方式参数。
+ 2. 当direction设置为Column时,主轴为垂直方向,交叉轴为水平方向。justifyContent参数控制垂直方向对齐方式,alignItems参数控制水平方向对齐方式,设置不同的主轴和交叉轴对齐方式参数。
+
+2. 元素排列方式wrap属性设置为多行/列(Wrap/WrapReverse)
+ 1. 当direction设置为Row时,主轴为水平方向,交叉轴为垂直方向。justifyContent参数控制水平方向对齐方式,alignContent参数控制垂直方向对齐方式,设置不同的主轴和交叉轴对齐方式参数。
+ 2. 当direction设置为Column时,主轴为垂直方向,交叉轴为水平方向。justifyContent参数控制垂直方向对齐方式,alignContent参数控制水平方向对齐方式,设置不同的主轴和交叉轴对齐方式参数。
+
+
+主轴方向设置为Row时,效果图如下:
+
+
+
+主轴方向设置为Column时,效果图如下:
+
+.png)
+
+# Column容器中对齐方式设置
+
+[Column布局容器](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md)的对齐方式与Flex布局容器中direction设置为Column时类似。区别在于交叉轴对齐方式设置alignItems的参数类型不一样,在Flex中alignItems的参数类型为[ItemAlign](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-appendix-enums.md#itemalign%E6%9E%9A%E4%B8%BE%E8%AF%B4%E6%98%8E),Column中alignItems的参数类型为HorizontalAlign。设置Column布局容器对齐方式的文件ColumnComponent.ets代码如下:
+
+```
+// ColumnComponent.ets
+@Entry
+@Component
+struct ColumnComponent {
+ @Provide currentJustifyContent: FlexAlign = FlexAlign.Start
+ @Provide currentAlignItems: HorizontalAlign = HorizontalAlign.Start
+
+ build() {
+ Column({ space: 10 }) {
+ // 设置主轴对齐方式组件
+ ColJustifyContentAttribute()
+ // 设置交叉轴轴对齐方式组件
+ ColAlignItemsAttribute()
+ // 返回按钮
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .width('100%')
+ .height('100%')
+ .padding(10)
+ }
+}
+```
+
+其中ColJustifyContentAttribute组件中包含元素对齐方式布局,详细代码如下:
+
+```
+// 设置主轴对齐方式组件
+@Component
+struct ColJustifyContentAttribute {
+ @State list: number[]= [1, 2, 3, 4, 5, 6, 7, 8]
+ @Consume currentJustifyContent: FlexAlign
+ @Consume currentAlignItems: HorizontalAlign
+
+ build() {
+ Column() {
+ // Column中元素对齐方式布局
+ Column() {
+ ForEach(this.list, (item) => {
+ Text(item.toString())
+ .fontSize(28)
+ .width(40)
+ .height(40)
+ .textAlign(TextAlign.Center)
+ .backgroundColor(0xF9CF93)
+ .borderColor(Color.White)
+ .borderWidth(1)
+ }, item => item)
+ }
+ // 设置交叉轴对齐方式属性
+ .alignItems(this.currentAlignItems)
+ // 设置主轴轴对齐方式属性
+ .justifyContent(this.currentJustifyContent)
+ .padding(5)
+ .width('100%')
+ .height('60%')
+ .backgroundColor(0xFAEEE0)
+ .margin({ top: 20 })
+
+ Text("主轴的对齐方式")
+ .fontSize(20)
+ .margin({ top: 20 })
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ // 点击后改变主轴对齐方式为Start,并改变背景色
+ Text("Start")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Start ? Color.Orange : '')
+ // 点击后改变主轴对齐方式为Center,并改变背景色
+ Text("Center")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Center ? Color.Orange : '')
+ // 点击后改变主轴对齐方式为End,并改变背景色
+ Text("End")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.End ? Color.Orange : '')
+ }
+ .padding(5)
+
+ }
+ .borderWidth(1)
+ .width('100%')
+
+ }
+}
+```
+
+设置不同的主轴对齐方式和交叉轴对齐方式的整体效果图如下:
+
+.png)
+
+# Row容器中对齐方式设置
+
+[Row布局容器](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md)的对齐方式与Flex布局容器中direction设置为Row时类似。区别在于交叉轴对齐方式设置alignItems的参数类型不一样,在Flex中alignItems的参数类型为[ItemAlign](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-appendix-enums.md#itemalign%E6%9E%9A%E4%B8%BE%E8%AF%B4%E6%98%8E),Row中alignItems的参数类型为VerticalAlign。设置Row布局容器对齐方式的文件RowComponent.ets代码如下:
+
+```
+// RowComponent.ets
+@Entry
+@Component
+struct RowComponent {
+ @Provide currentJustifyContent: FlexAlign = FlexAlign.Start
+ @Provide currentAlignItems: VerticalAlign = VerticalAlign.Top
+ @Provide currentAlignContent: FlexAlign = FlexAlign.Start
+
+ build() {
+ Column({ space: 10 }) {
+ // 设置主轴对齐方式组件
+ RowJustifyContentAttribute()
+ // 设置交叉轴对齐方式组件
+ RowAlignItemsAttribute()
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .width('100%')
+ .height('100%')
+ .padding(10)
+ }
+}
+```
+
+其中RowJustifyContentAttribute组件中包含元素对齐方式布局,详细代码如下:
+
+```
+// 设置主轴方向对齐组件
+@Component
+struct RowJustifyContentAttribute {
+ @State list: number[]= [1, 2, 3, 4, 5, 6, 7]
+ @Consume currentJustifyContent: FlexAlign
+ @Consume currentAlignItems: VerticalAlign
+
+ build() {
+ Column() {
+ // Row中元素对齐方式布局
+ Row() {
+ ForEach(this.list, (item) => {
+ Text(item.toString())
+ .fontSize(28)
+ .width(40)
+ .height(40)
+ .textAlign(TextAlign.Center)
+ .backgroundColor(0xF9CF93)
+ .borderColor(Color.White)
+ .borderWidth(1)
+ }, item => item)
+ }
+ .alignItems(this.currentAlignItems)
+ .justifyContent(this.currentJustifyContent)
+ .padding(5)
+ .width('100%')
+ .height('60%')
+ .backgroundColor(0xFAEEE0)
+ .margin({ top: 20 })
+
+ Text("主轴的对齐方式")
+ .fontSize(20)
+ .margin({ top: 20 })
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ Text("Start")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Start ? Color.Orange : '')
+
+ Text("Center")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Center ? Color.Orange : '')
+
+ Text("End")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.End ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+ }
+ .borderWidth(1)
+ .width('100%')
+
+ }
+}
+```
+
+设置不同的主轴对齐方式和交叉轴对齐方式的整体效果图如下:
+
+.png)
+
+# Stack容器中对齐方式设置
+
+[Stack布局容器](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-stack.md)是堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。通过alignContent参数来设置对齐方式,其参数类型为[Alignment](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-appendix-enums.md#alignment%E6%9E%9A%E4%B8%BE%E8%AF%B4%E6%98%8E)。设置Text子组件内容的对齐方式,可通过textAlign搭配align属性一起使用。设置Stack容器内的对齐方式的文件StackComponent.ets代码如下:
+
+```
+// StackComponent.ets
+@Entry
+@Component
+struct StackComponent {
+ @Provide currentAlignContent: Alignment = Alignment.Center
+ @Provide message: string = 'center'
+ @Provide textAl: TextAlign = TextAlign.Center
+
+ build() {
+ Column({ space: 10 }) {
+ // Stack中元素对齐方式布局
+ Stack({ alignContent: this.currentAlignContent }) {
+ Text(this.message)
+ .width('100%')
+ .height('80%')
+ // 设置内容在子组件范围内的对齐方式
+ .textAlign(this.textAl)
+ // 当textAlign属性设置达不到我们想要的效果时,可搭配通用属性align一起使用。
+ .align(Alignment.End)
+ .fontSize(16)
+ .backgroundColor(0xFFE4C4)
+ Text(this.message)
+ .fontSize(16)
+ .width('80%')
+ .height('60%')
+ .textAlign(this.textAl)
+ .align(this.currentAlignContent)
+ .backgroundColor(0xFFBBE3)
+ }
+ .borderWidth(1)
+ .width('100%')
+ .height('70%')
+
+ staAlignContentAttribute()
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .padding(5)
+ }
+}
+```
+
+效果图如下:
+
+.png)
+
+# 总结
+
+1. **justifyContent**:作用在布局容器上,设置主轴的对齐方式。
+2. **alignItems**:作用在布局容器上,设置交叉轴对齐方式。Flex布局容器内子组件单行/列时生效,多行/列时不生效。
+3. **alignContent**:作用在布局容器上,在Flex布局容器中设置交叉轴对齐方式,在Stack布局容器中设置容器内子组件整体的对齐方式。Flex布局容器内子组件多行/列时生效,单行/列时不生效。
+4. **alignSelf**:作用在子组件上,当想要布局容器内的某个子组件不按布局容器交叉轴方向设置的对齐方式排列时,可以用该属性覆盖alignItems的配置。
+5. **align**:作用在子组件上,设置组件内容在组件范围内的对齐方式,只有当组件设置的width和height大小超过组件本身内容大小时生效。
+
+ 如果组件有相应的属性设置对齐方式,比如text组件的textAlign属性,可使用align搭配textAlign一起使用来实现自己的UI布局。
diff --git a/ETSUI/LayoutAlignmentDemo/build-profile.json5 b/ETSUI/LayoutAlignmentDemo/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..d7b1117cdb34aab2983ac65026d9e8dcc91332d1
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/build-profile.json5
@@ -0,0 +1,27 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "compileSdkVersion": 9,
+ "compatibleSdkVersion": 9,
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/build-profile.json5 b/ETSUI/LayoutAlignmentDemo/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..7dc37bb919dada5132609c409200db266559004f
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/build-profile.json5
@@ -0,0 +1,13 @@
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default",
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/hvigorfile.js b/ETSUI/LayoutAlignmentDemo/entry/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7720ee6a7aad5c617d1fd2f6fc8c87067bfa32c
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks
diff --git a/ETSUI/LayoutAlignmentDemo/entry/package.json b/ETSUI/LayoutAlignmentDemo/entry/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4e988f30f2ec9e3430a4d0c8f05e89fabbc2659
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "ohos": {
+ "org": "huawei",
+ "buildTool": "hvigor",
+ "directoryLevel": "module"
+ },
+ "description": "example description",
+ "repository": {},
+ "license": "ISC",
+ "dependencies": {}
+}
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/Application/AbilityStage.ts b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/Application/AbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..32dfe93ccff0375201857794de902cec4d239442
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/Application/AbilityStage.ts
@@ -0,0 +1,7 @@
+import AbilityStage from "@ohos.application.AbilityStage"
+
+export default class MyAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] MyAbilityStage onCreate")
+ }
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/MainAbility/MainAbility.ts b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/MainAbility/MainAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..43f841fb90580b88e2a7d1b1192cd9a8c07d22d3
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/MainAbility/MainAbility.ts
@@ -0,0 +1,34 @@
+import Ability from '@ohos.application.Ability'
+
+export default class MainAbility extends Ability {
+ onCreate(want, launchParam) {
+ console.log("[Demo] MainAbility onCreate")
+ globalThis.abilityWant = want;
+ }
+
+ onDestroy() {
+ console.log("[Demo] MainAbility onDestroy")
+ }
+
+ onWindowStageCreate(windowStage) {
+ // Main window is created, set main page for this ability
+ console.log("[Demo] MainAbility onWindowStageCreate")
+
+ windowStage.setUIContent(this.context, "pages/Index", null)
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ console.log("[Demo] MainAbility onWindowStageDestroy")
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ console.log("[Demo] MainAbility onForeground")
+ }
+
+ onBackground() {
+ // Ability has back to background
+ console.log("[Demo] MainAbility onBackground")
+ }
+};
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/ColumnComponent.ets b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/ColumnComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..8ea7baccd764989636aa998eb07be1473a699cb2
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/ColumnComponent.ets
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2021 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import router from '@ohos.router';
+
+// 页面入口组件
+@Entry
+@Component
+struct ColumnComponent {
+ @Provide currentJustifyContent: FlexAlign = FlexAlign.Start
+ @Provide currentAlignItems: HorizontalAlign = HorizontalAlign.Start
+
+ build() {
+ Column({ space: 10 }) {
+ // 设置主轴对齐方式组件
+ ColJustifyContentAttribute()
+ // 设置交叉轴轴对齐方式组件
+ ColAlignItemsAttribute()
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .width('100%')
+ .height('100%')
+ .padding(10)
+ }
+}
+
+
+//JustifyContent属性
+@Component
+struct ColJustifyContentAttribute {
+ @State list: number[]= [1, 2, 3, 4, 5, 6, 7, 8]
+ @Consume currentJustifyContent: FlexAlign
+ @Consume currentAlignItems: HorizontalAlign
+
+ build() {
+ Column() {
+ // Column中元素对齐方式布局
+ Column() {
+ ForEach(this.list, (item) => {
+ Text(item.toString())
+ .fontSize(28)
+ .width(40)
+ .height(40)
+ .textAlign(TextAlign.Center)
+ .backgroundColor(0xF9CF93)
+ .borderColor(Color.White)
+ .borderWidth(1)
+ }, item => item)
+ }
+ .alignItems(this.currentAlignItems)
+ .justifyContent(this.currentJustifyContent)
+ .padding(5)
+ .width('100%')
+ .height('60%')
+ .backgroundColor(0xFAEEE0)
+ .margin({ top: 10 })
+
+ Text("主轴的对齐方式")
+ .fontSize(20)
+ .margin({ top: 20 })
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ Text("Start")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Start ? Color.Orange : '')
+
+ Text("Center")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Center ? Color.Orange : '')
+
+ Text("End")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.End ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+ }
+ .width('100%')
+
+ }
+}
+
+//AlignItems属性
+@Component
+struct ColAlignItemsAttribute {
+ @Consume currentAlignItems: HorizontalAlign
+
+ build() {
+ Column() {
+ Text("交叉轴对齐方式")
+ .fontSize(20)
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+
+ Text("Start")
+ .onClick(() => {
+ this.currentAlignItems = HorizontalAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == HorizontalAlign.Start ? Color.Orange : '')
+
+
+ Text("Center")
+ .onClick(() => {
+ this.currentAlignItems = HorizontalAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == HorizontalAlign.Center ? Color.Orange : '')
+
+ Text("End")
+ .onClick(() => {
+ this.currentAlignItems = HorizontalAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == HorizontalAlign.End ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+
+ }
+ .width('100%')
+
+ }
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/FlexComponent.ets b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/FlexComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..d308a595599ed70b5a67b166cfe6c262aabf41ac
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/FlexComponent.ets
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2021 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import router from '@ohos.router';
+
+// 页面入口组件
+@Entry
+@Component
+struct FlexComponent {
+ @Provide list: number[]= [1, 2, 3, 4, 5, 6]
+ @Provide currentFlexDirection: FlexDirection = FlexDirection.Row
+ @Provide currentFlexWrap: FlexWrap = FlexWrap.NoWrap
+ @Provide currentJustifyContent: FlexAlign = FlexAlign.Start
+ @Provide currentAlignItems: ItemAlign = ItemAlign.Start
+ @Provide currentAlignContent: FlexAlign = FlexAlign.Start
+
+ build() {
+ Column({ space: 10 }) {
+ // 设置主轴方向组件
+ FlexDirectionAttribute()
+ // 设置元素排列方式组件
+ FlexWrapAttribute()
+ // 设置主轴对齐方向组件
+ JustifyContentAttribute()
+ // 设置交叉轴对齐方向组件
+ AlignItemsAttribute()
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .width('100%')
+ .height('100%')
+ .padding(10)
+ }
+}
+//FlexWrap属性
+@Component
+struct FlexWrapAttribute {
+ @Consume list: number[]
+ @Consume currentFlexWrap: FlexWrap
+
+ build() {
+ Column() {
+ Text("排列方式")
+ .fontSize(20)
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ Text("NoWrap")
+ .onClick(() => {
+ this.currentFlexWrap = FlexWrap.NoWrap
+ this.list = [1, 2, 3, 4, 5, 6]
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexWrap == FlexWrap.NoWrap ? Color.Orange : '')
+
+ Text("Wrap")
+ .onClick(() => {
+ this.currentFlexWrap = FlexWrap.Wrap
+ this.list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexWrap == FlexWrap.Wrap ? Color.Orange : '')
+
+ Text("WrapReverse")
+ .onClick(() => {
+ this.currentFlexWrap = FlexWrap.WrapReverse
+ this.list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexWrap == FlexWrap.WrapReverse ? Color.Orange : '')
+
+ }
+ .padding(5)
+
+ }
+ .width('100%')
+
+ }
+}
+//FlexDirection属性
+@Component
+struct FlexDirectionAttribute {
+ @Consume list: number[]
+ @Consume currentFlexDirection: FlexDirection
+ @Consume currentJustifyContent: FlexAlign
+ @Consume currentAlignItems: ItemAlign
+ @Consume currentFlexWrap: FlexWrap
+ @Consume currentAlignContent: FlexAlign
+
+ build() {
+ Column() {
+ // Flex中元素对齐方式布局
+ Flex({
+ alignItems: this.currentAlignItems,
+ direction: this.currentFlexDirection,
+ justifyContent: this.currentJustifyContent,
+ wrap: this.currentFlexWrap,
+ alignContent: this.currentAlignContent
+ }) {
+ ForEach(this.list, (item) => {
+ Text(item.toString())
+ .fontSize(28)
+ .width('40')
+ .height('40')
+ .textAlign(TextAlign.Center)
+ .backgroundColor(0xF9CF93)
+ .borderColor(Color.White)
+ .borderWidth(1)
+ }, item => item)
+ }
+ .padding(5)
+ .width('100%')
+ .height('45%')
+ .backgroundColor(0xFAEEE0)
+ .margin({ top: 10 })
+
+ Text("主轴的方向")
+ .fontSize(20)
+ .margin({ top: 20 })
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ Text("Row")
+ .onClick(() => {
+ this.currentFlexDirection = FlexDirection.Row
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexDirection == FlexDirection.Row ? Color.Orange : '')
+
+ Text("Column")
+ .onClick(() => {
+ this.currentFlexDirection = FlexDirection.Column
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexDirection == FlexDirection.Column ? Color.Orange : '')
+
+ Text("ColumnReverse")
+ .onClick(() => {
+ this.currentFlexDirection = FlexDirection.ColumnReverse
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .padding(5)
+ .backgroundColor(this.currentFlexDirection == FlexDirection.ColumnReverse ? Color.Orange : '')
+ }
+ .padding(5)
+
+
+ }
+ .width('100%')
+
+ }
+}
+
+
+//JustifyContent属性
+@Component
+struct JustifyContentAttribute {
+ @Consume currentJustifyContent: FlexAlign
+
+ build() {
+ Column() {
+ Text("主轴的对齐方式")
+ .fontSize(20)
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ Text("Start")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Start ? Color.Orange : '')
+
+ Text("Center")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Center ? Color.Orange : '')
+
+ Text("End")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.End ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+ }
+ .width('100%')
+
+ }
+}
+
+//AlignItems属性
+@Component
+struct AlignItemsAttribute {
+ @Consume currentFlexWrap: FlexWrap
+ @Consume currentAlignItems: ItemAlign
+ @Consume currentAlignContent: FlexAlign
+
+ build() {
+ Column() {
+ Text(this.currentFlexWrap == FlexWrap.NoWrap ? 'alignItem: 交叉轴对齐方式' : "alignContent: 交叉轴对齐方式")
+ .fontSize(20)
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+
+ Text("Start")
+ .onClick(() => {
+ this.currentAlignItems = ItemAlign.Start
+ this.currentAlignContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == ItemAlign.Start ? Color.Orange : '')
+
+
+ Text("Center")
+ .onClick(() => {
+ this.currentAlignItems = ItemAlign.Center
+ this.currentAlignContent = FlexAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == ItemAlign.Center ? Color.Orange : '')
+
+ Text("End")
+ .onClick(() => {
+ this.currentAlignItems = ItemAlign.End
+ this.currentAlignContent = FlexAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == ItemAlign.End ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+
+ }
+ .width('100%')
+
+ }
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/Index.ets b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..cce4d1821898cb35a937ac7674cb42265d5718c4
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/Index.ets
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import router from '@ohos.router';
+
+@Entry
+@Component
+struct Index {
+ @State list: object[] = [
+ { name: 'Flex', path: 'pages/FlexComponent' },
+ { name: 'Column', path: 'pages/ColumnComponent' },
+ { name: 'Row', path: 'pages/RowComponent' },
+ { name: 'Stack', path: 'pages/StackComponent' },
+ ]
+
+ build() {
+ Row() {
+ Column() {
+ Text("常用布局容器对齐方式设置")
+ .width('100%')
+ .height(50)
+ .fontSize(30)
+ .textAlign(TextAlign.Start)
+ .align(Alignment.Start)
+ .fontColor(Color.Black)
+ Grid() {
+ ForEach(this.list, (item) => {
+ GridItem() {
+ Text(item.name)
+ .fontSize(20)
+ .fontColor(Color.White)
+ .border({radius:20})
+ .width('100%')
+ .height('100%')
+ .backgroundColor('#2788D9')
+ .textAlign(TextAlign.Center)
+ .onClick(() => {
+ router.push({
+ url: item.path
+ })
+ })
+ }
+
+ }, item => item.name)
+ }
+ .columnsTemplate('1fr 1fr')
+ .rowsTemplate('1fr 1fr')
+ .border({radius:20,color:Color.Blue})
+ .columnsGap(5)
+ .rowsGap(5)
+ .columnsGap(10)
+ .rowsGap(10)
+ .width('90%')
+ .height(200)
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+}
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/RowComponent.ets b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/RowComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..3781dda26846ea99d0c8804970a20bb1e70c9bf0
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/RowComponent.ets
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2021 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import router from '@ohos.router';
+
+// 入口组件
+@Entry
+@Component
+struct RowComponent {
+ @Provide currentJustifyContent: FlexAlign = FlexAlign.Start
+ @Provide currentAlignItems: VerticalAlign = VerticalAlign.Top
+ @Provide currentAlignContent: FlexAlign = FlexAlign.Start
+
+ build() {
+ Column({ space: 10 }) {
+ // 设置主轴对齐方式组件
+ RowJustifyContentAttribute()
+ // 设置交叉轴对齐方式组件
+ RowAlignItemsAttribute()
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .width('100%')
+ .height('100%')
+ .padding(10)
+ }
+}
+
+
+@Component
+struct RowJustifyContentAttribute {
+ @State list: number[]= [1, 2, 3, 4, 5, 6, 7]
+ @Consume currentJustifyContent: FlexAlign
+ @Consume currentAlignItems: VerticalAlign
+
+ build() {
+ Column() {
+ // Row中元素对齐方式布局
+ Row() {
+ ForEach(this.list, (item) => {
+ Text(item.toString())
+ .fontSize(28)
+ .width(40)
+ .height(40)
+ .textAlign(TextAlign.Center)
+ .backgroundColor(0xF9CF93)
+ .borderColor(Color.White)
+ .borderWidth(1)
+ }, item => item)
+ }
+ .alignItems(this.currentAlignItems)
+ .justifyContent(this.currentJustifyContent)
+ .padding(5)
+ .width('100%')
+ .height('60%')
+ .backgroundColor(0xFAEEE0)
+ .margin({ top: 10 })
+
+ Text("主轴的对齐方式")
+ .fontSize(20)
+ .margin({ top: 20 })
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+ Text("Start")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Start ? Color.Orange : '')
+
+ Text("Center")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.Center
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.Center ? Color.Orange : '')
+
+ Text("End")
+ .onClick(() => {
+ this.currentJustifyContent = FlexAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentJustifyContent == FlexAlign.End ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+ }
+ .width('100%')
+
+ }
+}
+
+//AlignItems属性
+@Component
+struct RowAlignItemsAttribute {
+ @Consume currentAlignItems: VerticalAlign
+ @Consume currentAlignContent: FlexAlign
+
+ build() {
+ Column() {
+ Text("交叉轴对齐方式")
+ .fontSize(20)
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+
+ Text("Top")
+ .onClick(() => {
+ this.currentAlignItems = VerticalAlign.Top
+ this.currentAlignContent = FlexAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == VerticalAlign.Top ? Color.Orange : '')
+
+
+ Text("Center")
+ .onClick(() => {
+ this.currentAlignItems = VerticalAlign.Center
+
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == VerticalAlign.Center ? Color.Orange : '')
+
+ Text("Bottom")
+ .onClick(() => {
+ this.currentAlignItems = VerticalAlign.Bottom
+
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignItems == VerticalAlign.Bottom ? Color.Orange : '')
+
+
+ }
+ .padding(5)
+
+
+ }
+ .width('100%')
+
+ }
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/StackComponent.ets b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/StackComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..9135eaeab83db10492dffa38d8b7b9c0ab188296
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/ets/pages/StackComponent.ets
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import router from '@ohos.router';
+// 入口组件
+@Entry
+@Component
+struct StackComponent {
+ @Provide currentAlignContent: Alignment = Alignment.Center
+ @Provide message: string = 'center'
+ @Provide textAl: TextAlign = TextAlign.Center
+
+ build() {
+ Column({ space: 10 }) {
+ // Stack中元素对齐方式布局
+ Stack({ alignContent: this.currentAlignContent }) {
+ Text(this.message)
+ .width('100%')
+ .height('80%')
+ .textAlign(this.textAl)
+ .align(Alignment.End)
+ .fontSize(16)
+ .backgroundColor(0xFFE4C4)
+ Text(this.message)
+ .fontSize(16)
+ .width('80%')
+ .height('60%')
+ .textAlign(this.textAl)
+ .align(this.currentAlignContent)
+ .backgroundColor(0xFFBBE3)
+ }
+ .borderWidth(1)
+ .width('100%')
+ .height('70%')
+
+ staAlignContentAttribute()
+ Text("BACK")
+ .width('20%')
+ .height(40)
+ .fontSize(20)
+ .border({ radius: 20 })
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .fontColor(Color.White)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ router.back();
+ })
+ }
+ .padding(5)
+ }
+}
+
+//AlignItems属性
+@Component
+struct staAlignContentAttribute {
+ @Consume currentAlignContent: Alignment
+ @Consume message: string
+ @Consume textAl: TextAlign
+
+ build() {
+ Column() {
+ Text("对齐方式")
+ .fontSize(20)
+ Flex({ justifyContent: FlexAlign.SpaceBetween }) {
+
+ Text("TopStart")
+ .onClick(() => {
+ this.currentAlignContent = Alignment.TopStart
+ this.message = "TopStart"
+ this.textAl = TextAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignContent == Alignment.TopStart ? Color.Orange : '')
+
+
+ Text("TopEnd")
+ .onClick(() => {
+ this.currentAlignContent = Alignment.TopEnd
+ this.message = "TopEnd"
+ this.textAl = TextAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignContent == Alignment.TopEnd ? Color.Orange : '')
+
+ Text("BottomStart")
+ .onClick(() => {
+ this.currentAlignContent = Alignment.BottomStart
+ this.message = "BottomStart"
+ this.textAl = TextAlign.Start
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignContent == Alignment.BottomStart ? Color.Orange : '')
+
+ Text("BottomEnd")
+ .onClick(() => {
+ this.currentAlignContent = Alignment.BottomEnd
+ this.message = "BottomEnd"
+ this.textAl = TextAlign.End
+ })
+ .fontSize(16)
+ .borderColor(Color.Grey)
+ .borderStyle(BorderStyle.Solid)
+ .borderWidth(1)
+ .borderRadius(10)
+ .textAlign(TextAlign.Center)
+ .align(Alignment.Center)
+ .padding(5)
+ .backgroundColor(this.currentAlignContent == Alignment.BottomEnd ? Color.Orange : '')
+
+ }
+ .padding(5)
+
+
+ }
+ .width('100%')
+
+ }
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/module.json5 b/ETSUI/LayoutAlignmentDemo/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..035cdc75464278063f3d80f9fc859ac37d6b8b61
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/module.json5
@@ -0,0 +1,37 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "srcEntrance": "./ets/Application/AbilityStage.ts",
+ "description": "$string:entry_desc",
+ "mainElement": "MainAbility",
+ "deviceTypes": [
+ "phone",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "MainAbility",
+ "srcEntrance": "./ets/MainAbility/MainAbility.ts",
+ "description": "$string:MainAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:MainAbility_label",
+ "visible": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/element/string.json b/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..490210a3908f47722dc942d49dacc98b97669a5f
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "entry_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/media/icon.png b/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/media/icon.png differ
diff --git a/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/profile/main_pages.json b/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..adc3b608495c23102e695863cb646975224d4670
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,9 @@
+{
+ "src": [
+ "pages/Index",
+ "pages/FlexComponent",
+ "pages/ColumnComponent",
+ "pages/RowComponent",
+ "pages/StackComponent"
+ ]
+}
diff --git a/ETSUI/LayoutAlignmentDemo/figures/unnaming-(1).png b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(1).png
new file mode 100644
index 0000000000000000000000000000000000000000..9e7a85becbdcacef11c284d5b64ce3a43e40e88c
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(1).png differ
diff --git a/ETSUI/LayoutAlignmentDemo/figures/unnaming-(2).png b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(2).png
new file mode 100644
index 0000000000000000000000000000000000000000..d3be761da11d1dd7ab38fabc6186e2b9edefa63b
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(2).png differ
diff --git a/ETSUI/LayoutAlignmentDemo/figures/unnaming-(3).png b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(3).png
new file mode 100644
index 0000000000000000000000000000000000000000..867ac769d5cbd68ce1a30b462fe2783afe697e2f
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(3).png differ
diff --git a/ETSUI/LayoutAlignmentDemo/figures/unnaming-(4).png b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(4).png
new file mode 100644
index 0000000000000000000000000000000000000000..48eea79655cc71235cafe3350879ef0d538684a8
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/unnaming-(4).png differ
diff --git a/ETSUI/LayoutAlignmentDemo/figures/unnaming.png b/ETSUI/LayoutAlignmentDemo/figures/unnaming.png
new file mode 100644
index 0000000000000000000000000000000000000000..562ac1ec3769a29dd6ef2462c5c95f3984c59da8
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/unnaming.png differ
diff --git a/ETSUI/LayoutAlignmentDemo/figures/zh-cn_image_0000001214154402.png b/ETSUI/LayoutAlignmentDemo/figures/zh-cn_image_0000001214154402.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d752ff5311af2816bf898cb2c18ef33bf085726
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/zh-cn_image_0000001214154402.png differ
diff --git a/ETSUI/LayoutAlignmentDemo/figures/zh-cn_image_0000001287284688.png b/ETSUI/LayoutAlignmentDemo/figures/zh-cn_image_0000001287284688.png
new file mode 100644
index 0000000000000000000000000000000000000000..9959958fd42a98c8ae81847e7f52447d29ecbdfa
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/figures/zh-cn_image_0000001287284688.png differ
diff --git "a/ETSUI/LayoutAlignmentDemo/figures/\351\246\226\351\241\265.png" "b/ETSUI/LayoutAlignmentDemo/figures/\351\246\226\351\241\265.png"
new file mode 100644
index 0000000000000000000000000000000000000000..5efefeb52922af3af1ca3f324bb8ff20d0ccd28e
Binary files /dev/null and "b/ETSUI/LayoutAlignmentDemo/figures/\351\246\226\351\241\265.png" differ
diff --git a/ETSUI/LayoutAlignmentDemo/hvigorfile.js b/ETSUI/LayoutAlignmentDemo/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f2735e3deeaf655828407544bbed9365c258278
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').appTasks
\ No newline at end of file
diff --git a/ETSUI/LayoutAlignmentDemo/package.json b/ETSUI/LayoutAlignmentDemo/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..03ee58ba167d4859598b75edb98d34943b8d0463
--- /dev/null
+++ b/ETSUI/LayoutAlignmentDemo/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "layoutalignmentdemo",
+ "version": "1.0.0",
+ "ohos": {
+ "org": "huawei",
+ "buildTool": "hvigor",
+ "directoryLevel": "project"
+ },
+ "description": "example description",
+ "repository": {},
+ "license": "ISC",
+ "dependencies": {
+ "hypium": "^1.0.0",
+ "@ohos/hvigor": "1.0.6",
+ "@ohos/hvigor-ohos-plugin": "1.0.6"
+ }
+}
diff --git a/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-caution.gif b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-caution.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-caution.gif differ
diff --git a/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-danger.gif b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-danger.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-danger.gif differ
diff --git a/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-note.gif b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-note.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-note.gif differ
diff --git a/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-notice.gif b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-notice.gif
new file mode 100644
index 0000000000000000000000000000000000000000..86024f61b691400bea99e5b1f506d9d9aef36e27
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-notice.gif differ
diff --git a/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-tip.gif b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-tip.gif
new file mode 100644
index 0000000000000000000000000000000000000000..93aa72053b510e456b149f36a0972703ea9999b7
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-tip.gif differ
diff --git a/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-warning.gif b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-warning.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571
Binary files /dev/null and b/ETSUI/LayoutAlignmentDemo/public_sys-resources/icon-warning.gif differ
diff --git a/GraphicImage/GestureScreenshot/AppScope/app.json5 b/GraphicImage/GestureScreenshot/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..5c165b1f0c39e34c46e5a5ffe8bb0bfa1ccee77a
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/AppScope/app.json5
@@ -0,0 +1,13 @@
+{
+ "app": {
+ "bundleName": "com.example.gesturescreenshot",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name",
+ "distributedNotificationEnabled": true,
+ "minAPIVersion": 9,
+ "targetAPIVersion": 9
+ }
+}
diff --git a/GraphicImage/GestureScreenshot/AppScope/resources/base/element/string.json b/GraphicImage/GestureScreenshot/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..8aba185c7ea09ca8f06ebc30df6cb945549ed213
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "GestureScreenshot"
+ }
+ ]
+}
diff --git a/GraphicImage/GestureScreenshot/AppScope/resources/base/media/app_icon.png b/GraphicImage/GestureScreenshot/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/GraphicImage/GestureScreenshot/AppScope/resources/base/media/app_icon.png differ
diff --git a/GraphicImage/GestureScreenshot/README.md b/GraphicImage/GestureScreenshot/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ce617d46ba180e87f18ad26ab0e63d5960c23466
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/README.md
@@ -0,0 +1,464 @@
+# 介绍
+
+本篇Codelab是基于TS扩展的声明式开发范式及OpenHarmony的手势处理和截屏能力结合实现的一个手势截屏的示例。
+
+本篇Codelab实现如下功能:
+
+- 展示商品详情页信息。
+- 手指双击屏幕进行区域截屏,截屏区域可手动改变。
+- 手指下滑屏幕进行全屏截屏。
+
+相关效果图如下:
+
+**图 1** :全屏截屏:
+
+
+
+**图 2** :区域截屏
+
+
+# 相关概念
+
+- [Canvas](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-canvasrenderingcontext2d.md):提供画布组件,用于自定义绘制图形。
+- [双击手势](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-tapgesture.md):手指双击屏幕回调事件。
+- [手指下滑手势](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-pangesture.md):手指从屏幕由上向下滑回调事件。
+- [截屏功能](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-screenshot.md):截屏功能API。
+
+# 搭建OpenHarmony环境
+
+完成本篇Codelab我们首先要完成开发环境的搭建,本示例以**RK3568**开发板为例,参照以下步骤进行:
+
+1. [获取OpenHarmony系统版本](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md#%E8%8E%B7%E5%8F%96%E6%96%B9%E5%BC%8F3%E4%BB%8E%E9%95%9C%E5%83%8F%E7%AB%99%E7%82%B9%E8%8E%B7%E5%8F%96):标准系统解决方案(二进制)。
+
+ 以3.1版本为例:
+
+ 
+
+2. 搭建烧录环境。
+ 1. [完成DevEco Device Tool的安装](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-standard-env-setup.md)
+ 2. [完成RK3568开发板的烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-ide-standard-running-rk3568-burning.md)
+
+3. 搭建开发环境。
+ 1. 开始前请参考[工具准备](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-overview.md#%E5%B7%A5%E5%85%B7%E5%87%86%E5%A4%87),完成DevEco Studio的安装和开发环境配置。
+ 2. 开发环境配置完成后,请参考[使用工程向导](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E5%88%9B%E5%BB%BAets%E5%B7%A5%E7%A8%8B)创建工程(模板选择“Empty Ability”),选择JS或者eTS语言开发。
+ 3. 工程创建完成后,选择使用[真机进行调测](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E4%BD%BF%E7%94%A8%E7%9C%9F%E6%9C%BA%E8%BF%90%E8%A1%8C%E5%BA%94%E7%94%A8)。
+
+
+# 代码结构导读
+
+
+
+- AppScope:App作用域目录。
+- entry/src/main/ets:程序目录。
+ - Application:stage模型目录。
+ - AbilityStage.ts:stage模型文件。
+
+ - common/picture:资源图片及图标。
+ - MainAbility:程序入口目录。
+ - pages/Index.ets:程序入口类。
+ - app.ets:程序入口。
+
+ - model/resourceData:数据模型。
+ - pages:界面目录。
+ - index.ets:主界面。
+
+
+- entry/src/main/resources:资源文件目录。
+- entry/src/main/module.json5:应用配置文件。
+- entry/src/build-profile.json5:应用构建配置文件。
+
+# 相关权限设置
+
+本篇Codelab需要在module.json5中配置如下权限:
+
+```
+"requestPermissions" :[
+ {
+ // 截屏权限
+ "name": "ohos.permission.CAPTURE_SCREEN"
+ }
+]
+```
+
+> **说明:**
+>本篇codelab所涉及的截屏权限属于高级权限,高级权限需要增加一些配置,请参考:[权限申请说明](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/accesstoken-guidelines.md#acl%E6%96%B9%E5%BC%8F%E5%A3%B0%E6%98%8E)
+
+# 截屏主界面
+
+主界面包括图片文字两部分,整体效果图如下:
+
+
+
+示例代码如下:
+
+```
+Column() {
+ // 图片轮播图
+ Column() {
+ Swiper(this.swiperController) {
+ ForEach(this.swiperArray.map((item, index) => {
+ return { i: index, data: item };
+ }), (item) => {
+ Column() {
+ Image(item.data.resourceSrc)
+ .objectFit(ImageFit.Cover)
+ .width('100%')
+ .height('100%')
+ }
+ .width('100%')
+ .height('100%')
+ .alignItems(HorizontalAlign.Center)
+ })
+ }
+ .index(0)
+ .autoPlay(true)
+ .interval(3000)
+ .indicator(false)
+ .loop(true)
+ .indicatorStyle({ color: '#cccccc', selectedColor: '#ffffff' })
+ .height('100%')
+ }
+ .width('100%')
+ .height('50%')
+
+ // 文字信息
+ Column() {
+ Column({ space: 20 }) {
+ Text('微澜止水 ')
+ .fontSize(20)
+ .padding({ bottom: 10 })
+ Text('平地微澜终吹皱,')
+ .fontSize(20)
+ Text('顾盼逡巡为哪般?')
+ .fontSize(20)
+ Text('杨柳青烟红暮里,')
+ .fontSize(20)
+ Text('斌驳墙下故人还。')
+ .fontSize(20)
+ }
+ .border({ width: 2, radius: 10, style: BorderStyle.Solid, color: Color.White })
+ .align(Alignment.Top)
+ .padding({ top: 28, bottom: 8 })
+ .margin({ left: 8, right: 8, top: 50 })
+ .width('100%')
+ }
+ .padding({ left: 8, right: 8 })
+ .height('50%')
+}
+.width('100%')
+.height('100%')
+.backgroundColor('#ffd6d6d6')
+```
+
+# 截屏操作界面
+
+截屏操作分为全屏截屏和区域截屏,具体操作步骤代码介绍如下:
+
+1. 全屏截屏,手指从屏幕任何地方向下滑动一小段距离即可触发全屏截屏,示例代码如下:
+
+ ```
+ Column() {
+ // 图片轮播图
+ Column() {
+ ...
+ }
+ .width('100%')
+ .height('50%')
+
+ // 文字信息
+ Column() {
+ ...
+ }
+ .padding({ left: 8, right: 8 })
+ .height('50%')
+ }
+ .width('100%')
+ .height('100%')
+ .backgroundColor('#ffd6d6d6')
+ .gesture(
+ // 触发手指数:fingers 触发方向:direction 触发滑动距离:distance
+ PanGesture({ fingers: 3, direction: PanDirection.Down, distance: 50 })
+ // 手势触发开始回调
+ .onActionStart((event: GestureEvent) => {
+ // 截屏参数
+ var ScreenshotOptions = {
+ rotation: 0
+ };
+ // 截屏API
+ let promise = screenshot.save(ScreenshotOptions);
+ promise.then(() => {
+ console.log('screenshot save success');
+ }).catch((err) => {
+ console.log('screenshot save fail: ' + JSON.stringify(err));
+ });
+ })
+ // 手势触发更新回调
+ .onActionUpdate((event: GestureEvent) => {
+ console.info('Pan update' + JSON.stringify(event))
+ })
+ // 手势触发结束回调
+ .onActionEnd(() => {
+ console.info('Pan end')
+ })
+ )
+ ```
+
+2. 区域截屏,分为三个操作步骤,具体操作如下:
+
+(1)手指双击屏幕触发区域截屏选取框,示例代码如下:
+
+```
+Column() {
+ // 图片轮播图
+ Column() {
+ ...
+ }
+ .width('100%')
+ .height('50%')
+
+ // 文字信息
+ Column() {
+ ...
+ }
+ .padding({ left: 8, right: 8 })
+ .height('50%')
+}
+.width('100%')
+.height('100%')
+.backgroundColor('#ffd6d6d6')
+// 手势双击操作
+.gesture(
+ // 触发连击次数:count
+ TapGesture({ count: 2 })
+ .onAction(() => {
+ // 显示区域截屏选取界面
+ this.showScreen = true
+ })
+)
+
+if (this.showScreen) {
+ Stack() {
+ Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
+ Button({ type: ButtonType.Capsule, stateEffect: true }) {
+ Text('取消')
+ .fontSize(18)
+ .fontWeight(FontWeight.Bolder)
+ .fontColor('#FFFFFF')
+ }
+ .onClick(() => {
+ // 区域截屏初始化
+ this.showScreen = false
+ this.offsetX1 = 50
+ this.offsetX2 = 300
+ this.offsetY1 = 200
+ this.offsetY2 = 450
+ })
+ .backgroundColor('#00ffffff')
+ .width('25%')
+
+ Button({ type: ButtonType.Capsule, stateEffect: true }) {
+ Text('确定')
+ .fontSize(18)
+ .fontWeight(FontWeight.Bolder)
+ .fontColor('#FFFFFF')
+ }
+ .onClick(() => {
+ // 截屏参数
+ var ScreenshotOptions = {
+ rotation: 0,
+ "screenRect": {
+ "left": this.offsetX1,
+ "top": this.offsetY1,
+ "width": this.offsetX2 - this.offsetX1,
+ "height": this.offsetY2 - this.offsetY1 }
+ };
+ // 截屏API
+ let promise = screenshot.save(ScreenshotOptions);
+ promise.then(() => {
+ console.log('screenshot save success');
+ }).catch((err) => {
+ console.log('screenshot save fail: ' + JSON.stringify(err));
+ });
+
+ // 区域截屏初始化
+ this.showScreen = false
+ this.offsetX1 = 50
+ this.offsetX2 = 300
+ this.offsetY1 = 200
+ this.offsetY2 = 450
+ })
+ .backgroundColor('#00ffffff')
+ .width('25%')
+ }
+ .zIndex(1)
+ .position({ x: 0, y: 0 })
+
+ // Canvas绘制选取框
+ Canvas(this.context)
+ .height('100%')
+ .onReady(() => {
+ this.draw(this.offsetX1, this.offsetX2, this.offsetY1, this.offsetY2)
+ })
+
+ }
+ .width('100%')
+ .height('100%')
+ .onTouch((event: TouchEvent) => {
+ ...
+ })
+}
+```
+
+Canvas绘制选取框方法,示例代码如下:
+
+```
+// 传入左上及右下两个点横纵坐标
+private draw(x1, x2, y1, y2) {
+ // 每次画之前先清除之前的画布
+ this.context.clearRect(0, 0, this.screenWidth, this.screenHeight)
+ this.context.beginPath()
+
+ // 先画一个最外层的与屏幕大小一样的透明矩形
+ this.context.moveTo(0, 0)
+ this.context.lineTo(this.screenWidth, 0)
+ this.context.lineTo(this.screenWidth, this.screenHeight)
+ this.context.lineTo(0, this.screenHeight)
+ this.context.globalAlpha = 0
+ this.context.closePath()
+ this.context.fill()
+
+ // 画选取框大小
+ this.context.moveTo(x2, y1)
+ this.context.lineTo(x1, y1)
+ this.context.lineTo(x1, y2)
+ this.context.lineTo(x2, y2)
+
+ this.context.globalAlpha = 0.9
+ this.context.fillStyle = '#000'
+ this.context.closePath()
+ this.context.fill()
+}
+```
+
+(2)拖拽选取框的边缘可以改变选取框的大小,包括八个方向,示例代码如下:
+
+```
+if (this.showScreen) {
+ Stack() {
+ ...
+ }
+ .width('100%')
+ .height('100%')
+ .onTouch((event: TouchEvent) => {
+ // 触摸手指按下事件回调
+ if (event.type === TouchType.Down) {
+ // 确定用户是向哪个方向拖动
+ this.clippingType = this.fetchType(event.touches[0].screenX, event.touches[0].screenY)
+ }
+ // 触摸手指移动事件回调
+ if (event.type === TouchType.Move) {
+ if (this.clippingType !== -1) {
+ // 不同方向传入不同参数来用Canvas重绘选取框
+ if (this.clippingType == CLIPPINGENUM.LEFT) {
+ this.draw(event.touches[0].screenX, this.offsetX2, this.offsetY1, this.offsetY2)
+ this.offsetX1 = event.touches[0].screenX
+ }
+ if (this.clippingType === CLIPPINGENUM.RIGHT) {
+ this.draw(this.offsetX1, event.touches[0].screenX, this.offsetY1, this.offsetY2)
+ this.offsetX2 = event.touches[0].screenX
+ }
+ if (this.clippingType === CLIPPINGENUM.UP) {
+ this.draw(this.offsetX1, this.offsetX2, event.touches[0].screenY, this.offsetY2)
+ this.offsetY1 = event.touches[0].screenY
+ }
+ if (this.clippingType === CLIPPINGENUM.DOWN) {
+ this.draw(this.offsetX1, this.offsetX2, this.offsetY1, event.touches[0].screenY)
+ this.offsetY2 = event.touches[0].screenY
+ }
+ if (this.clippingType === CLIPPINGENUM.LEFTUP) {
+ this.draw(event.touches[0].screenX, this.offsetX2, event.touches[0].screenY, this.offsetY2)
+ this.offsetX1 = event.touches[0].screenX
+ this.offsetY1 = event.touches[0].screenY
+ }
+ if (this.clippingType === CLIPPINGENUM.LEFTDOWN) {
+ this.draw(event.touches[0].screenX, this.offsetX2, this.offsetY1, event.touches[0].screenY)
+ this.offsetX1 = event.touches[0].screenX
+ this.offsetY2 = event.touches[0].screenY
+ }
+ if (this.clippingType === CLIPPINGENUM.RIGHTUP) {
+ this.draw(this.offsetX1, event.touches[0].screenX, event.touches[0].screenY, this.offsetY2)
+ this.offsetX2 = event.touches[0].screenX
+ this.offsetY1 = event.touches[0].screenY
+ }
+ if (this.clippingType === CLIPPINGENUM.RIGHTDOWN) {
+ this.draw(this.offsetX1, event.touches[0].screenX, this.offsetY1, event.touches[0].screenY)
+ this.offsetX2 = event.touches[0].screenX
+ this.offsetY2 = event.touches[0].screenY
+ }
+ }
+ }
+ // 触摸手指抬起事件回调
+ if (event.type === TouchType.Up) {
+ }
+ })
+}
+```
+
+确定用户拖动方向函数,示例代码如下:
+
+```
+// 八方向枚举类
+const CLIPPINGENUM = {
+ LEFT: 0,
+ RIGHT: 1,
+ UP: 2,
+ DOWN: 3,
+ LEFTUP: 4,
+ RIGHTUP: 5,
+ LEFTDOWN: 6,
+ RIGHTDOWN: 7,
+}
+
+// 通过手指触发屏幕的x,y坐标确定手指在哪个方向的选取框上
+private fetchType(x, y) {
+ let left, right, up, down
+ if (y > this.offsetY1 - 5 && y < this.offsetY2 + 5 && x > this.offsetX1 - 5 && x < this.offsetX1 + 5) {
+ left = true
+ }
+ if (y > this.offsetY1 - 5 && y < this.offsetY2 + 5 && x > this.offsetX2 - 5 && x < this.offsetX2 + 5) {
+ right = true
+ }
+ if (x > this.offsetX1 - 5 && x < this.offsetX2 + 5 && y > this.offsetY1 - 5 && y < this.offsetY1 + 5) {
+ up = true
+ }
+ if (x > this.offsetX1 - 5 && x < this.offsetX2 + 5 && y > this.offsetY2 - 5 && y < this.offsetY2 + 5) {
+ down = true
+ }
+
+ if (left && up) return CLIPPINGENUM.LEFTUP
+ if (left && down) return CLIPPINGENUM.LEFTDOWN
+ if (right && up) return CLIPPINGENUM.RIGHTUP
+ if (right && down) return CLIPPINGENUM.RIGHTDOWN
+ if (left) return CLIPPINGENUM.LEFT
+ if (right) return CLIPPINGENUM.RIGHT
+ if (up) return CLIPPINGENUM.UP
+ if (down) return CLIPPINGENUM.DOWN
+
+ return -1
+}
+```
+
+(3)确定选取及取消事件,示例代码在步骤(1)的示例代码可查看。
+
+# 恭喜您
+
+目前你已经成功完成了本Codelab并且学到了:
+
+- 如何使用双击手势。
+- 如何使用手指下滑手势。
+- 如何使用截屏API。
+- 如何使用Canvas动态绘制图形。
+
+
+
+
diff --git a/GraphicImage/GestureScreenshot/build-profile.json5 b/GraphicImage/GestureScreenshot/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..d7b1117cdb34aab2983ac65026d9e8dcc91332d1
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/build-profile.json5
@@ -0,0 +1,27 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "compileSdkVersion": 9,
+ "compatibleSdkVersion": 9,
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/entry/build-profile.json5 b/GraphicImage/GestureScreenshot/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..7dc37bb919dada5132609c409200db266559004f
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/build-profile.json5
@@ -0,0 +1,13 @@
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default",
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/entry/hvigorfile.js b/GraphicImage/GestureScreenshot/entry/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7720ee6a7aad5c617d1fd2f6fc8c87067bfa32c
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks
diff --git a/GraphicImage/GestureScreenshot/entry/package.json b/GraphicImage/GestureScreenshot/entry/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c7685ac4e7c0d79df04c96744f0d8f22cb4a9025
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/package.json
@@ -0,0 +1,14 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "entry",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "module",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {}
+}
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/ets/Application/AbilityStage.ts b/GraphicImage/GestureScreenshot/entry/src/main/ets/Application/AbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..32dfe93ccff0375201857794de902cec4d239442
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/ets/Application/AbilityStage.ts
@@ -0,0 +1,7 @@
+import AbilityStage from "@ohos.application.AbilityStage"
+
+export default class MyAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] MyAbilityStage onCreate")
+ }
+}
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/ets/MainAbility/MainAbility.ts b/GraphicImage/GestureScreenshot/entry/src/main/ets/MainAbility/MainAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..edb9130731e04190fedab08e6446428203cadc28
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/ets/MainAbility/MainAbility.ts
@@ -0,0 +1,34 @@
+import Ability from '@ohos.application.Ability'
+
+export default class MainAbility extends Ability {
+ onCreate(want, launchParam) {
+ console.log("[Demo] MainAbility onCreate")
+ globalThis.abilityWant = want;
+ }
+
+ onDestroy() {
+ console.log("[Demo] MainAbility onDestroy")
+ }
+
+ onWindowStageCreate(windowStage) {
+ // Main window is created, set main page for this ability
+ console.log("[Demo] MainAbility onWindowStageCreate")
+
+ windowStage.setUIContent(this.context, "pages/index", null)
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ console.log("[Demo] MainAbility onWindowStageDestroy")
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ console.log("[Demo] MainAbility onForeground")
+ }
+
+ onBackground() {
+ // Ability has back to background
+ console.log("[Demo] MainAbility onBackground")
+ }
+};
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/ets/model/resourceData.ets b/GraphicImage/GestureScreenshot/entry/src/main/ets/model/resourceData.ets
new file mode 100644
index 0000000000000000000000000000000000000000..4fc72a01cc4311b5d5e0773327bb5453b9ed642b
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/ets/model/resourceData.ets
@@ -0,0 +1,32 @@
+export class Resources {
+ id: number;
+ resourceSrc: string;
+ resourceType: number;
+ createTime:number;
+ updateTime:number;
+ constructor(id: number, resourceSrc: string, resourceType: number, createTime:number,updateTime:number) {
+ this.id = id;
+ this.resourceSrc = resourceSrc;
+ this.resourceType = resourceType;
+ this.createTime = createTime;
+ this.updateTime = updateTime;
+ }
+}
+
+const resourceList = [
+ {
+ 'resourceSrc': "common/picture/sun.JPG",
+ 'resourceType': 1,
+ 'createTime': new Date().getTime(),
+ 'updateTime': new Date().getTime()
+ }
+]
+
+export function getResourcesList(): Array {
+ let resourcesArr: Array = []
+ resourceList.forEach(item => {
+ resourcesArr.push(new Resources(null, item.resourceSrc, item.resourceType, item.createTime, item.updateTime));
+ })
+ return resourcesArr;
+}
+
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/ets/pages/index.ets b/GraphicImage/GestureScreenshot/entry/src/main/ets/pages/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2c569eaf56be4f204cfdcae3a04c5fbce0310d86
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/ets/pages/index.ets
@@ -0,0 +1,359 @@
+import { getResourcesList, Resources } from '../model/resourceData'
+import screenshot from '@ohos.screenshot'
+import display from '@ohos.display';
+import image from '@ohos.multimedia.image'
+
+const clippingEnum = {
+ LEFT: 0,
+ RIGHT: 1,
+ UP: 2,
+ DOWN: 3,
+ leftUp: 4,
+ rightUp: 5,
+ leftDown: 6,
+ rightDown: 7,
+}
+
+@CustomDialog
+struct ScreenshotDialog {
+ controller: CustomDialogController
+ cancel: () => void
+ confirm: () => void
+ private pixelMap: PixelMap = null
+
+ build() {
+ Column() {
+ Text('ɹ').width('70%').fontSize(20).margin({ top: 10, bottom: 10 })
+ Image(this.pixelMap)
+ .width('95%')
+ .height('50%')
+ .margin({ top: 10 })
+ .objectFit(ImageFit.Contain)
+ Flex({ justifyContent: FlexAlign.End }) {
+ Button('confirm')
+ .onClick(() => {
+ this.controller.close()
+ this.confirm()
+ }).backgroundColor(0xffffff).fontColor(Color.Red)
+ }.margin({ bottom: 10 })
+ }
+ }
+}
+
+@Entry
+@Component
+struct Index {
+ @State pixelMap: image.PixelMap = undefined
+ dialogController: CustomDialogController = new CustomDialogController({
+ builder: ScreenshotDialog({ cancel: this.onCancel, confirm: this.onAccept, pixelMap: this.pixelMap }),
+ cancel: this.existApp,
+ autoCancel: true
+ })
+ private swiperController: SwiperController = new SwiperController()
+ @State swiperArray: Resources[] = getResourcesList()
+ private settings: RenderingContextSettings = new RenderingContextSettings(true)
+ private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
+ private offContext: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(600, 600, this.settings)
+ private offsetX1: number = 50
+ private offsetX2: number = 300
+ private offsetY1: number = 200
+ private offsetY2: number = 450
+ private screenWidth: number = 0
+ private screenHeight: number = 0
+ private isClipping: boolean = false
+ private clippingType: number
+ @State showScreen: boolean = false
+
+ onCancel() {
+ console.info('Callback when the first button is clicked')
+ }
+
+ onAccept() {
+ console.info('Callback when the second button is clicked')
+ }
+
+ existApp() {
+ console.info('Click the callback in the blank area')
+ }
+
+ private draw(x1, x2, y1, y2) {
+ this.context.clearRect(0, 0, this.screenWidth, this.screenHeight)
+ this.context.beginPath()
+
+ this.context.moveTo(0, 0)
+ this.context.lineTo(this.screenWidth, 0)
+ this.context.lineTo(this.screenWidth, this.screenHeight)
+ this.context.lineTo(0, this.screenHeight)
+ this.context.globalAlpha = 0
+ this.context.closePath()
+ this.context.fill()
+
+
+ this.context.moveTo(x2, y1)
+ this.context.lineTo(x1, y1)
+ this.context.lineTo(x1, y2)
+ this.context.lineTo(x2, y2)
+
+ this.context.globalAlpha = 0.9
+ this.context.fillStyle = '#ff000000'
+ this.context.closePath()
+ this.context.fill()
+ }
+
+ private fetchType(x, y) {
+ let left, right, up, down
+ if (y > this.offsetY1 - 15 && y < this.offsetY2 + 15 && x > this.offsetX1 - 15 && x < this.offsetX1 + 15) {
+ left = true
+ }
+ if (y > this.offsetY1 - 15 && y < this.offsetY2 + 15 && x > this.offsetX2 - 15 && x < this.offsetX2 + 15) {
+ right = true
+ }
+ if (x > this.offsetX1 - 15 && x < this.offsetX2 + 15 && y > this.offsetY1 - 15 && y < this.offsetY1 + 15) {
+ up = true
+ }
+ if (x > this.offsetX1 - 15 && x < this.offsetX2 + 15 && y > this.offsetY2 - 15 && y < this.offsetY2 + 15) {
+ down = true
+ }
+
+ if (left && up) return clippingEnum.leftUp
+ if (left && down) return clippingEnum.leftDown
+ if (right && up) return clippingEnum.rightUp
+ if (right && down) return clippingEnum.rightDown
+ if (left) return clippingEnum.LEFT
+ if (right) return clippingEnum.RIGHT
+ if (up) return clippingEnum.UP
+ if (down) return clippingEnum.DOWN
+
+ return -1
+ }
+
+ aboutToAppear() {
+ display.getDefaultDisplay((err, data) => {
+ if (err.code === 0) {
+ console.info('Failed to obtain the default display object. Code: ' + JSON.stringify(err))
+ }
+ console.info('Failed to obtain the default display object. Code: ' + JSON.stringify(data))
+ this.screenWidth = data.width
+ this.screenHeight = data.height
+ });
+ }
+
+ build() {
+ Stack() {
+ Column() {
+ // ͼƬֲͼ
+ Column() {
+ Swiper(this.swiperController) {
+ ForEach(this.swiperArray.map((item, index) => {
+ return { i: index, data: item };
+ }), (item) => {
+ Column() {
+ Image(item.data.resourceSrc)
+ .objectFit(ImageFit.Cover)
+ .width('100%')
+ .height('100%')
+ }
+ .width('100%')
+ .height('100%')
+ .alignItems(HorizontalAlign.Center)
+ })
+ }
+ .index(0)
+ .autoPlay(true)
+ .interval(3000)
+ .indicator(false)
+ .loop(true)
+ .indicatorStyle({ color: '#cccccc', selectedColor: '#ffffff' })
+ .height('100%')
+ }
+ .width('100%')
+ .height('50%')
+
+ // Ϣ
+ Column() {
+ Column({ space: 20 }) {
+ Text('ֹˮ ')
+ .fontSize(20)
+ .padding({ bottom: 10 })
+ Text('ƽմ壬')
+ .fontSize(20)
+ Text('ѲΪİ㣿')
+ .fontSize(20)
+ Text('̺ĺ')
+ .fontSize(20)
+ Text('ǽ¹˻')
+ .fontSize(20)
+ }
+ .border({ width: 2, radius: 10, style: BorderStyle.Solid, color: Color.White })
+ .align(Alignment.Top)
+ .padding({ top: 28, bottom: 8 })
+ .margin({ left: 8, right: 8, top: 50 })
+ .width('100%')
+ }
+ .padding({ left: 8, right: 8 })
+ .height('50%')
+ }
+ .width('100%')
+ .height('100%')
+ .backgroundColor('#ffd6d6d6')
+ .gesture(
+ // ָfingers direction 룺distance
+ PanGesture({ fingers: 1, direction: PanDirection.Down, distance: 50 })
+ // ʼص
+ .onActionStart((event: GestureEvent) => {
+ console.info('Pan update' + JSON.stringify(event))
+ // API
+ var ScreenshotOptions = {
+ rotation: 0
+ };
+ screenshot.save(ScreenshotOptions, (err, data: image.PixelMap) => {
+ if (err) {
+ console.error(`Failed to save the screenshot. Error:${JSON.stringify(err)}`)
+ }
+ if (this.pixelMap !== undefined) {
+ this.pixelMap.release()
+ }
+ this.pixelMap = data
+ this.dialogController.open()
+ console.log(`Success to save the screenshot. data:${JSON.stringify(this.pixelMap)}`)
+ })
+ })
+ // »ص
+ .onActionUpdate((event: GestureEvent) => {
+ console.info('Pan update' + JSON.stringify(event))
+ })
+ // ص
+ .onActionEnd(() => {
+ console.info('Pan end')
+ })
+ )
+
+ if (this.showScreen) {
+ Stack() {
+ Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
+ Button({ type: ButtonType.Capsule, stateEffect: true }) {
+ Text('ȡ')
+ .fontSize(24)
+ .fontWeight(FontWeight.Bolder)
+ .fontColor('#FFFFFF')
+ }
+ .onClick(() => {
+ this.showScreen = false
+ this.offsetX1 = 50
+ this.offsetX2 = 300
+ this.offsetY1 = 200
+ this.offsetY2 = 450
+ })
+ .backgroundColor('#00ffffff')
+ .width('25%')
+ .height(50)
+
+ Button({ type: ButtonType.Capsule, stateEffect: true }) {
+ Text('ȷ')
+ .fontSize(24)
+ .fontWeight(FontWeight.Bolder)
+ .fontColor('#FFFFFF')
+ }
+ .onClick(() => {
+ this.showScreen = false
+ var ScreenshotOptions = {
+ screenRect: {
+ left: this.offsetX1,
+ top: this.offsetY1,
+ width: this.offsetX2 - this.offsetX1,
+ height: this.offsetY2 - this.offsetY1 },
+ imageSize: { width: this.offsetX2 - this.offsetX1, height: this.offsetY2 - this.offsetY1 },
+ rotation: 0,
+ displayId: 0
+ };
+ screenshot.save(ScreenshotOptions, (err, data: image.PixelMap) => {
+ if (err) {
+ console.error(`Failed to save the screenshot. Error:${JSON.stringify(err)}`)
+ }
+ if (this.pixelMap !== undefined) {
+ this.pixelMap.release()
+ }
+ this.pixelMap = data
+ this.dialogController.open()
+ console.log(`Success to save the screenshot. data:${JSON.stringify(this.pixelMap)}`)
+ })
+ this.offsetX1 = 50
+ this.offsetX2 = 300
+ this.offsetY1 = 200
+ this.offsetY2 = 450
+ })
+ .backgroundColor('#00ffffff')
+ .width('25%')
+ .height(50)
+ }
+ .zIndex(1)
+ .position({ x: 0, y: 0 })
+ .margin({top: 20})
+
+ Canvas(this.context)
+ .height('100%')
+ .onReady(() => {
+ this.draw(this.offsetX1, this.offsetX2, this.offsetY1, this.offsetY2)
+ })
+
+ }
+ .width('100%')
+ .height('100%')
+ .onTouch((event: TouchEvent) => {
+ if (event.type === TouchType.Down) {
+ this.clippingType = this.fetchType(event.touches[0].screenX, event.touches[0].screenY)
+ }
+ if (event.type === TouchType.Move) {
+ if (this.clippingType !== -1) {
+ if (this.clippingType == clippingEnum.LEFT) {
+ this.draw(event.touches[0].screenX, this.offsetX2, this.offsetY1, this.offsetY2)
+ this.offsetX1 = event.touches[0].screenX
+ }
+ if (this.clippingType === clippingEnum.RIGHT) {
+ this.draw(this.offsetX1, event.touches[0].screenX, this.offsetY1, this.offsetY2)
+ this.offsetX2 = event.touches[0].screenX
+ }
+ if (this.clippingType === clippingEnum.UP) {
+ this.draw(this.offsetX1, this.offsetX2, event.touches[0].screenY, this.offsetY2)
+ this.offsetY1 = event.touches[0].screenY
+ }
+ if (this.clippingType === clippingEnum.DOWN) {
+ this.draw(this.offsetX1, this.offsetX2, this.offsetY1, event.touches[0].screenY)
+ this.offsetY2 = event.touches[0].screenY
+ }
+ if (this.clippingType === clippingEnum.leftUp) {
+ this.draw(event.touches[0].screenX, this.offsetX2, event.touches[0].screenY, this.offsetY2)
+ this.offsetX1 = event.touches[0].screenX
+ this.offsetY1 = event.touches[0].screenY
+ }
+ if (this.clippingType === clippingEnum.leftDown) {
+ this.draw(event.touches[0].screenX, this.offsetX2, this.offsetY1, event.touches[0].screenY)
+ this.offsetX1 = event.touches[0].screenX
+ this.offsetY2 = event.touches[0].screenY
+ }
+ if (this.clippingType === clippingEnum.rightUp) {
+ this.draw(this.offsetX1, event.touches[0].screenX, event.touches[0].screenY, this.offsetY2)
+ this.offsetX2 = event.touches[0].screenX
+ this.offsetY1 = event.touches[0].screenY
+ }
+ if (this.clippingType === clippingEnum.rightDown) {
+ this.draw(this.offsetX1, event.touches[0].screenX, this.offsetY1, event.touches[0].screenY)
+ this.offsetX2 = event.touches[0].screenX
+ this.offsetY2 = event.touches[0].screenY
+ }
+ }
+ }
+ if (event.type === TouchType.Up) {
+ }
+ })
+ }
+
+ }.gesture(
+ TapGesture({ count: 2 })
+ .onAction(() => {
+ console.info('TapGesture')
+ this.showScreen = true
+ })
+ )
+ }
+}
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/module.json5 b/GraphicImage/GestureScreenshot/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..8035866470fa2c98a03ebf3a3e6d78b152482bbc
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/module.json5
@@ -0,0 +1,40 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "srcEntrance": "./ets/Application/AbilityStage.ts",
+ "description": "$string:entry_desc",
+ "mainElement": "MainAbility",
+ "deviceTypes": [
+ "phone",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "MainAbility",
+ "srcEntrance": "./ets/MainAbility/MainAbility.ts",
+ "description": "$string:MainAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:MainAbility_label",
+ "visible": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {name:"ohos.permission.CAPTURE_SCREEN"},
+ ]
+ }
+}
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/resources/base/element/string.json b/GraphicImage/GestureScreenshot/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..490210a3908f47722dc942d49dacc98b97669a5f
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "entry_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/resources/base/media/icon.png b/GraphicImage/GestureScreenshot/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/GraphicImage/GestureScreenshot/entry/src/main/resources/base/media/icon.png differ
diff --git a/GraphicImage/GestureScreenshot/entry/src/main/resources/base/profile/main_pages.json b/GraphicImage/GestureScreenshot/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..feec276e105eeb8d621c20aaf838f318b0a94150
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/index"
+ ]
+}
diff --git a/GraphicImage/GestureScreenshot/figures/2.gif b/GraphicImage/GestureScreenshot/figures/2.gif
new file mode 100644
index 0000000000000000000000000000000000000000..73d15652dbf4654c4b70bafd4b535560683f454c
Binary files /dev/null and b/GraphicImage/GestureScreenshot/figures/2.gif differ
diff --git a/GraphicImage/GestureScreenshot/figures/3.gif b/GraphicImage/GestureScreenshot/figures/3.gif
new file mode 100644
index 0000000000000000000000000000000000000000..9892a97437c59aa4fef3566975e2692a2ba85b83
Binary files /dev/null and b/GraphicImage/GestureScreenshot/figures/3.gif differ
diff --git a/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001281785298.png b/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001281785298.png
new file mode 100644
index 0000000000000000000000000000000000000000..835869d209a417cba07b7f1b0fb191e4170353ba
Binary files /dev/null and b/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001281785298.png differ
diff --git a/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001281946098.png b/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001281946098.png
new file mode 100644
index 0000000000000000000000000000000000000000..15860b7c850d1d24b1be8a4b9a04d4838d7e8f6c
Binary files /dev/null and b/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001281946098.png differ
diff --git a/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001323911073.png b/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001323911073.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d752ff5311af2816bf898cb2c18ef33bf085726
Binary files /dev/null and b/GraphicImage/GestureScreenshot/figures/zh-cn_image_0000001323911073.png differ
diff --git a/GraphicImage/GestureScreenshot/hvigorfile.js b/GraphicImage/GestureScreenshot/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f2735e3deeaf655828407544bbed9365c258278
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').appTasks
\ No newline at end of file
diff --git a/GraphicImage/GestureScreenshot/package.json b/GraphicImage/GestureScreenshot/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..37f03fa6d7df72cca24a0a7f6f6bd16507ad370e
--- /dev/null
+++ b/GraphicImage/GestureScreenshot/package.json
@@ -0,0 +1,18 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "gesturescreenshot",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "project",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {
+ "@ohos/hvigor-ohos-plugin": "1.0.6",
+ "hypium": "^1.0.0",
+ "@ohos/hvigor": "1.0.6"
+ }
+}
diff --git a/Media/VideoPlayerStage/AppScope/app.json5 b/Media/VideoPlayerStage/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..af7380afc041d044d48931595adad130c271746b
--- /dev/null
+++ b/Media/VideoPlayerStage/AppScope/app.json5
@@ -0,0 +1,11 @@
+{
+ "app": {
+ "bundleName": "com.huawei.myapplication",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name",
+ "distributedNotificationEnabled": true
+ }
+}
diff --git a/Media/VideoPlayerStage/AppScope/resources/base/element/string.json b/Media/VideoPlayerStage/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..90d91a7824fe54bb278cd9fd902da760ee16dcdd
--- /dev/null
+++ b/Media/VideoPlayerStage/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "VideoPlayerStage"
+ }
+ ]
+}
diff --git a/Media/VideoPlayerStage/AppScope/resources/base/media/app_icon.png b/Media/VideoPlayerStage/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/Media/VideoPlayerStage/AppScope/resources/base/media/app_icon.png differ
diff --git a/Media/VideoPlayerStage/LICENSE b/Media/VideoPlayerStage/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..381eb507bc1eef9b5158e496dedab769e810b657
--- /dev/null
+++ b/Media/VideoPlayerStage/LICENSE
@@ -0,0 +1,78 @@
+ Copyright (c) 2022 Huawei Device Co., Ltd. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Apache License, Version 2.0
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+1.You must give any other recipients of the Work or Derivative Works a copy of this License; and
+2.You must cause any modified files to carry prominent notices stating that You changed the files; and
+3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/README.md b/Media/VideoPlayerStage/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8a1a63e21b18536de33b34eef71b237cf60d15d6
--- /dev/null
+++ b/Media/VideoPlayerStage/README.md
@@ -0,0 +1,323 @@
+# 介绍
+
+本Codelab指导开发者如何在OpenHarmony应用中使用VideoPlayer更灵活地构建自定义播放器。
+
+VideoPlayer支持播放http网络地址、hls直播流地址、系统文件绝对路径地址、rawfile资源路径地址。
+
+与ArkUI提供的Video组件使用场景区分:VideoPlayer属于媒体服务系统,兼容视频软件主流的播放格式和主流分辨率,与其它媒体服务系统搭配使用更方便,面对复杂逻辑的场景有更好的兼容性。
+
+本篇Codelab实现如下功能:
+
+- 如何使用VideoPlayer。
+- 如何实现自定义播控菜单栏。
+- 如何通过触屏控制播放进度、音量、亮度。
+
+- 如何通过媒体库访问媒体资源。
+
+最终效果如下所示:
+
+
+
+
+# 相关概念
+
+- [VideoPlayer](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-media.md#videoplayer8):系统媒体库视频播放管理类,用于管理和播放视频媒体。
+- VideoService:VideoPlayer封装的服务类,提供播放器各个状态切换回调、设置亮度等功能,不同播放源的构造和释放,简化使用难度。
+- VideoView:使用XComponent封装的自定义组件,管理播放器窗口,适配播放内容宽高。
+- VideoController:控制播放菜单,包括加载时动画、播放控制、手势控制、查询媒体库功能。
+- VideoGestureView:手势控制自定义组件,显示进度、音量、亮度数据修改面板。
+- VideoAsset:由播放路径、播放标题等组成媒资内容。
+- MediaLibOperator:媒体库操作服务类,提供查询媒资、新建媒资、获取读写权限、释放读写权限等能力。
+
+# 搭建OpenHarmony环境
+
+完成本篇Codelab我们首先要完成开发环境的搭建,本示例以**RK3568**开发板为例,参照以下步骤进行:
+
+1. [获取OpenHarmony系统版本](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md#%E8%8E%B7%E5%8F%96%E6%96%B9%E5%BC%8F3%E4%BB%8E%E9%95%9C%E5%83%8F%E7%AB%99%E7%82%B9%E8%8E%B7%E5%8F%96):标准系统解决方案(二进制)。
+
+ 以3.1版本为例:
+
+ 
+
+2. 搭建烧录环境。
+ 1. [完成DevEco Device Tool的安装](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-standard-env-setup.md)
+ 2. [完成RK3568开发板的烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-ide-standard-running-rk3568-burning.md)
+
+3. 搭建开发环境。
+ 1. 开始前请参考[工具准备](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-overview.md#%E5%B7%A5%E5%85%B7%E5%87%86%E5%A4%87),完成DevEco Studio的安装和开发环境配置。
+ 2. 开发环境配置完成后,请参考[使用工程向导](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E5%88%9B%E5%BB%BAets%E5%B7%A5%E7%A8%8B)创建工程(模板选择“Empty Ability”),选择JS或者eTS语言开发。
+ 3. 工程创建完成后,选择使用[真机进行调测](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E4%BD%BF%E7%94%A8%E7%9C%9F%E6%9C%BA%E8%BF%90%E8%A1%8C%E5%BA%94%E7%94%A8)。
+
+
+# 代码结构解读
+
+
+
+```
+└── entry/src/main/ets // ets代码区
+ └── Application
+ │ └── AbilityStage.ts // Hap包运行时类
+ ├── component
+ │ └── LoadingView.ets // 加载动画组件
+ │ ├── MediaLibView.ets // 媒体选择库组件
+ │ ├── PressureEffectButton.ets // 多状态按钮组件
+ │ ├── VideoController.ets // 播控控制菜单
+ │ ├── VideoGestureView.ets // 手势控制组件
+ │ └── VideoView.ets // 播放容器组件
+ ├── MainAbility
+ │ └── MainAbility.ts // Ability,提供对Ability生命周期、上下文环境等调用管理
+ ├── model
+ │ └── media
+ │ └── MediaConstants.ts // 媒体常量类
+ │ ├── MediaLibOperator.ts // 媒体库操作类
+ │ └── VideoService.ts // VideoPlayer服务类
+ │── pages
+ │ └── video_lib.ets // 媒体库列表页面
+ │ └── video_player.ets // 播放器页面
+ └── util
+ └── DateTimeUtils.ts // 日期工具类
+ ├── LogUtils.ts // 日志工具类
+ └── SysPermissionUtils.ts // 系统权限工具类
+```
+
+# 相关权限设置
+
+本篇Codelab需要在module.json5中配置如下权限:
+
+```
+"requestPermissions": [
+ {
+ "name": "ohos.permission.READ_MEDIA"
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA"
+ },
+ {
+ "name": "ohos.permission.WRITE_USER_STORAGE"
+ },
+ {
+ "name": "ohos.permission.READ_USER_STORAGE"
+ },
+ {
+ "name": "ohos.permission.MEDIA_LOCATION"
+ },
+ {
+ "name": "ohos.permission.INTERNET"
+ }
+]
+```
+
+> **说明:**
+>应用获取权限的流程取决于相应的权限类型:
+>- 如果目标权限是system\_grant类型,开发者需要在module.json5文件中[声明目标权限](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/accesstoken-guidelines.md),系统会在安装应用时为其进行权限预授予。
+>- 如果目标权限是user\_grant类型,开发者需要先在module.json5文件中[声明目标权限](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/accesstoken-guidelines.md),然后运行时发送弹窗,请求用户授权。
+>- 本篇Codelab用user\_grant类型权限进行讲解,具体可参考[权限定义列表](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/accesstoken-overview.md#%E6%9D%83%E9%99%90%E5%AE%9A%E4%B9%89%E5%88%97%E8%A1%A8)。
+
+# 播放器界面
+
+播放器界面由VideoView和VideoController组件组成,VideoView负责播放器内容显示的适配,VideoController提供加载动画、播放控制、手势操作、查询媒体库等功能。
+
+
+
+1. VideoView是以XComponent主体的自定义组件,提供视频播放的内存地址,使用时传入[VideoService](相关概念.md)作为自定义组件参数。
+
+ ```
+ VideoView({
+ service: this.videoService
+ ...
+ })
+ ```
+
+2. VideoView初始化过程中添加播放尺寸改变状态回调,当播放视频尺寸更改时,修改XComponennt的宽、高比来适应视频尺寸。
+
+ ```
+ this.service.addStatusChangedListener((state, extra) => {
+ switch (state) {
+ case VideoPlayerState.SIZE_CHANGED:
+ if (!this.isContentFull) {
+ this.ratio = extra.width / extra.height
+ this.playerAreaController.setXComponentSurfaceSize({
+ surfaceWidth: extra.width,
+ surfaceHeight: extra.height
+ })
+ }
+ break;
+ }
+ })
+ ```
+
+3. XComponent初始化完成,绑定VideoPlayer和XComponent的内存地址,至此,VideoView构建完毕。
+
+ ```
+ XComponent({
+ id: '',
+ type: 'surface',
+ libraryname: '',
+ controller: this.playerAreaController
+ })
+ .onLoad(() => {
+ let renderId = this.playerAreaController.getXComponentSurfaceId()
+ if (this.service != null) {
+ this.service.addSurface(renderId)
+ }
+ this.onCreated(renderId)
+ })
+ .aspectRatio(this.ratio)
+ ```
+
+4. 在使用[VideoController](相关概念.md)时传入VideoService作为自定义组件参数。
+
+ ```
+ VideoController({ service: this.videoService })
+ ```
+
+5. VideoController初始化过程中添加播放状态改变回调,进行相应的UI改变。
+
+ ```
+ this.service.addStatusChangedListener((state, extra) => {
+ switch (state) {
+ case VideoPlayerState.START:
+ break;
+ case VideoPlayerState.PREPARED:
+ // 播放器准备完毕回调,更新控制菜单栏的标题、播放总时长
+ this.title = extra.title
+ this.totalDuration = extra.duration
+ break
+ case VideoPlayerState.BUFFERING_START:
+ // 视频加载状态回调,显示加载动画
+ this.isBuffering = true
+ break;
+ case VideoPlayerState.PLAY:
+ // 视频播放状态,修改播放按钮样式,启动进度刷新任务
+ this.showMenu(5000)
+ this.playBtnSrc = $r('app.media.ic_pause')
+ case VideoPlayerState.BUFFERING_END:
+ this.isBuffering = false
+ this.startDurationTasker()
+ break;
+ case VideoPlayerState.IDLE:
+ // 视频停止状态,修改播放按钮样式,停止进度刷新任务
+ case VideoPlayerState.ERROR:
+ this.stopDurationTasker()
+ case VideoPlayerState.FINISH:
+ this.curDuration = 0
+ case VideoPlayerState.STOP:
+ case VideoPlayerState.PAUSE:
+ this.showMenu()
+ this.playBtnSrc = $r('app.media.ic_play')
+ break;
+ }
+ })
+ ```
+
+6. 在使用[VideoGestureView](相关概念.md)时传入VideoService作为自定义组件参数。
+
+ ```
+ VideoGestureView({ service: this.service })
+ ```
+
+7. 手指按下时保存屏幕起始坐标,根据其实坐标和位移方向,设置当前模式为进度、音量或者亮度,手指移动时计算位移变量,手指松开后根据当前设置模式设置进度、音量或者亮度。
+
+ ```
+ // 手指按下
+ if (event.type === TouchType.Down) {
+ this.startFocusX = focusPoint.x
+ this.startFocusY = focusPoint.y
+ }
+
+ // 手指移动
+ onFingerMove(focusX, focusY) {
+ let changedValue
+ let offsetX = focusX - this.startFocusX
+ let offsetY = focusY - this.startFocusY
+ switch (this.gestureType) {
+ case VideoPlayerGestureType.IDLE:
+ // 根据按下的位置,设置当前模式
+ break;
+ case VideoPlayerGestureType.PROGRESS_CONTROL:
+ // 当前为进度控制模式,计算位移量
+ break;
+ case VideoPlayerGestureType.VOLUME_CONTROL:
+ // 当前为音量控制模式,计算位移量
+ break;
+ case VideoPlayerGestureType.BRIGHT_CONTROL:
+ // 当前为亮度控制模式,计算位移量
+ break;
+ }
+ }
+
+ // 手指松开
+ onFingerUp() {
+ switch (this.gestureType) {
+ case VideoPlayerGestureType.PROGRESS_CONTROL:
+ // 设置播放进度
+ this.service.seek(this.gestureValue)
+ break;
+ case VideoPlayerGestureType.VOLUME_CONTROL:
+ // 设置音量
+ this.service.setVolume(this.gestureValue)
+ break;
+ case VideoPlayerGestureType.BRIGHT_CONTROL:
+ // 设置亮度
+ this.service.setBrightness(this.gestureValue)
+ break;
+ }
+ // 重置当前模式
+ this.gestureType = VideoPlayerGestureType.IDLE
+ this.setViewShow(false)
+ }
+ ```
+
+# 媒体库列表界面
+
+媒体库列表界面由MediaLibView自定义组件构建,根据媒体类型不同展示不同UI风格的媒体库列表。
+
+
+
+1. 在使用MediaLibView时传入需要查询的媒体类型作为自定义组件参数。
+
+ ```
+ MediaLibView({
+ mediaTypes: [MediaType.VIDEO]
+ ...
+ })
+ ```
+
+2. MediaLibView初始化时使用[MediaLibOperator](相关概念.md)根据传入的媒体类型查询媒体数据,没有媒体类型默认查询全部类型的数据,根据数据类型设置列表item的样式。
+
+ ```
+ this.mediaLibOperator.getAllAssets(this.mediaTypes).then(async (assets) => {
+ for (let i = 0;i < assets.length; i++) {
+ let thumbnail
+ switch (assets[i].mediaType) {
+ case MediaType.FILE:
+ thumbnail = $r('app.media.ic_file')
+ break
+ case MediaType.IMAGE:
+ case MediaType.VIDEO:
+ thumbnail = await assets[i].getThumbnail()
+ break
+ case MediaType.AUDIO:
+ thumbnail = $r('app.media.ic_note')
+ break
+ }
+ let mediaInfo: MediaInfo = new MediaInfo(assets[i].id, assets[i].mediaType, assets[i].duration, thumbnail)
+ this.mediaList.push(mediaInfo)
+ }
+ if (this.controller != null) {
+ this.controller.setList(this.mediaList)
+ }
+ })
+ ```
+
+# 恭喜您
+
+目前你已经成功完成了本Codelab并且学到了:
+
+- 如何使用VideoPlayer。
+- 如何实现自定义播控菜单栏。
+- 如何通过手势控制播放进度、音量、亮度。
+
+- 如何通过媒体库访问媒体资源。
+
+
diff --git a/Media/VideoPlayerStage/build-profile.json5 b/Media/VideoPlayerStage/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ee735436c35d78a2c7ce41fe89b14c48abc76fe2
--- /dev/null
+++ b/Media/VideoPlayerStage/build-profile.json5
@@ -0,0 +1,26 @@
+{
+ "app": {
+ "compileSdkVersion": 9,
+ "compatibleSdkVersion": 9,
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default"
+ }
+ ],
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/build-profile.json5 b/Media/VideoPlayerStage/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..7dc37bb919dada5132609c409200db266559004f
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/build-profile.json5
@@ -0,0 +1,13 @@
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default",
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/hvigorfile.js b/Media/VideoPlayerStage/entry/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7720ee6a7aad5c617d1fd2f6fc8c87067bfa32c
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks
diff --git a/Media/VideoPlayerStage/entry/package.json b/Media/VideoPlayerStage/entry/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c7685ac4e7c0d79df04c96744f0d8f22cb4a9025
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/package.json
@@ -0,0 +1,14 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "entry",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "module",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {}
+}
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/Application/AbilityStage.ts b/Media/VideoPlayerStage/entry/src/main/ets/Application/AbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2fc7a5eba8bbf7a1cf090b51cc141eea99eb9f6b
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/Application/AbilityStage.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import AbilityStage from "@ohos.application.AbilityStage"
+
+export default class MyAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] MyAbilityStage onCreate")
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/MainAbility/MainAbility.ts b/Media/VideoPlayerStage/entry/src/main/ets/MainAbility/MainAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f3f1c9d4dacf92f67dbc41506908b3229da54538
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/MainAbility/MainAbility.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import SysPermissionUtils from '../util/SysPermissionUtils';
+import Ability from '@ohos.application.Ability'
+
+export default class MainAbility extends Ability {
+ onCreate(want, launchParam) {
+ let abilityInfo = this.context.abilityInfo;
+ globalThis.bundleName = abilityInfo.bundleName
+ globalThis.abilityWant = want;
+ globalThis.context = this.context
+ globalThis.LogTag = 'cwq'
+ globalThis.isShowLog = true
+ globalThis.display
+ }
+
+
+ onWindowStageCreate(windowStage) {
+ console.log("[Demo] MainAbility onWindowStageCreate")
+ globalThis.requestPermissions = (() => {
+ return SysPermissionUtils.requestWithinNecessary(null,
+ ["ohos.permission.READ_MEDIA",
+ "ohos.permission.WRITE_MEDIA",
+ "ohos.permission.MEDIA_LOCATION"
+ ]
+ )
+ })
+ windowStage.setUIContent(this.context, "pages/video_player", null)
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ console.log("[Demo] MainAbility onWindowStageDestroy")
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ console.log("[Demo] MainAbility onForeground")
+ }
+
+ onBackground() {
+ // Ability has back to background
+ console.log("[Demo] MainAbility onBackground")
+ }
+};
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/component/LoadingView.ets b/Media/VideoPlayerStage/entry/src/main/ets/component/LoadingView.ets
new file mode 100644
index 0000000000000000000000000000000000000000..cdc4008210723f97afbc7acbd44587601faadc00
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/component/LoadingView.ets
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@Component
+export default struct LoadingView {
+ private animTasker
+ @State rotateAngle: number = 0
+
+ aboutToAppear() {
+ this.animTasker = setInterval(() => {
+ this.rotateAngle = ((this.rotateAngle == 360) ? 0 : (this.rotateAngle + 1))
+ }, 10)
+ }
+
+ aboutToDisappear() {
+ clearInterval(this.animTasker)
+ }
+
+ build() {
+ Stack({ alignContent: Alignment.Center }) {
+ Image($r('app.media.ic_loading'))
+ .width('50px')
+ .height('50px')
+ .objectFit(ImageFit.Fill)
+ .rotate({
+ z: 1,
+ centerX: '50%',
+ centerY: '50%',
+ angle: this.rotateAngle,
+ })
+ }.width('100%').height('100%').backgroundColor('#00000000')
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/component/MediaLibView.ets b/Media/VideoPlayerStage/entry/src/main/ets/component/MediaLibView.ets
new file mode 100644
index 0000000000000000000000000000000000000000..c515e86fa6b7554322c0aab7d43cde498b7a4078
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/component/MediaLibView.ets
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import multimedia_image from '@ohos.multimedia.image';
+import { MediaType } from '../model/media/MediaConstants';
+import MediaLibOperator from '../model/media/MediaLibOperator';
+import PressureEffectButton from '../component/PressureEffectButton'
+import DateTimeUtils from '../util/DateTimeUtils'
+
+export { MediaLibView, MediaInfo, MediaLibController }
+
+@Component
+struct MediaLibView {
+ @State mediaList: Array = new Array()
+ private controller: MediaLibController
+ private mediaTypes: MediaType[]
+ private mediaLibOperator: MediaLibOperator = new MediaLibOperator()
+ private onItemClick: (item) => void
+
+ aboutToAppear() {
+ //系统multimedia_image服务空调用,避免multimedia_image被系统优化掉
+ multimedia_image.createPixelMap(
+ new ArrayBuffer(4096),
+ { size: { height: 1, width: 2 } },
+ (err, pixelMap) => {
+ if (pixelMap != null) {
+ this.initListData()
+ }
+ })
+ }
+
+ private initListData() {
+ this.mediaLibOperator.getAllAssets(this.mediaTypes).then(async (assets) => {
+ for (let i = 0;i < assets.length; i++) {
+ let thumbnail
+ switch (assets[i].mediaType) {
+ case MediaType.FILE:
+ thumbnail = $r('app.media.ic_file')
+ break
+ case MediaType.IMAGE:
+ case MediaType.VIDEO:
+ thumbnail = await assets[i].getThumbnail()
+ break
+ case MediaType.AUDIO:
+ thumbnail = $r('app.media.ic_note')
+ break
+ }
+ let mediaInfo: MediaInfo = new MediaInfo(assets[i].id, assets[i].mediaType, assets[i].duration, thumbnail)
+ this.mediaList.push(mediaInfo)
+ }
+ if (this.controller != null) {
+ this.controller.setList(this.mediaList)
+ }
+ })
+ }
+
+ build() {
+ Grid() {
+ ForEach(
+ this.mediaList,
+ (item) => {
+ GridItem() {
+ Stack({ alignContent: Alignment.BottomEnd }) {
+ PressureEffectButton({
+ commonSrc: item.thumbnail,
+ width: '100%',
+ height: '200',
+ bgColor: '#000000',
+ bgRadius: 5,
+ effectWidth: (item.mType == MediaType.IMAGE || item.mType == MediaType.VIDEO) ? '100%' : '60',
+ effectHeight: (item.mType == MediaType.IMAGE || item.mType == MediaType.VIDEO) ? '150' : '75',
+ })
+ if (item.mType == MediaType.AUDIO || item.mType == MediaType.VIDEO) {
+ Text(DateTimeUtils.ms2CountdownTime(item.duration)).fontColor('#ffffff').fontSize(18)
+ }
+ }
+ }.onClick(() => {
+ this.onItemClick(item)
+ })
+ },
+ item => item.id)
+ }
+ .width('100%')
+ .height('100%')
+ .columnsTemplate('1fr 1fr 1fr 1fr')
+ .columnsGap(10)
+ .rowsGap(10)
+ .padding(20)
+ }
+}
+
+class MediaInfo {
+ public id: number
+ public mType: number
+ public duration: number
+ public thumbnail: string | PixelMap | Resource
+
+ constructor(id, mType, duration, thumbnail) {
+ this.id = id
+ this.mType = mType
+ this.duration = duration
+ this.thumbnail = thumbnail
+ }
+}
+
+class MediaLibController {
+ public mediaList: Array
+
+ public setList(mediaList: Array) {
+ this.mediaList = mediaList
+ }
+
+ public addItem(item: MediaInfo) {
+ if (this.mediaList != null) {
+ this.mediaList.unshift(item)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/component/PressureEffectButton.ets b/Media/VideoPlayerStage/entry/src/main/ets/component/PressureEffectButton.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f956977df529ba89ad64efdd681d9ac89113e1f5
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/component/PressureEffectButton.ets
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@Component
+export default struct PressureEffectButton {
+ @State isEffected: boolean = false
+ @State isPressed: boolean = false
+ private width: string = '100px'
+ private height: string = '100px'
+ private bgColor: string = '#00000000'
+ private bgRadius: number = 0
+ private effectWidth: string
+ private effectHeight: string
+ private commonSrc: string | PixelMap | Resource
+ private effectSrc: string | PixelMap | Resource
+ private effectCallback: (isEffected: boolean) => void
+
+ aboutToAppear() {
+ if (!this.effectWidth) {
+ this.effectWidth = this.width
+ }
+ if (!this.effectHeight) {
+ this.effectHeight = this.height
+ }
+ }
+
+ build() {
+ Stack({ alignContent: Alignment.Center }) {
+ Image((this.isEffected && this.effectSrc != null) ? this.effectSrc : this.commonSrc)
+ .objectFit(ImageFit.Fill)
+ .width(this.effectWidth)
+ .height(this.effectHeight)
+ .borderRadius((this.effectHeight == this.height) ? this.bgRadius : 0)
+ }
+ .backgroundColor(this.bgColor)
+ .borderRadius(this.bgRadius)
+ .height(this.height)
+ .width(this.width)
+ .scale({ x: this.isPressed ? 0.8 : 1.0, y: this.isPressed ? 0.8 : 1.0 })
+ .opacity(this.isPressed ? 0.6 : 1.0)
+ .animation({
+ duration: this.isPressed || !this.effectSrc ? 300 : 0,
+ curve: Curve.Linear
+ })
+ .onTouch((event: TouchEvent) => {
+ if (event.type === TouchType.Down) {
+ this.isPressed = true
+ } else if (event.type === TouchType.Up) {
+ this.isPressed = false
+ this.isEffected = !this.isEffected
+ this.effectCallback(this.isEffected)
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/component/VideoController.ets b/Media/VideoPlayerStage/entry/src/main/ets/component/VideoController.ets
new file mode 100644
index 0000000000000000000000000000000000000000..68ca2480450b3bdb01465a503ebd924c022db76a
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/component/VideoController.ets
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MediaPlayService from '../model/media/MediaPlayService'
+import VideoGestureView from '../component/VideoGestureView'
+import LoadingView from './LoadingView';
+import { MediaPlayerState, MediaAsset } from '../model/media/MediaConstants';
+import DateTimeUtil from '../util/DateTimeUtils';
+import router from '@system.router';
+
+@Component
+export default struct VideoController {
+ @State isMenuShow: boolean = true
+ @State isBuffering: boolean = false
+ @State title: string = ''
+ @State playBtnSrc: Resource = $r('app.media.ic_play')
+ private service: MediaPlayService
+ @State curDuration: number = 0
+ @State totalDuration: number = 0
+ private menuShowTasker: number = -1
+ private durationTasker: number = -1
+ private dialogController: CustomDialogController = new CustomDialogController({
+ builder: SearchResourceDialog({
+ folderChosen: () => {
+ router.push({ uri: 'pages/video_lib' })
+ },
+ confirm: (url) => {
+ let playAsset = new MediaAsset()
+ playAsset.setTitle('在线测试视频')
+ playAsset.setSource(url)
+ this.service.loadAsset(playAsset, true);
+ }
+ }),
+ autoCancel: true,
+ alignment: DialogAlignment.Center
+ })
+
+ aboutToAppear() {
+ if (this.service != null) {
+ this.service.addStatusChangedListener((state, extra) => {
+ switch (state) {
+ case MediaPlayerState.START:
+ break;
+ case MediaPlayerState.LOAD:
+ this.title = extra.asset.getTitle()
+ this.totalDuration = 0
+ break;
+ case MediaPlayerState.PREPARED:
+ this.totalDuration = extra.duration
+ break
+ case MediaPlayerState.BUFFERING_START:
+ this.isBuffering = true
+ break;
+ case MediaPlayerState.PLAY:
+ this.showMenu(5000)
+ this.playBtnSrc = $r('app.media.ic_pause')
+ case MediaPlayerState.BUFFERING_END:
+ this.isBuffering = false
+ this.startDurationTasker()
+ break;
+ case MediaPlayerState.IDLE:
+ case MediaPlayerState.ERROR:
+ this.stopDurationTasker()
+ case MediaPlayerState.FINISH:
+ this.curDuration = 0
+ case MediaPlayerState.STOP:
+ case MediaPlayerState.PAUSE:
+ this.showMenu()
+ this.playBtnSrc = $r('app.media.ic_play')
+ break;
+
+ }
+ })
+ }
+ }
+
+ private startDurationTasker() {
+ if (this.service != null && this.durationTasker == -1) {
+ this.durationTasker = setInterval(() => {
+ if (this.service.getPlayerState() == MediaPlayerState.PLAY) {
+ this.curDuration = this.service.getCurrentTime()
+ }
+ }, 1000)
+ }
+ }
+
+ private stopDurationTasker() {
+ if (this.durationTasker) {
+ clearInterval(this.durationTasker)
+ this.durationTasker = -1
+ }
+ }
+
+ private showMenu(duration?: number) {
+ clearTimeout(this.menuShowTasker)
+ this.isMenuShow = true
+ if (duration) {
+ this.hideMenu(duration)
+ }
+ }
+
+ private hideMenu(delayTime?: number) {
+ clearTimeout(this.menuShowTasker)
+ this.menuShowTasker = setTimeout(() => {
+ this.menuShowTasker = -1
+ this.isMenuShow = false
+ }, delayTime ? delayTime : 0)
+ }
+
+ @Builder TopMenu() {
+ Stack({ alignContent: Alignment.End }) {
+ Text(this.title)
+ .fontSize('58px')
+ .fontColor('#ffffff')
+ .width('100%')
+ .height('100%')
+ .textAlign(TextAlign.Center)
+ Button({ type: ButtonType.Normal, stateEffect: true }) {
+ Image($r('app.media.ic_search')).width(30).height(30).objectFit(ImageFit.Fill)
+ }
+ .width(90)
+ .height(90)
+ .margin({ right: 20 })
+ .backgroundColor('#00000000')
+ .onClick(() => {
+ this.showMenu(5000)
+ this.dialogController.open()
+ })
+ }
+ .position({ x: 0, y: 0 })
+ .linearGradient({
+ direction: GradientDirection.Bottom,
+ colors: [['#CC000000', 0.0], ['#88000000', 0.4], ['#44000000', 0.8], ['#00000000', 1.0]]
+ })
+ .width('100%')
+ .height(this.isMenuShow ? '15%' : '0%')
+ .opacity(this.isMenuShow ? 1.0 : 0)
+ .animation({
+ duration: 300,
+ curve: Curve.Smooth,
+ })
+ }
+
+ @Builder CenterMenu() {
+ Stack() {
+ if (this.isBuffering) {
+ LoadingView()
+ } else if (this.isMenuShow) {
+ Button({ type: ButtonType.Circle, stateEffect: true }) {
+ Image(this.playBtnSrc).width(30).height(30).objectFit(ImageFit.Fill)
+ }
+ .width('100%')
+ .height('100%')
+ .backgroundColor('#88000000')
+ .onClick(() => {
+ this.showMenu()
+ if (this.service != null) {
+ if (this.service.getPlayerState() == MediaPlayerState.PLAY) {
+ this.service.pause()
+ } else if (this.service.getPlayerState() == MediaPlayerState.PAUSE) {
+ this.service.resume()
+ } else {
+ this.service.play()
+ }
+ }
+ })
+ }
+ }.width(this.isBuffering || this.isMenuShow ? 60 : 0)
+ .height(this.isBuffering || this.isMenuShow ? 60 : 0)
+ .position({ x: '50%', y: '50%' })
+ .markAnchor({ x: '50%', y: '50%' })
+ }
+
+ @Builder BottomMenu() {
+ Row() {
+ Text(DateTimeUtil.ms2CountdownTime(this.curDuration))
+ .fontSize(25)
+ .fontColor(Color.White)
+ Slider({ value: this.curDuration / this.service.getDuration() * 100, max: 100, min: 0 })
+ .selectedColor(Color.White)
+ .layoutWeight(1)
+ .trackColor('#5a5a5a')
+ .trackThickness(5)
+ .selectedColor('#FF6103')
+ .onChange((value: number, mode: SliderChangeMode) => {
+ switch (mode) {
+ case SliderChangeMode.Begin:
+ this.showMenu()
+ this.stopDurationTasker()
+ break;
+ case SliderChangeMode.Moving:
+ this.curDuration = Math.round(value / 100 * this.service.getDuration() / 1000) * 1000
+ break;
+ case 3:
+ this.curDuration = Math.round(value / 100 * this.service.getDuration() / 1000) * 1000
+ case SliderChangeMode.End:
+ this.showMenu(5000)
+ if (this.service != null) {
+ this.service.seek(value / 100 * this.service.getDuration())
+ }
+ break;
+ }
+ })
+ Text(DateTimeUtil.ms2CountdownTime(this.totalDuration))
+ .fontSize(25)
+ .fontColor(Color.White)
+ }
+ .linearGradient({
+ direction: GradientDirection.Top,
+ colors: [['#CC000000', 0.0], ['#88000000', 0.4], ['#44000000', 0.8], ['#00000000', 1.0]]
+ })
+ .padding({ left: 20, right: 20 })
+ .justifyContent(FlexAlign.Center)
+ .width('100%')
+ .height(this.isMenuShow ? '15%' : '0%')
+ .opacity(this.isMenuShow ? 1.0 : 0)
+ .animation({
+ duration: 300,
+ curve: Curve.Smooth,
+ })
+ }
+
+ build() {
+ Stack({ alignContent: Alignment.Bottom }) {
+ VideoGestureView({
+ service: this.service,
+ onStateUpdate: (isGestureViewShow) => {
+ isGestureViewShow ? this.hideMenu() : this.showMenu(5000)
+ }
+ })
+ this.TopMenu()
+ this.CenterMenu()
+ this.BottomMenu()
+ }.height('100%').width('100%').backgroundColor('#00000000')
+ .onClick(() => {
+ if (this.service != null) {
+ this.isMenuShow ? this.hideMenu() : this.showMenu(5000)
+ }
+ })
+ }
+}
+
+@CustomDialog
+struct SearchResourceDialog {
+ controller: CustomDialogController
+ confirm: (url) => void
+ folderChosen: () => void
+ private inputUrl: string = 'https://ss0.bdstatic.com/-0U0bnSm1A5BphGlnYG/cae-legoup-video-target/93be3d88-9fc2-4fbd-bd14-833bca731ca7.mp4'
+
+ build() {
+ Column() {
+ Row() {
+ TextInput({ placeholder: this.inputUrl })
+ .inputFilter("/(http:|https:|data)\/\/([\w.]+\/?)\S*/", (value) => {
+ this.inputUrl = value
+ }).layoutWeight(1)
+ Button({ type: ButtonType.Normal, stateEffect: true }) {
+ Image($r('app.media.ic_folder'))
+ .width(30)
+ .height(30)
+ .objectFit(ImageFit.Fill)
+ }.backgroundColor('#00000000').margin({ left: 10 }).onClick(() => {
+ this.controller.close()
+ this.folderChosen()
+ })
+ }.margin({ bottom: 30 })
+
+ Button('confirm')
+ .onClick(() => {
+ this.controller.close()
+ this.confirm(this.inputUrl)
+ })
+ }.padding({ left: 20, right: 20, top: 40, bottom: 30 })
+ }
+}
+
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/component/VideoGestureView.ets b/Media/VideoPlayerStage/entry/src/main/ets/component/VideoGestureView.ets
new file mode 100644
index 0000000000000000000000000000000000000000..d420081e19183435ec84652492719b4df595ef6a
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/component/VideoGestureView.ets
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MediaPlayService from '../model/media/MediaPlayService';
+import { MediaPlayerState, VideoPlayerGestureType } from '../model/media/MediaConstants';
+import DateTimeUtils from '../util/DateTimeUtils'
+
+@Component
+export default struct VideoGestureView {
+ private service: MediaPlayService
+ private startFocusX: number
+ private startFocusY: number
+ private gestureValue: number
+ private gestureType: VideoPlayerGestureType = VideoPlayerGestureType.IDLE
+ @State controlHint: string = ''
+ @State controlTypeSrc: Resource = undefined
+ @State isShow: boolean = false
+ private onStateUpdate: (isShow: boolean) => void
+
+ aboutToAppear() {
+ if (this.service != null) {
+ this.service.addStatusChangedListener((state, extra) => {
+ switch (state) {
+ case MediaPlayerState.STOP:
+ case MediaPlayerState.IDLE:
+ case MediaPlayerState.ERROR:
+ case MediaPlayerState.FINISH:
+ this.setViewShow(false)
+ break;
+ }
+ })
+ }
+ }
+
+ private setViewShow(isShow: boolean) {
+ if (isShow != this.isShow) {
+ this.isShow = isShow
+ this.onStateUpdate(isShow)
+ }
+ }
+
+ private getFocusPoint(event: TouchEvent) {
+ let sumX = 0;
+ let sumY = 0;
+ let count = event.touches.length;
+ for (let i = 0;i < event.touches.length; i++) {
+ sumX += event.touches[i].x
+ sumY += event.touches[i].y
+ }
+ return { x: sumX / count, y: sumY / count };
+ }
+
+ onFingerMove(focusX, focusY) {
+ let changedValue
+ let offsetX = focusX - this.startFocusX
+ let offsetY = focusY - this.startFocusY
+ switch (this.gestureType) {
+ case VideoPlayerGestureType.IDLE:
+ if (Math.abs(offsetX) == Math.abs(offsetY)) {
+ break
+ } else if (Math.abs(offsetX) > Math.abs(offsetY)) {
+ this.gestureType = VideoPlayerGestureType.PROGRESS_CONTROL
+ } else if (this.startFocusX > px2vp(globalThis.display.width) / 2) {
+ this.gestureType = VideoPlayerGestureType.VOLUME_CONTROL
+ } else {
+ this.gestureType = VideoPlayerGestureType.BRIGHT_CONTROL
+ }
+ this.setViewShow(true)
+ break;
+ case VideoPlayerGestureType.PROGRESS_CONTROL:
+ changedValue = this.service.getCurrentTime() + offsetX / px2vp(globalThis.display.width) * this.service.getDuration()
+ this.gestureValue = (changedValue <= 0 ? 0 : (changedValue >= this.service.getDuration() ? this.service.getDuration() : changedValue))
+ this.controlHint = DateTimeUtils.ms2CountdownTime(Math.round(this.gestureValue / 1000) * 1000) + '/' + DateTimeUtils.ms2CountdownTime(this.service.getDuration())
+ this.controlTypeSrc = (offsetX >= 0 ? $r('app.media.ic_forward') : $r('app.media.ic_backward'))
+ break;
+ case VideoPlayerGestureType.VOLUME_CONTROL:
+ changedValue = this.service.getVolume() - offsetY / px2vp(globalThis.display.height)
+ this.gestureValue = (changedValue <= 0 ? 0 : (changedValue >= 1 ? 1 : changedValue))
+ this.controlHint = Math.round(this.gestureValue * 100) + '%'
+ this.controlTypeSrc = $r('app.media.ic_horns')
+ break;
+ case VideoPlayerGestureType.BRIGHT_CONTROL:
+ changedValue = this.service.getBrightness() - offsetY / px2vp(globalThis.display.height)
+ this.gestureValue = (changedValue <= 0 ? 0 : (changedValue >= 1 ? 1 : changedValue))
+ this.controlHint = Math.round(this.gestureValue * 100) + '%'
+ this.controlTypeSrc = $r('app.media.ic_bright')
+ break;
+ }
+ }
+
+ onFingerUp() {
+ switch (this.gestureType) {
+ case VideoPlayerGestureType.PROGRESS_CONTROL:
+ this.service.seek(this.gestureValue)
+ break;
+ case VideoPlayerGestureType.VOLUME_CONTROL:
+ this.service.setVolume(this.gestureValue)
+ break;
+ case VideoPlayerGestureType.BRIGHT_CONTROL:
+ this.service.setBrightness(this.gestureValue)
+ break;
+ }
+ this.gestureType = VideoPlayerGestureType.IDLE
+ this.setViewShow(false)
+ }
+
+ build() {
+ Stack({ alignContent: Alignment.Center }) {
+ if (this.isShow) {
+ Column() {
+ Image(this.controlTypeSrc)
+ .width(40)
+ .height(40)
+ .objectFit(ImageFit.Fill)
+ Text(this.controlHint).margin({ top: 15 }).fontColor('#ffffff')
+ }
+ .width(120)
+ .height(120)
+ .padding({ left: 10, right: 10, top: 20, bottom: 20 })
+ .justifyContent(FlexAlign.Center)
+ .alignItems(HorizontalAlign.Center)
+ .backgroundColor('#88000000')
+ .borderRadius(10)
+ }
+ }
+ .width('100%')
+ .height('100%')
+ .backgroundColor('#00000000')
+ .onTouch((event) => {
+ if (this.service != null
+ && (this.service.getPlayerState() == MediaPlayerState.PLAY
+ || this.service.getPlayerState() == MediaPlayerState.PAUSE)
+ ) {
+ let focusPoint = this.getFocusPoint(event)
+ if (event.type === TouchType.Down) {
+ this.startFocusX = focusPoint.x
+ this.startFocusY = focusPoint.y
+ } else if (event.type === TouchType.Move) {
+ this.onFingerMove(focusPoint.x, focusPoint.y)
+ } else if (event.type === TouchType.Up) {
+ this.onFingerUp()
+ }
+ }
+ })
+ .gesture(
+ TapGesture({ count: 2 })
+ .onAction(() => {
+ if (this.service != null) {
+ if (this.service.getPlayerState() == MediaPlayerState.PLAY) {
+ this.service.pause()
+ } else {
+ this.service.resume()
+ }
+ }
+ })
+ )
+ }
+}
+
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/component/VideoView.ets b/Media/VideoPlayerStage/entry/src/main/ets/component/VideoView.ets
new file mode 100644
index 0000000000000000000000000000000000000000..56881e3a5badc1dee34dcc2152dc65d8e43439ae
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/component/VideoView.ets
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MediaPlayService from '../model/media/MediaPlayService'
+import { MediaPlayerState } from '../model/media/MediaConstants'
+import prompt from '@system.prompt';
+
+@Component
+export default struct VideoView {
+ private service: MediaPlayService
+ private playerAreaController = new XComponentController()
+ private isContentFull: boolean = false
+ @State ratio: number = 0
+ private onCreated: (id) => void
+
+ aboutToAppear() {
+ if (this.service != null) {
+ this.service.addStatusChangedListener((state, extra) => {
+ switch (state) {
+ case MediaPlayerState.SIZE_CHANGED:
+ if (!this.isContentFull) {
+ this.ratio = extra.width / extra.height
+ this.playerAreaController.setXComponentSurfaceSize({
+ surfaceWidth: extra.width,
+ surfaceHeight: extra.height
+ })
+ }
+ break;
+ case MediaPlayerState.ERROR:
+ if (extra.code == -331350011 || extra.code == -331350014 || extra.code == -331350007) {
+ prompt.showDialog({ message: '播放错误,请检查播放源,如果是在线视频,请检查网络是否连接。错误码:' + extra.code })
+ }
+ break;
+ }
+ })
+ }
+ }
+
+ build() {
+ Stack() {
+ XComponent({
+ id: '',
+ type: 'surface',
+ libraryname: '',
+ controller: this.playerAreaController
+ })
+ .onLoad(() => {
+ let renderId = this.playerAreaController.getXComponentSurfaceId()
+ if (this.service != null) {
+ this.service.addSurface(renderId)
+ }
+ this.onCreated(renderId)
+ })
+ .aspectRatio(this.ratio)
+ }.backgroundColor('#ffffff')
+ .width('100%')
+ .height('100%')
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaAssetBuilder.ts b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaAssetBuilder.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a571962afd7bbf4c715b260646ed33ec558246e1
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaAssetBuilder.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fileIO from '@ohos.fileio';
+import MediaLibOperator from './MediaLibOperator';
+import { MediaAsset, MediaOperationAsset, MediaSourceType } from './MediaConstants';
+
+/*
+ *音视频播放服务类
+ */
+export default class MediaAssetBuilder {
+ private mediaLibOperator: MediaLibOperator
+ private mediaLibAsset: MediaOperationAsset
+ private mediaAsset: MediaAsset
+ private realUrl
+ private sourceType: MediaSourceType = MediaSourceType.DEFAULT
+ private fileDescription: number
+
+ constructor() {
+ this.mediaLibOperator = new MediaLibOperator()
+ }
+
+ public async build(asset: MediaAsset) {
+ this.mediaAsset = asset
+ let originSource = this.mediaAsset.getSource()
+ if (!isNaN(originSource)) {
+ //Src为数字=fileId
+ this.mediaLibAsset = await this.mediaLibOperator.openMediaFileOperationById(originSource)
+ this.fileDescription = this.mediaLibAsset.getFd()
+ this.sourceType = MediaSourceType.MEDIA_LIB_FILE
+ this.mediaAsset.setTitle(this.mediaLibAsset.getAsset().displayName)
+ this.realUrl = "fd://" + this.mediaLibAsset.getFd()
+ } else if (originSource.startsWith('data/') || originSource.startsWith('/data')) {
+ let fd = fileIO.openSync(originSource)
+ this.fileDescription = fd
+ this.sourceType = MediaSourceType.ABSOLUTE_PATH_FILE
+ this.realUrl = "fd://" + fd
+ } else if (originSource.indexOf('../resources/rawfile/') >= 0) {
+ let rawfileFd = await globalThis.context.resourceManager.getRawFileDescriptor(originSource)
+ this.fileDescription = rawfileFd.fd
+ this.sourceType = MediaSourceType.RAWFILE_FILE
+ this.realUrl = "fd://" + rawfileFd.fd
+ } else {
+ this.realUrl = originSource
+ }
+ return this.mediaAsset
+ }
+
+ public getRealUrl() {
+ return this.realUrl
+ }
+
+ public async release() {
+ switch (this.sourceType) {
+ case MediaSourceType.MEDIA_LIB_FILE:
+ if (this.mediaLibAsset != null) {
+ await this.mediaLibOperator.closeOMediaFileOperation(this.mediaLibAsset.getAsset(), this.fileDescription)
+ this.mediaLibAsset = null
+ }
+ break
+ case MediaSourceType.ABSOLUTE_PATH_FILE:
+ fileIO.closeSync(this.fileDescription)
+ break
+ case MediaSourceType.RAWFILE_FILE:
+ globalThis.context.resourceManager.closeRawFileDescriptor(this.mediaAsset.getSource())
+ break
+ }
+ this.mediaAsset = null
+ this.sourceType = MediaSourceType.DEFAULT
+ this.fileDescription = -1
+ }
+}
+
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaConstants.ts b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaConstants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9d98f273439f52e1d82a22b67c88a611eccdac07
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaConstants.ts
@@ -0,0 +1,252 @@
+export { TAGS, AudioPlayerState, VideoRecorderState, CaptureMode, AudioRecordState, MediaType, FrameConfigInfo,
+ MediaOperationAsset, MediaPlayerState, MediaPlaySpeed, VideoPlayerGestureType, StatusChangedListener,
+ EncodeAction, MediaAsset, MediaSourceType, AudioCaptureAction, AudioRenderAction, AudioPlaySpeed,
+ DecodeAction, CodecState }
+
+interface StatusChangedListener {
+ (state, extra): void
+}
+
+enum TAGS {
+ CAMERA_SERVICE = '[CameraService]',
+ VIDEO_DECODER = '[VideoDecoder]',
+ VIDEO_ENCODER = '[VideoEncoder]',
+ VIDEO_RECORDER = '[VideoRecorder]',
+ MEDIA_PLAYER = '[MediaPlayer]',
+ AUDIO_RECORDER = '[AudioRecorder]',
+ AUDIO_PLAYER = '[AudioPlayer]',
+ AUDIO_CAPTURE = '[AudioCapture]',
+ MEDIA_PLAYER_LIFECYCLE = '[MediaPlayerLifeCycle]'
+}
+
+enum MediaPlaySpeed {
+ SPEED_FORWARD_0_75_X = 0,
+ SPEED_FORWARD_1_00_X = 1,
+ SPEED_FORWARD_1_25_X = 2,
+ SPEED_FORWARD_1_75_X = 3,
+ SPEED_FORWARD_2_00_X = 4,
+}
+
+enum VideoPlayerGestureType {
+ IDLE,
+ PROGRESS_CONTROL,
+ VOLUME_CONTROL,
+ BRIGHT_CONTROL
+}
+
+enum MediaPlayerState {
+ IDLE,
+ LOAD,
+ PREPARED,
+ START,
+ PLAY,
+ PAUSE,
+ STOP,
+ ERROR,
+ FINISH,
+ BUFFERING_START,
+ BUFFERING_END,
+ BUFFERING_PERCENT,
+ CACHED_DURATION,
+ SIZE_CHANGED,
+}
+
+enum MediaSourceType {
+ DEFAULT,
+ MEDIA_LIB_FILE,
+ ABSOLUTE_PATH_FILE,
+ RAWFILE_FILE
+}
+
+enum AudioPlayerState {
+ IDLE,
+ LOAD,
+ PLAY,
+ PAUSE,
+ STOP,
+ ERROR,
+ FINISH,
+ PROGRESS_SPEED,
+ TIME_UPDATE,
+ VOLUME_CHANGE
+}
+
+enum VideoRecorderState {
+ IDLE,
+ CONFIGURED,
+ START,
+ PAUSE,
+ STOP,
+ RESET,
+ RELEASE,
+ ERROR
+}
+
+enum AudioPlaySpeed {
+ RENDER_RATE_NORMAL = 0,
+ RENDER_RATE_DOUBLE = 1,
+ RENDER_RATE_HALF = 2,
+}
+
+enum CaptureMode {
+ PHOTO,
+ VIDEO,
+ ENCODE,
+ MULTI
+}
+
+enum AudioRecordState {
+ IDLE,
+ PREPARE,
+ START,
+ COLLECT_BUFFER,
+ PAUSE,
+ STOP,
+ RELEASE,
+ RESET,
+ ERROR
+}
+
+enum MediaType {
+ FILE = 1,
+ IMAGE = 3,
+ VIDEO = 4,
+ AUDIO = 5,
+}
+
+enum EncodeAction {
+ INIT,
+ START,
+ STOP,
+ TERMINATE
+}
+
+enum DecodeAction {
+ INIT,
+ START,
+ LOAD_BUFFER,
+ STOP,
+ TERMINATE
+}
+
+enum CodecState {
+ IDLE,
+ PREPARED,
+ STARTED,
+ CODE_CHANGE,
+ CODE_CHANGING,
+ CODE_CHANGED,
+ STOP,
+ RESET,
+ RELEASE,
+ ERROR
+}
+
+enum AudioCaptureAction {
+ INIT,
+ START,
+ PAUSE,
+ RESUME,
+ STOP,
+ TERMINATE
+}
+
+enum AudioRenderAction {
+ INIT,
+ START,
+ PAUSE,
+ SPEED,
+ STOP,
+ TERMINATE
+}
+
+
+class MediaOperationAsset {
+ private operationId: number = -1
+ private asset: any
+
+ public setFd(fd: number) {
+ this.operationId = fd
+ }
+
+ public getFd() {
+ return this.operationId
+ }
+
+ public setAsset(asset: any) {
+ this.asset = asset
+ }
+
+ public getAsset() {
+ return this.asset
+ }
+}
+
+class MediaAsset {
+ private title: string
+ private source
+
+ public setTitle(playTitle: string) {
+ this.title = playTitle
+ }
+
+ public getTitle(): string {
+ return this.title
+ }
+
+ public setSource(src) {
+ this.source = src
+ }
+
+ public getSource() {
+ return this.source
+ }
+}
+
+class FrameConfigInfo {
+ private index
+ private timeMs
+ private offset
+ private length
+ private flags
+
+ public setIndex(index) {
+ this.index = index
+ }
+
+ public getIndex() {
+ return this.index
+ }
+
+ public setTimeMs(timeMs) {
+ this.timeMs = timeMs
+ }
+
+ public getTimeMs() {
+ return this.timeMs
+ }
+
+ public setOffset(offset) {
+ this.offset = offset
+ }
+
+ public getOffset() {
+ return this.offset
+ }
+
+ public setLength(length) {
+ this.length = length
+ }
+
+ public getLength() {
+ return this.length
+ }
+
+ public setFlags(flags) {
+ this.flags = flags
+ }
+
+ public getFlags() {
+ return this.flags
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaLibOperator.ts b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaLibOperator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3049257f7a3a7c9a9baa0f25aecc5c77a7129297
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaLibOperator.ts
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import mediaLibrary from '@ohos.multimedia.mediaLibrary';
+import { MediaOperationAsset, MediaType } from '../media/MediaConstants'
+
+export default class MediaLibOperator {
+ private sysMediaLib
+
+ public constructor() {
+ this.initOperator();
+ }
+
+ private initOperator() {
+ this.sysMediaLib = mediaLibrary.getMediaLibrary(globalThis.context);
+ }
+
+ private getSaveDir(mediaType: MediaType) {
+ switch (mediaType) {
+ case MediaType.IMAGE:
+ return mediaLibrary.DirectoryType.DIR_IMAGE
+ case MediaType.AUDIO:
+ return mediaLibrary.DirectoryType.DIR_AUDIO
+ case MediaType.VIDEO:
+ return mediaLibrary.DirectoryType.DIR_VIDEO
+ case MediaType.FILE:
+ return mediaLibrary.DirectoryType.DIR_DOCUMENTS
+ }
+ }
+
+ private getSysMediaType(mediaType: MediaType) {
+ switch (mediaType) {
+ case MediaType.IMAGE:
+ return mediaLibrary.MediaType.IMAGE
+ case MediaType.AUDIO:
+ return mediaLibrary.MediaType.AUDIO
+ case MediaType.VIDEO:
+ return mediaLibrary.MediaType.VIDEO
+ case MediaType.FILE:
+ return mediaLibrary.MediaType.FILE
+ default:
+ return mediaLibrary.MediaType.IMAGE
+ }
+ }
+
+ /*
+ *打开新建媒资操作通道
+ *
+ *return fileAsset and fd
+ */
+ async createMediaFile(creationName, mediaType: MediaType): Promise {
+ let publicPath = await this.sysMediaLib.getPublicDirectory(this.getSaveDir(mediaType));
+ let mediaBuilderAsset = await this.sysMediaLib.createAsset(this.getSysMediaType(mediaType), creationName, publicPath)
+ let fd = await this.openMediaFileOperation(mediaBuilderAsset, 'rw')
+ if (fd > 0) {
+ let operationAsset = new MediaOperationAsset()
+ operationAsset.setAsset(mediaBuilderAsset)
+ operationAsset.setFd(fd)
+ return operationAsset
+ }
+ }
+
+ /*
+ *获取媒资操作符操作权限
+ *
+ *return fd
+ */
+ async openMediaFileOperation(mediaFileAsset, operation): Promise {
+ if (mediaFileAsset != null) {
+ let operationAuth = await mediaFileAsset.open(operation);
+ if (operationAuth > 0) {
+ return operationAuth
+ }
+ }
+ }
+
+ async closeOMediaFileOperation(mediaFileAsset, fd): Promise {
+ if (mediaFileAsset != null) {
+ mediaFileAsset.close(fd)
+ }
+ }
+
+ /*
+ * 打开指定媒资操作通道
+ *
+ *return fileAsset and fd
+ */
+ async openMediaFileOperationById(fileId): Promise {
+ let fileKeyObj = mediaLibrary.FileKey;
+ let fetchOp = {
+ selections: fileKeyObj.ID + '= ?',
+ selectionArgs: [fileId.toString()],
+ order: fileKeyObj.DATE_ADDED + " DESC",
+ }
+ let fetchFileResult = await this.sysMediaLib.getFileAssets(fetchOp);
+ let fileAsset = await fetchFileResult.getFirstObject();
+ let fd = await this.openMediaFileOperation(fileAsset, 'r')
+ if (fd > 0) {
+ let operationAsset = new MediaOperationAsset()
+ operationAsset.setAsset(fileAsset)
+ operationAsset.setFd(fd)
+ return operationAsset
+ }
+ }
+
+ async getAsset(mediaType, fileId) {
+ let fileKeyObj = mediaLibrary.FileKey;
+ let fetchOp = {
+ selections: fileKeyObj.ID + '= ?',
+ selectionArgs: [fileId.toString()],
+ };
+ let fetchFileResult = await this.sysMediaLib.getFileAssets(fetchOp);
+ let fileAsset = await fetchFileResult.getFirstObject();
+ return fileAsset
+ }
+
+ /*
+ *@param mediaType 非必需,不传时查全部类型
+ */
+ async getAllAssets(mediaTypes?: MediaType[]) {
+ if (mediaTypes == null) {
+ mediaTypes = []
+ }
+ if (mediaTypes.length == 0) {
+ mediaTypes.push(MediaType.FILE)
+ mediaTypes.push(MediaType.IMAGE)
+ mediaTypes.push(MediaType.AUDIO)
+ mediaTypes.push(MediaType.VIDEO)
+ }
+ let selections = ''
+ let selectionArgs = []
+ let fileKeyObj = mediaLibrary.FileKey;
+ for (let i = 0; i < mediaTypes.length; i++) {
+ selections += ((i == 0 ? '' : ' or ') + fileKeyObj.MEDIA_TYPE + '= ?')
+ selectionArgs.push(this.getSysMediaType(mediaTypes[i]).toString())
+ }
+ let fetchOp = {
+ selections: selections,
+ selectionArgs: selectionArgs,
+ order: fileKeyObj.DATE_ADDED + " DESC",
+ };
+ let fetchFileResult = await this.sysMediaLib.getFileAssets(fetchOp);
+ let fileAssets = await fetchFileResult.getAllObject();
+ return fileAssets
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaPlayService.ts b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaPlayService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c92b857558ae0b8e9b799ff64b4ecd567b43efe
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/model/media/MediaPlayService.ts
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MediaAssetBuilder from './MediaAssetBuilder'
+import brightness from '@ohos.brightness';
+import { StatusChangedListener, MediaPlayerState, TAGS, MediaPlaySpeed, MediaAsset, MediaOperationAsset
+} from './MediaConstants';
+import media from '@ohos.multimedia.media'
+import LogUtils from '../../util/LogUtils'
+
+export default class MediaPlayService {
+ private mediaAssetBuilder: MediaAssetBuilder
+ private state = MediaPlayerState.IDLE
+ private mediaPlayer
+ private displaySurfaceId
+ private displayNeedChanged: boolean = false
+ private isPrepared: boolean
+ private volumeValue: number = 0.5
+ private brightness: number = 0.5
+ private statusChangedListeners: StatusChangedListener[]
+ private mediaAsset: MediaAsset
+ private mediaLibAsset: MediaOperationAsset
+
+ constructor() {
+ this.mediaAssetBuilder = new MediaAssetBuilder()
+ this.statusChangedListeners = new Array()
+ this.initPlayer()
+ }
+
+ private initPlayer() {
+ media.createVideoPlayer().then((player) => {
+ if (player != null) {
+ LogUtils.info(TAGS.MEDIA_PLAYER, 'createVideoPlayer success!');
+ this.mediaPlayer = player
+ this.setCallback()
+ }
+ }, this.failureCallback).catch(this.catchCallback)
+ }
+
+ /*
+ *play/seek/playbackCompleted回调BUFFERING_START和BUFFERING_END
+ */
+ private setCallback() {
+ this.mediaPlayer.on('playbackCompleted', () => {
+ this.stepCallback(MediaPlayerState.FINISH, null)
+ });
+ this.mediaPlayer.on('bufferingUpdate', (infoType, value) => {
+ switch (infoType) {
+ case media.BufferingInfoType.BUFFERING_START:
+ this.stepCallback(MediaPlayerState.BUFFERING_START, value)
+ break;
+ case media.BufferingInfoType.BUFFERING_END:
+ this.stepCallback(MediaPlayerState.BUFFERING_END, value)
+ break;
+ case media.BufferingInfoType.BUFFERING_PERCENT:
+ this.stepCallback(MediaPlayerState.BUFFERING_PERCENT, value)
+ break;
+ case media.BufferingInfoType.CACHED_DURATION:
+ this.stepCallback(MediaPlayerState.CACHED_DURATION, value)
+ break;
+ }
+ if (this.getCurrentTime() == this.getDuration()) {
+ this.state = MediaPlayerState.FINISH
+ } else if (this.mediaPlayer.state == 'playing' || this.mediaPlayer.state == 'prepared') {
+ this.state = MediaPlayerState.PLAY
+ } else if (this.mediaPlayer.state == 'paused') {
+ this.state = MediaPlayerState.PAUSE
+ } else if (this.mediaPlayer.state == 'stopped') {
+ this.state = MediaPlayerState.STOP
+ } else if (this.mediaPlayer.state == 'error') {
+ this.state = MediaPlayerState.ERROR
+ }
+ });
+ this.mediaPlayer.on('startRenderFrame', () => {
+ this.stepCallback(MediaPlayerState.START, null)
+ this.state = MediaPlayerState.PLAY
+ });
+ this.mediaPlayer.on('videoSizeChanged', (width, height) => {
+ this.stepCallback(MediaPlayerState.SIZE_CHANGED, {
+ width: width, height: height
+ })
+ });
+ this.mediaPlayer.on('error', (error) => {
+ this.stepCallback(MediaPlayerState.ERROR, error)
+ });
+ }
+
+ private getKeyFrame(ms: number): number{
+ return Math.round(ms / 1000) * 1000
+ }
+
+ public addStatusChangedListener(listener: StatusChangedListener) {
+ this.statusChangedListeners.push(listener)
+ }
+
+ public addSurface(surfaceId) {
+ this.displaySurfaceId = surfaceId
+ this.displayNeedChanged = true
+ }
+
+ /*
+ *@param asset.playSrc string|number
+ * 支持网络路径:http://
+ * hls直播流:m3u8
+ * 支持本地路径:data/
+ * 支持媒体库fileId
+ * 支持项目路径../../resources/rawfile/
+ */
+ public async loadAsset(asset: MediaAsset, isAutoPlay: boolean, seekMs?: number) {
+ await this.stop()
+ if (this.mediaAsset == null || this.mediaAsset.getSource() != asset.getSource()) {
+ await this.reset()
+ this.mediaAsset = await this.mediaAssetBuilder.build(asset)
+ this.mediaPlayer.url = this.mediaAssetBuilder.getRealUrl()
+ this.stepCallback(MediaPlayerState.LOAD, {
+ asset: this.mediaAsset
+ })
+ }
+ if (this.displayNeedChanged) {
+ await this.mediaPlayer.setDisplaySurface(this.displaySurfaceId)
+ this.displayNeedChanged = false
+ }
+ this.start(isAutoPlay, seekMs)
+ }
+
+ private start(isAutoPlay: boolean, seekMs?: number) {
+ this.mediaPlayer.prepare().then(() => {
+ this.isPrepared = true
+ this.stepCallback(MediaPlayerState.PREPARED, {
+ duration: this.getDuration()
+ })
+ if (isAutoPlay) {
+ this.play(seekMs)
+ }
+ }).catch((error) => {
+ this.stepCallback(MediaPlayerState.ERROR, error)
+ });
+ }
+
+ public async play(seekMs?: number) {
+ if (!this.isPrepared) {
+ this.start(true, seekMs)
+ } else {
+ this.mediaPlayer.play().then(() => {
+ if (seekMs) {
+ this.seek(seekMs)
+ }
+ this.stepCallback(MediaPlayerState.PLAY, null)
+ })
+ }
+ }
+
+ public pause() {
+ if (this.isPrepared && this.state == MediaPlayerState.PLAY) {
+ this.mediaPlayer.pause().then(() => {
+ this.stepCallback(MediaPlayerState.PAUSE, null)
+ });
+ }
+ }
+
+ public resume() {
+ if (this.state == MediaPlayerState.PAUSE) {
+ this.play()
+ }
+ }
+
+ public seek(ms) {
+ if (this.isPrepared && this.state != MediaPlayerState.ERROR) {
+ let seekMode = this.getCurrentTime() < ms ? 0 : 1
+ let realTime = (ms <= 0 ? 0 : (ms >= this.getDuration() ? this.getDuration() : this.getKeyFrame(ms)))
+ this.mediaPlayer.seek(realTime, seekMode)
+ }
+ }
+
+ public setSpeed(speed: MediaPlaySpeed) {
+ if (this.isPrepared) {
+ this.mediaPlayer.setSpeed(speed)
+ }
+ }
+
+ /*
+ *@param vol [0.00-1.00]
+ */
+ public async setVolume(vol) {
+ if (this.isPrepared) {
+ let value = (vol <= 0 ? 0 : (vol >= 1 ? 1 : vol))
+ await this.mediaPlayer.setVolume(value)
+ this.volumeValue = value
+ }
+ }
+
+ public getVolume() {
+ return this.volumeValue
+ }
+
+ /*
+ *@param bright [0.00-1.00] bright设置为0时屏幕无亮度
+ */
+ public setBrightness(bright: number) {
+ let value = (bright <= 0 ? 0.1 : (bright >= 1 ? 1 : bright))
+ brightness.setValue(value * 255)
+ this.brightness = bright
+ }
+
+ public getBrightness() {
+ return this.brightness
+ }
+
+ public resizeScreen(width: number, height: number) {
+ if (width >= 0 && height >= 0) {
+ this.stepCallback(MediaPlayerState.SIZE_CHANGED, {
+ width: width, height: height
+ })
+ }
+ }
+
+ public async stop() {
+ if (this.isPrepared) {
+ await this.mediaPlayer.stop()
+ this.stepCallback(MediaPlayerState.STOP, null)
+ }
+ }
+
+ private async reset() {
+ await this.mediaAssetBuilder.release()
+ await this.mediaPlayer.reset()
+ this.isPrepared = false
+ }
+
+ public release() {
+ this.stop()
+ this.reset()
+ this.mediaPlayer.release()
+ this.stepCallback(MediaPlayerState.IDLE, null)
+ this.statusChangedListeners.length = 0
+ this.statusChangedListeners = null
+ }
+
+ public getCurrentTime() {
+ if (this.isPrepared) {
+ return this.mediaPlayer.currentTime
+ }
+ return 0
+ }
+
+ public getDuration() {
+ if (this.isPrepared) {
+ return this.mediaPlayer.duration
+ }
+ return 0
+ }
+
+ async getTrackDescription() {
+ return await this.mediaPlayer.getTrackDescription()
+ }
+
+ public getPlayerState() {
+ return this.state
+ }
+
+ public getMediaAsset(): MediaAsset{
+ return this.mediaAsset
+ }
+
+ /* 函数调用发生状态改变 */
+ private stepCallback(state, extra) {
+ LogUtils.info(TAGS.MEDIA_PLAYER, 'stepCallback, state:' + state + ',extra is ' + extra);
+ this.state = state
+ for (let i = 0; i < this.statusChangedListeners.length; i++) {
+ this.statusChangedListeners[i](state, extra)
+ }
+ }
+
+ failureCallback= (error) => {
+ LogUtils.info(TAGS.MEDIA_PLAYER, `error happened,error Name is ${error.name}`);
+ LogUtils.info(TAGS.MEDIA_PLAYER, `error happened,error Code is ${error.code}`);
+ LogUtils.info(TAGS.MEDIA_PLAYER, `error happened,error Message is ${error.message}`);
+ }
+
+ // 当函数调用发生异常时用于上报错误信息
+ catchCallback= (error) => {
+ LogUtils.info(TAGS.MEDIA_PLAYER, `catch error happened,error Name is ${error.name}`);
+ LogUtils.info(TAGS.MEDIA_PLAYER, `catch error happened,error Code is ${error.code}`);
+ LogUtils.info(TAGS.MEDIA_PLAYER, `catch error happened,error Message is ${error.message}`);
+ }
+}
+
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/pages/video_lib.ets b/Media/VideoPlayerStage/entry/src/main/ets/pages/video_lib.ets
new file mode 100644
index 0000000000000000000000000000000000000000..35aab766589e593acdf51c80887b2f78a8afa8e3
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/pages/video_lib.ets
@@ -0,0 +1,34 @@
+import prompt from '@system.prompt';
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { MediaType } from '../model/media/MediaConstants';
+import router from '@system.router';
+import { MediaLibView } from '../component/MediaLibView'
+
+@Entry
+@Component
+struct media_lib {
+ build() {
+ Column() {
+ MediaLibView({
+ mediaTypes: [MediaType.VIDEO],
+ onItemClick: (item) => {
+ router.back({ uri:'pages/video_player',params: { fileId: item.id } })
+ }
+ })
+ }.width('100%').height('100%')
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/pages/video_player.ets b/Media/VideoPlayerStage/entry/src/main/ets/pages/video_player.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2c05db23155f111f5fb77f5cf28ff21baa4c78c1
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/pages/video_player.ets
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import display from '@ohos.display';
+import { MediaAsset } from '../model/media/MediaConstants';
+import router from '@system.router';
+import VideoView from '../component/VideoView'
+import VideoController from '../component/VideoController'
+import MediaPlayService from '../model/media/MediaPlayService'
+
+@Entry
+@Component
+struct video_player {
+ private videoService: MediaPlayService= new MediaPlayService()
+ private curVideoAsset
+ @State isAuth: boolean = false
+
+ aboutToAppear() {
+ globalThis.requestPermissions().then((isAuth) => {
+ this.isAuth = isAuth
+ })
+ display.getDefaultDisplay((err, data) => {
+ if (!err.code) {
+ globalThis.display = data;
+ }
+ });
+ }
+
+ aboutToDisappear() {
+ this.videoService.release()
+ }
+
+ onPageShow() {
+ if (router.getParams() != null) {
+ let fileId = router.getParams().fileId
+ this.loadPlayerAsset('', Number(fileId), true)
+ }
+ }
+
+ onPageHide() {
+ this.videoService.pause()
+ }
+
+ private loadPlayerAsset(title: string, url: string | number, isAutoPlay: boolean) {
+ let playAsset = new MediaAsset()
+ playAsset.setTitle(title)
+ playAsset.setSource(url)
+ this.videoService.loadAsset(playAsset, isAutoPlay);
+ }
+
+ build() {
+ Stack() {
+ if (this.isAuth) {
+ VideoView({
+ service: this.videoService,
+ isContentFull: false,
+ onCreated: (id) => {
+ this.loadPlayerAsset('video_test_1.mp4', '../../resources/rawfile/video_test_1.mp4', false)
+ }
+ })
+ VideoController({ service: this.videoService })
+ }
+ }
+ .height('100%').width('100%')
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/util/DateTimeUtils.ts b/Media/VideoPlayerStage/entry/src/main/ets/util/DateTimeUtils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a2d3fbec339ca4e645acb081d08ea5f408599dfe
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/util/DateTimeUtils.ts
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file: 日期工具
+ */
+
+export default class DateTimeUtil {
+
+ /**
+ * 时分秒
+ *
+ * @return {string} - 返回时分秒
+ */
+ static getTime() {
+ const DATETIME = new Date();
+ const HOURS = DATETIME.getHours();
+ const MINUTES = DATETIME.getMinutes();
+ const SECONDS = DATETIME.getSeconds();
+ return this.concatTime(HOURS, MINUTES, SECONDS);
+ }
+
+ /**
+ * 年月日
+ *
+ * @return {string} - 返回年月日
+ */
+ static getDate() {
+ const DATETIME = new Date();
+ const YEAR = DATETIME.getFullYear();
+ const MONTH = DATETIME.getMonth() + 1;
+ const DAY = DATETIME.getDate();
+ return this.concatDate(YEAR, MONTH, DAY);
+ }
+
+ /**
+ * 日期不足两位补 0
+ *
+ * @param {string} value - 数据值
+ * @return {string} - 日期不足两位补 0
+ */
+ static fill(value: number) {
+ return (value < 10 ? `0${value}` : `${value}`);
+ }
+
+ /**
+ * 年月日格式修饰
+ *
+ * @param {string} year - 年
+ * @param {string} month - 月
+ * @param {string} date - 日
+ * @return {string} - 年月日格式修饰
+ */
+ static concatDate(year, month, date) {
+ return `${year}${month}${date}`;
+ }
+
+ /**
+ * 时分秒格式修饰
+ *
+ * @param {string} hours - 时
+ * @param {string} minutes - 分
+ * @param {string} seconds - 秒
+ * @return {string} - 时分秒格式修饰
+ */
+ static concatTime(hours, minutes, seconds) {
+ return `${this.fill(hours)}${this.fill(minutes)}${this.fill(seconds)}`;
+ }
+
+ static dateFormat(timestamp?) {
+ const t = new Date(timestamp)
+ let year = t.getFullYear()
+ let month = t.getMonth() + 1
+ let day = t.getDate()
+ let hours = t.getHours()
+ let minutes = t.getMinutes()
+ let seconds = t.getSeconds()
+ return year + "-" + this.fill(month) + "-" + this.fill(day) + " " + this.fill(hours) + ":" + this.fill(minutes) + ":" + this.fill(seconds);
+ }
+
+ static ms2CountdownTime(ms) {
+ if (!ms)return '00:00'
+ const days = Math.floor(ms / (1000 * 60 * 60 * 24))
+ const hours = Math.floor((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+ const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((ms % (1000 * 60)) / 1000);
+ return (days ? this.fill(days) + ':' : '') +
+ (hours ? this.fill(hours) + ':' : '') +
+ this.fill(minutes) + ':' +
+ this.fill(seconds)
+ }
+}
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/util/LogUtils.ts b/Media/VideoPlayerStage/entry/src/main/ets/util/LogUtils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..66ee1d43b4d178f4e32a345aaf73fc400e5eba01
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/util/LogUtils.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import hiLog from '@ohos.hilog';
+
+export default class LogUtils {
+ private static domain: number = 0xFF00
+
+ static info(tag, msg: string) {
+ if (globalThis.isShowLog) {
+ hiLog.info(this.domain, globalThis.LogTag, "%{public}s " + msg, tag);
+ }
+ }
+
+ static debug(tag, msg: string) {
+ if (globalThis.isShowLog) {
+ hiLog.debug(this.domain, globalThis.LogTag, "%{public}s " + msg, tag);
+ }
+ }
+
+ static error(tag, msg: string) {
+ if (globalThis.isShowLog) {
+ hiLog.error(this.domain, globalThis.LogTag, "%{public}s " + msg, tag);
+ }
+ }
+
+ static fatal(tag, msg: string) {
+ if (globalThis.isShowLog) {
+ hiLog.fatal(this.domain, globalThis.LogTag, "%{public}s " + msg, tag);
+ }
+ }
+
+ static warn(tag, msg: string) {
+ if (globalThis.isShowLog) {
+ hiLog.warn(this.domain, globalThis.LogTag, "%{public}s " + msg, tag);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/ets/util/SysPermissionUtils.ts b/Media/VideoPlayerStage/entry/src/main/ets/util/SysPermissionUtils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8529699c0b290af039a8c785474b12e67e099810
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/ets/util/SysPermissionUtils.ts
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022 Huawei Device Co., Ltd.
+ * Licensed under the Apache License,Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import bundle from '@ohos.bundle'
+import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
+import LogUtils from './LogUtils';
+
+export default class SysPermissionUtils {
+ static async hasAuth(permission) {
+ let bundleFlag = 0
+ let userId = 100
+ let appInfo = await bundle.getApplicationInfo(globalThis.bundleName, bundleFlag, userId)
+ let tokenId = appInfo.accessTokenId
+ let atManager = abilityAccessCtrl.createAtManager()
+ let result = await atManager.verifyAccessToken(tokenId, permission)
+ return result == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
+ }
+
+ static async request(targetPermissionArray) {
+ let needPermissionArray: Array = new Array()
+ for (let targetPermission of targetPermissionArray) {
+ let isAuth = await this.hasAuth(targetPermission)
+ if (!isAuth) {
+ needPermissionArray.push(targetPermission)
+ }
+ }
+ if (needPermissionArray.length > 0) {
+ globalThis.context.requestPermissionsFromUser(needPermissionArray)
+ }
+ }
+
+ static async requestWithinNecessary(targetPermissionArray: string[], necessaryPermissionArray?: string[]) {
+ let isNecessaryPermissionAuth: boolean = true
+ let needPermissionArray: Array = new Array()
+ if (targetPermissionArray != null) {
+ for (let targetPermission of targetPermissionArray) {
+ let isTargetAuth = await this.hasAuth(targetPermission)
+ if (!isTargetAuth) {
+ needPermissionArray.push(targetPermission)
+ }
+ }
+ }
+ if (necessaryPermissionArray != null) {
+ for (let necessaryPermission of necessaryPermissionArray) {
+ let isNecessaryAuth = await this.hasAuth(necessaryPermission)
+ if (!isNecessaryAuth) {
+ needPermissionArray.push(necessaryPermission)
+ isNecessaryPermissionAuth = false
+ }
+ }
+ }
+ if (needPermissionArray.length > 0) {
+ let result = await globalThis.context.requestPermissionsFromUser(needPermissionArray)
+ LogUtils.info('SysPermissionUtils', 'request is called,result is ' + JSON.stringify(result))
+ if (!isNecessaryPermissionAuth) {
+ isNecessaryPermissionAuth = true
+ out: for (let i = 0; i < needPermissionArray.length; i++) {
+ for (let j = 0; j < necessaryPermissionArray.length; j++) {
+ if (needPermissionArray[i] == necessaryPermissionArray[j] && (result.authResults[i] == abilityAccessCtrl.GrantStatus.PERMISSION_DENIED)) {
+ isNecessaryPermissionAuth = false
+ break out
+ }
+ }
+ }
+ }
+ }
+ return isNecessaryPermissionAuth
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/module.json5 b/Media/VideoPlayerStage/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..6e4214cf9853472696603c3840a56ff0e65c1ebd
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/module.json5
@@ -0,0 +1,57 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "srcEntrance": "./ets/Application/AbilityStage.ts",
+ "description": "$string:entry_desc",
+ "mainElement": "MainAbility",
+ "deviceTypes": [
+ "phone",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "MainAbility",
+ "srcEntrance": "./ets/MainAbility/MainAbility.ts",
+ "description": "$string:MainAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:MainAbility_label",
+ "visible": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": "ohos.permission.READ_MEDIA"
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA"
+ },
+ {
+ "name": "ohos.permission.WRITE_USER_STORAGE"
+ },
+ {
+ "name": "ohos.permission.READ_USER_STORAGE"
+ },
+ {
+ "name": "ohos.permission.MEDIA_LOCATION"
+ },
+ {
+ "name": "ohos.permission.INTERNET"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/element/string.json b/Media/VideoPlayerStage/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..0585be1f13ac23f6e605bfafdfa655e71c3a7bf0
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,24 @@
+{
+ "string": [
+ {
+ "name": "entry_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_label",
+ "value": "VideoPlayerStage"
+ },
+ {
+ "name": "MediaLibAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "MediaLibAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_backward.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_backward.png
new file mode 100644
index 0000000000000000000000000000000000000000..2696217ec5156e83b88adf46d87d4c5bfd328075
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_backward.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_bright.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_bright.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1ea29d98fa6edc34951519a4cccaf03ad2ffcc5
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_bright.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_direction_change.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_direction_change.png
new file mode 100644
index 0000000000000000000000000000000000000000..51126eb60b11f12660140f1ff278814929db173e
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_direction_change.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_file.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_file.png
new file mode 100644
index 0000000000000000000000000000000000000000..4bc7ada4231095505b49fd8c5b4bcda891fb84cd
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_file.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_folder.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_folder.png
new file mode 100644
index 0000000000000000000000000000000000000000..9cdd74ca494f00d4d63e7dca0c36c986d0d0a63d
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_folder.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_forward.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_forward.png
new file mode 100644
index 0000000000000000000000000000000000000000..b52b99f1c209870d3c4a9b083b767eac2209bec8
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_forward.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_horns.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_horns.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee0de06494813d36e55aeb9a557bffdd3e755432
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_horns.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_loading.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_loading.png
new file mode 100644
index 0000000000000000000000000000000000000000..a3c061f5b4753fbd669792b8992dc0cfb9122d19
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_loading.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_note.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_note.png
new file mode 100644
index 0000000000000000000000000000000000000000..e750b7cc2b36937cc413008d223a41a71e5d9aa5
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_note.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_pause.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..16fa2ff0901918a1a26b0d5feea0cef5a3baf856
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_pause.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_play.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_play.png
new file mode 100644
index 0000000000000000000000000000000000000000..8824afa863b6cac7228d6301838f60dbd5bf4d65
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_play.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_search.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..b8f8977e9347c05fc8d2b9a50306e9658ed566eb
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_search.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_update.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_update.png
new file mode 100644
index 0000000000000000000000000000000000000000..1b63a318d863b5b236da9c83f616334b702dde82
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/ic_update.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/media/icon.png b/Media/VideoPlayerStage/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/base/media/icon.png differ
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/base/profile/main_pages.json b/Media/VideoPlayerStage/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..b5ab98cd5a480be0fcd1705ae12437ba2bd88dd2
--- /dev/null
+++ b/Media/VideoPlayerStage/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,6 @@
+{
+ "src": [
+ "pages/video_player",
+ "pages/video_lib"
+ ]
+}
diff --git a/Media/VideoPlayerStage/entry/src/main/resources/rawfile/video_test_1.mp4 b/Media/VideoPlayerStage/entry/src/main/resources/rawfile/video_test_1.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..e34d0d9f9dcbc724142de146a3537d756a21e267
Binary files /dev/null and b/Media/VideoPlayerStage/entry/src/main/resources/rawfile/video_test_1.mp4 differ
diff --git a/Media/VideoPlayerStage/figures/1.png b/Media/VideoPlayerStage/figures/1.png
new file mode 100644
index 0000000000000000000000000000000000000000..c388f6c7c398f9af4cc9c621c9b2d0512520f2ba
Binary files /dev/null and b/Media/VideoPlayerStage/figures/1.png differ
diff --git a/Media/VideoPlayerStage/figures/Screenshot_1501923660641.jpg b/Media/VideoPlayerStage/figures/Screenshot_1501923660641.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fbf1d8bbcea44cc2a779b177fd08439f25a8245e
Binary files /dev/null and b/Media/VideoPlayerStage/figures/Screenshot_1501923660641.jpg differ
diff --git a/Media/VideoPlayerStage/figures/Screenshot_1501924822509.jpg b/Media/VideoPlayerStage/figures/Screenshot_1501924822509.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0d0734aff3897a9c34f9d95b8e91636a3885a8ff
Binary files /dev/null and b/Media/VideoPlayerStage/figures/Screenshot_1501924822509.jpg differ
diff --git a/Media/VideoPlayerStage/figures/zh-cn_attachment_0000001333004829-00_00_00-00_00_30-2.gif b/Media/VideoPlayerStage/figures/zh-cn_attachment_0000001333004829-00_00_00-00_00_30-2.gif
new file mode 100644
index 0000000000000000000000000000000000000000..1e240fe94613600aa4c6e0b65edffd517b1fbf93
Binary files /dev/null and b/Media/VideoPlayerStage/figures/zh-cn_attachment_0000001333004829-00_00_00-00_00_30-2.gif differ
diff --git a/Media/VideoPlayerStage/figures/zh-cn_image_0000001280949160.png b/Media/VideoPlayerStage/figures/zh-cn_image_0000001280949160.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d752ff5311af2816bf898cb2c18ef33bf085726
Binary files /dev/null and b/Media/VideoPlayerStage/figures/zh-cn_image_0000001280949160.png differ
diff --git a/Media/VideoPlayerStage/hvigorfile.js b/Media/VideoPlayerStage/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f2735e3deeaf655828407544bbed9365c258278
--- /dev/null
+++ b/Media/VideoPlayerStage/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').appTasks
\ No newline at end of file
diff --git a/Media/VideoPlayerStage/package.json b/Media/VideoPlayerStage/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..11cf2d03fc71427744af36c70d0e8b28eff688d4
--- /dev/null
+++ b/Media/VideoPlayerStage/package.json
@@ -0,0 +1,18 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "videoplayerstage",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "project",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {
+ "@ohos/hvigor-ohos-plugin": "1.0.6",
+ "hypium": "^1.0.0",
+ "@ohos/hvigor": "1.0.6"
+ }
+}
diff --git a/Media/VideoPlayerStage/public_sys-resources/icon-caution.gif b/Media/VideoPlayerStage/public_sys-resources/icon-caution.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571
Binary files /dev/null and b/Media/VideoPlayerStage/public_sys-resources/icon-caution.gif differ
diff --git a/Media/VideoPlayerStage/public_sys-resources/icon-danger.gif b/Media/VideoPlayerStage/public_sys-resources/icon-danger.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571
Binary files /dev/null and b/Media/VideoPlayerStage/public_sys-resources/icon-danger.gif differ
diff --git a/Media/VideoPlayerStage/public_sys-resources/icon-note.gif b/Media/VideoPlayerStage/public_sys-resources/icon-note.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda
Binary files /dev/null and b/Media/VideoPlayerStage/public_sys-resources/icon-note.gif differ
diff --git a/Media/VideoPlayerStage/public_sys-resources/icon-notice.gif b/Media/VideoPlayerStage/public_sys-resources/icon-notice.gif
new file mode 100644
index 0000000000000000000000000000000000000000..86024f61b691400bea99e5b1f506d9d9aef36e27
Binary files /dev/null and b/Media/VideoPlayerStage/public_sys-resources/icon-notice.gif differ
diff --git a/Media/VideoPlayerStage/public_sys-resources/icon-tip.gif b/Media/VideoPlayerStage/public_sys-resources/icon-tip.gif
new file mode 100644
index 0000000000000000000000000000000000000000..93aa72053b510e456b149f36a0972703ea9999b7
Binary files /dev/null and b/Media/VideoPlayerStage/public_sys-resources/icon-tip.gif differ
diff --git a/Media/VideoPlayerStage/public_sys-resources/icon-warning.gif b/Media/VideoPlayerStage/public_sys-resources/icon-warning.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571
Binary files /dev/null and b/Media/VideoPlayerStage/public_sys-resources/icon-warning.gif differ
diff --git a/README.md b/README.md
index ac5b619e1def247cec9fcd48d7a1fff1276c2208..52c19368a4edbba516601c3f090614e39b07c5e8 100644
--- a/README.md
+++ b/README.md
@@ -7,51 +7,53 @@
## 目录
- Ability
- - [Page内和Page间导航跳转(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Ability/PageAbility)(API8+)
- - [为应用添加运行时权限(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Ability/AccessPermission)(API9+)
+ - [Page内和Page间导航跳转(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Ability/PageAbility)(API 8+)
+ - [为应用添加运行时权限(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Ability/AccessPermission)(API 9+)
- ArkUI
- - [极简声明式UI范式(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/SimpleGalleryEts)(API8+)
- - [购物应用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/ShoppingEts)(API8+)
- - [购物应用(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/ShoppingOpenHarmony)(API8+)
- - [自定义组件(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/JSCanvasComponet)(API8+)
- - [转场动画的使用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/TransitionAnimtaionEts)(API8+)
- - [基础组件Slider的使用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/SliderApplicationEts)(API8+)
- - [image、image-animator(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/ClickableJsDemo)(API8+)
- - [动画样式(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/AnimationDemo)(API8+)
- - [dialog(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/DialogDemo)(API8+)
- - [input、label(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/InputApplication)(API8+)
- - [rating(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/RatingApplication)(API8+)
- - [slider(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/SliderApplication)(API8+)
- - [switch、chart(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/SwitchApplication)(API8+)
- - [流式布局(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/FlowLayoutEts)(API8+)
- - [弹窗(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/CustomDialogEts)(API8+)
- - [一次开发多端部署(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultiDeploymentEts)(API8+)
- - [Web组件加载本地H5小程序(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/WebComponent)(API9+)
- - [像素转换(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/PixelUnitsDemo)(API9+)
+ - [极简声明式UI范式(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/SimpleGalleryEts)(API 8+)
+ - [购物应用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/ShoppingEts)(API 8+)
+ - [购物应用(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/ShoppingOpenHarmony)(API 8+)
+ - [自定义组件(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/JSCanvasComponet)(API 8+)
+ - [转场动画的使用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/TransitionAnimtaionEts)(API 8+)
+ - [基础组件Slider的使用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/SliderApplicationEts)(API 8+)
+ - [image、image-animator(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/ClickableJsDemo)(API 8+)
+ - [动画样式(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/AnimationDemo)(API 8+)
+ - [dialog(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/DialogDemo)(API 8+)
+ - [input、label(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/InputApplication)(API 8+)
+ - [rating(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/RatingApplication)(API 8+)
+ - [slider(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/SliderApplication)(API 8+)
+ - [switch、chart(JS)](https://gitee.com/openharmony/codelabs/tree/master/JSUI/SwitchApplication)(API 8+)
+ - [流式布局(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/FlowLayoutEts)(API 8+)
+ - [弹窗(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/CustomDialogEts)(API 8+)
+ - [一次开发多端部署(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultiDeploymentEts)(API 8+)
+ - [Web组件加载本地H5小程序(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/WebComponent)(API 9+)
+ - [像素转换(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/PixelUnitsDemo)(API 9+)
+ - [ArkUI常用布局容器对齐方式(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/LayoutAlignmentDemo)(API 9+)
- 分布式
- - [分布式调度启动远程FA(JS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/RemoteStartFA)(API8+)
- - [分布式新闻客户端(JS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/NewsDemo)(API8+)
- - [分布式手写板(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/DistributeDatabaseDrawEts)(API8+)
- - [分布式鉴权(JS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/GameAuthOpenH)(API8+)
- - [分布式游戏手柄(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/HandleGameApplication)(API8+)
- - [分布式邮件(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/OHMailETS)(API8+)
- - [分布式亲子早教系统(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/OpenHarmonyPictureGame)(API8+)
- - [分布式遥控器(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/RemoteControllerETS)(API8+)
+ - [分布式调度启动远程FA(JS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/RemoteStartFA)(API 8+)
+ - [分布式新闻客户端(JS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/NewsDemo)(API 8+)
+ - [分布式手写板(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/DistributeDatabaseDrawEts)(API 8+)
+ - [分布式鉴权(JS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/GameAuthOpenH)(API 8+)
+ - [分布式游戏手柄(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/HandleGameApplication)(API 8+)
+ - [分布式邮件(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/OHMailETS)(API 8+)
+ - [分布式亲子早教系统(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/OpenHarmonyPictureGame)(API 8+)
+ - [分布式遥控器(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Distributed/RemoteControllerETS)(API 8+)
- 公共事件与通知
- - [闹钟应用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/CommonEventAndNotification/AlarmClock)(API9+)
+ - [闹钟应用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/CommonEventAndNotification/AlarmClock)(API 9+)
- 媒体
- - [图片编辑模板(JS)](https://gitee.com/openharmony/codelabs/tree/master/Media/ImageEditorTemplate)(API8+)
- - [图片常见操作(JS)](https://gitee.com/openharmony/codelabs/tree/master/Media/ImageJsDemo)(API8+)
- - [简易视频播放器(JS)](https://gitee.com/openharmony/codelabs/tree/master/Media/VideoOpenHarmony)(API8+)
- - [音频播放器(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Media/Audio_OH_ETS)(API9+)
+ - [图片编辑模板(JS)](https://gitee.com/openharmony/codelabs/tree/master/Media/ImageEditorTemplate)(API 8+)
+ - [图片常见操作(JS)](https://gitee.com/openharmony/codelabs/tree/master/Media/ImageJsDemo)(API 8+)
+ - [简易视频播放器(JS)](https://gitee.com/openharmony/codelabs/tree/master/Media/VideoOpenHarmony)(API 8+)
+ - [音频播放器(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Media/Audio_OH_ETS)(API 9+)
+ - [视频播放器(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Media/VideoPlayerStage)(API 9+)
- NativeAPI
- - [第一个Native C++应用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/NativeTemplateDemo)(API9+)
- - [Native Component(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/XComponent)(API9+)
+ - [第一个Native C++应用(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/NativeTemplateDemo)(API 9+)
+ - [Native Component(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/XComponent)(API 9+)
- 数据库
- - [分布式数据库(JS)](https://gitee.com/openharmony/codelabs/tree/master/Data/JsDistributedData)(API8+)
- - [关系型数据库(JS)](https://gitee.com/openharmony/codelabs/tree/master/Data/JSRelationshipData)(API8+)
- - [轻量级偏好数据库(JS)](https://gitee.com/openharmony/codelabs/tree/master/Data/Database)(API8+)
- - [备忘录(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Data/NotePad_OH_ETS)(API8+)
+ - [分布式数据库(JS)](https://gitee.com/openharmony/codelabs/tree/master/Data/JsDistributedData)(API 8+)
+ - [关系型数据库(JS)](https://gitee.com/openharmony/codelabs/tree/master/Data/JSRelationshipData)(API 8+)
+ - [轻量级偏好数据库(JS)](https://gitee.com/openharmony/codelabs/tree/master/Data/Database)(API 8+)
+ - [备忘录(eTS)](https://gitee.com/openharmony/codelabs/tree/master/Data/NotePad_OH_ETS)(API 8+)
- 设备开发
- [轻量系统环境搭建指导](https://gitee.com/openharmony/codelabs/tree/master/Device/DeviceEnvironmentSetupGuide)(指导文档)
- [日志、线程、定时器](https://gitee.com/openharmony/codelabs/tree/master/Device/%E6%97%A5%E5%BF%97%E3%80%81%E7%BA%BF%E7%A8%8B%E3%80%81%E5%AE%9A%E6%97%B6%E5%99%A8)(开发板类型:Hi3861,OpenHarmony系统:OpenHarmony 3.0 LTS)
@@ -64,11 +66,13 @@
- [OLED屏幕显示](https://gitee.com/openharmony/codelabs/tree/master/Device/OLED%E5%B1%8F%E5%B9%95%E7%9A%84%E6%98%BE%E7%A4%BA)(开发板类型:Hi3861,OpenHarmony系统:OpenHarmony 3.0 LTS)
- [智能门禁](https://gitee.com/openharmony/codelabs/tree/master/Device/smart_door_access)(开发板类型:RK3568,OpenHarmony系统:OpenHarmony 3.0.2 LTS)
- 三方库
- - [VCard(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ThirdPartyComponents/VCardDemo)(API8+)
+ - [VCard(eTS)](https://gitee.com/openharmony/codelabs/tree/master/ThirdPartyComponents/VCardDemo)(API 8+)
- 网络管理
- - [使用UDP实现与服务端通信(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/UdpDemoOH)(API9+)
- - [使用HTTP实现与服务端通信(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/SmartChatEtsOH)(API9+)
- - [使用TCP实现与服务端通信(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/TcpSocketDemo)(API9+)
+ - [使用UDP实现与服务端通信(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/UdpDemoOH)(API 9+)
+ - [使用HTTP实现与服务端通信(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/SmartChatEtsOH)(API 9+)
+ - [使用TCP实现与服务端通信(eTS)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/TcpSocketDemo)(API 9+)
+- 图形图像
+ - [手势截屏(eTS)](https://gitee.com/openharmony/codelabs/tree/master/GraphicImage/GestureScreenshot)(API 9+)
## 使用说明