diff --git a/zh-cn/application-dev/reference/arkui-ts/Readme-CN.md b/zh-cn/application-dev/reference/arkui-ts/Readme-CN.md index f75b98a10733627de2d38e4ab22950deeff3d749..38d46f170edc8f55ec3d41a30a1e009a2338afee 100644 --- a/zh-cn/application-dev/reference/arkui-ts/Readme-CN.md +++ b/zh-cn/application-dev/reference/arkui-ts/Readme-CN.md @@ -159,5 +159,7 @@ - [时间选择弹窗](ts-methods-timepicker-dialog.md) - [文本选择弹窗](ts-methods-textpicker-dialog.md) - [菜单](ts-methods-menu.md) +- [应用级变量的状态管理](ts-state-management.md) +- [像素单位](ts-pixel-units.md) - [枚举说明](ts-appendix-enums.md) - [类型说明](ts-types.md) diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169582302.gif b/zh-cn/application-dev/reference/arkui-ts/figures/zh-cn_image_0000001169582302.gif similarity index 100% rename from zh-cn/application-dev/ui/figures/zh-cn_image_0000001169582302.gif rename to zh-cn/application-dev/reference/arkui-ts/figures/zh-cn_image_0000001169582302.gif diff --git a/zh-cn/application-dev/ui/ts-pixel-units.md b/zh-cn/application-dev/reference/arkui-ts/ts-pixel-units.md similarity index 87% rename from zh-cn/application-dev/ui/ts-pixel-units.md rename to zh-cn/application-dev/reference/arkui-ts/ts-pixel-units.md index 3a4c5c7e6a0ef27361f80529265cff853e5600db..aa077567dec5f971cb4ecc13de55214026107413 100644 --- a/zh-cn/application-dev/ui/ts-pixel-units.md +++ b/zh-cn/application-dev/reference/arkui-ts/ts-pixel-units.md @@ -3,12 +3,12 @@ 为开发者提供4种像素单位,框架采用vp为基准数据单位。 -| 名称 | 描述 | -| ---- | ---------------------------------------- | -| px | 屏幕物理像素单位。 | -| vp | 屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位vp。 | -| fp | 字体像素,与vp类似适用屏幕密度变化,随系统字体大小设置变化。 | -| lpx | 视窗逻辑像素单位,lpx单位为实际屏幕宽度与逻辑宽度(通过[designWidth](../quick-start/package-structure.md)配置)的比值,designWidth默认值为720。当designWidth为720时,在实际宽度为1440物理像素的屏幕上,1lpx为2px大小。 | +| 名称 | 描述 | +| ---- | ------------------------------------------------------------ | +| px | 屏幕物理像素单位。 | +| vp | 屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位vp。 | +| fp | 字体像素,与vp类似适用屏幕密度变化,随系统字体大小设置变化。 | +| lpx | 视窗逻辑像素单位,lpx单位为实际屏幕宽度与逻辑宽度(通过[designWidth](../../quick-start/package-structure.md)配置)的比值,designWidth默认值为720。当designWidth为720时,在实际宽度为1440物理像素的屏幕上,1lpx为2px大小。 | ## 像素单位转换 diff --git a/zh-cn/application-dev/ui/Readme-CN.md b/zh-cn/application-dev/ui/Readme-CN.md index 259444e873b0f02ba1fdb248958108daa64e7f29..228ed9614b70659d608782fc64435eb2cb1a4a45 100755 --- a/zh-cn/application-dev/ui/Readme-CN.md +++ b/zh-cn/application-dev/ui/Readme-CN.md @@ -10,25 +10,27 @@ - 资源管理 - [资源文件的分类](ui-ts-basic-resource-file-categories.md) - [资源访问](ts-resource-access.md) - - [像素单位](ts-pixel-units.md) - - 深入理解组件化 + - 自定义组件 - [自定义组件初始化](ts-custom-component-initialization.md) - [自定义组件生命周期回调函数](ts-custom-component-lifecycle-callbacks.md) - [组件创建和重新初始化示例](ts-component-creation-re-initialization.md) + + - 声明式开发实例 + - [创建简单视图](ui-ts-creating-simple-page.md) + - 构建实例 + - [构建食物数据模型](ui-ts-building-data-model.md) + - [构建食物列表List布局](ui-ts-building-category-list-layout.md) + - [构建食物分类Grid布局](ui-ts-building-category-grid-layout.md) + - [页面跳转与数据传递](ui-ts-page-redirection-data-transmission.md) + + - 添加闪屏动画 + - [绘制图像](ui-ts-drawing-feature) + - [添加动画效果](ui-ts-animation-feature) - 常见布局开发指导 - [弹性布局](ui-ts-layout-flex.md) - [栅格布局](ui-ts-layout-grid-container.md) - [媒体查询](ui-ts-layout-mediaquery.md) - [Web组件开发](ui-ts-components-web.md) - - 体验声明式UI - - [创建声明式UI工程](ui-ts-creating-project.md) - - [初识Component](ui-ts-components.md) - - [创建简单视图](ui-ts-creating-simple-page.md) - - 页面布局与连接 - - [构建食物数据模型](ui-ts-building-data-model.md) - - [构建食物列表List布局](ui-ts-building-category-list-layout.md) - - [构建食物分类Grid布局](ui-ts-building-category-grid-layout.md) - - [页面跳转与数据传递](ui-ts-page-redirection-data-transmission.md) - [性能提升的推荐方法](ts-performance-improvement-recommendation.md) - UI开发(兼容JS的类Web开发范式) - [概述](ui-js-overview.md) diff --git a/zh-cn/application-dev/ui/figures/animation-feature.gif b/zh-cn/application-dev/ui/figures/animation-feature.gif new file mode 100644 index 0000000000000000000000000000000000000000..f75ac924799af38dbfe43c2c2e5572fb0e3c09d0 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/animation-feature.gif differ diff --git a/zh-cn/application-dev/ui/figures/animation-feature1.gif b/zh-cn/application-dev/ui/figures/animation-feature1.gif new file mode 100644 index 0000000000000000000000000000000000000000..5dc236ba194e59fd46416ca0a738ed7139a59374 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/animation-feature1.gif differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature.png b/zh-cn/application-dev/ui/figures/drawing-feature.png new file mode 100644 index 0000000000000000000000000000000000000000..c1468667bdf74b80629c1eaa32cbef935ea5048c Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature1.png b/zh-cn/application-dev/ui/figures/drawing-feature1.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3ca4fa90e25b5906bdadcdcbf134bd00b7b34b Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature1.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature2.png b/zh-cn/application-dev/ui/figures/drawing-feature2.png new file mode 100644 index 0000000000000000000000000000000000000000..42fc7cdf9436680a30413c5bb07e02fd3230ec0c Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature2.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature3.png b/zh-cn/application-dev/ui/figures/drawing-feature3.png new file mode 100644 index 0000000000000000000000000000000000000000..c090ff948d7333f3dea17dd7ec54488638788c0c Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature3.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature4.png b/zh-cn/application-dev/ui/figures/drawing-feature4.png new file mode 100644 index 0000000000000000000000000000000000000000..bcfee4728b4d5fc9bfc57e6bb743e708ad0b2379 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature4.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature5.png b/zh-cn/application-dev/ui/figures/drawing-feature5.png new file mode 100644 index 0000000000000000000000000000000000000000..e70c63ed2f601cf4cd30f319dc6ebd74f216c909 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature5.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature6.png b/zh-cn/application-dev/ui/figures/drawing-feature6.png new file mode 100644 index 0000000000000000000000000000000000000000..772bd122cd1ecb252625f59af5ea3e5ff61689fc Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature6.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature7.png b/zh-cn/application-dev/ui/figures/drawing-feature7.png new file mode 100644 index 0000000000000000000000000000000000000000..c77366f46008f173bc117f95bed6bc7827af11cd Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature7.png differ diff --git a/zh-cn/application-dev/ui/figures/drawing-feature8.png b/zh-cn/application-dev/ui/figures/drawing-feature8.png new file mode 100644 index 0000000000000000000000000000000000000000..17cdf2ba3b6e3eeabbe93ed3115c34801634ba8e Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawing-feature8.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001169918548.gif b/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001169918548.gif new file mode 100644 index 0000000000000000000000000000000000000000..aac78d2abe528a9781e7e07ed2e46a4bf56d7891 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001169918548.gif differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001170008198.gif b/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001170008198.gif new file mode 100644 index 0000000000000000000000000000000000000000..a7d9b572abb833cc7cd52e63d25c4c261a10af65 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001170008198.gif differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001204776353.png b/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001204776353.png new file mode 100644 index 0000000000000000000000000000000000000000..25330d63162d9999d2c57d3c33bcc20731df7851 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/zh-cn_image1_0000001204776353.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168410342.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168410342.png index 5accde8bbb1146e3bade9d6cc4d35127dd58b86f..67b8d1571853fe13079a13ed32aff66bc2fc4452 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168410342.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168410342.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168728872.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168728872.png index b6045521e10cf66733222c0b85b1b65dd20cb66f..ddf24cd804055371cbd8a753089263f6bcc32b79 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168728872.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001168728872.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169678922.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169678922.png index 546fb29b1f18573353d11d5515f444bf720fcf52..9c89860f26331dc11cf8104711be1ad3be918111 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169678922.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169678922.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169759552.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169759552.png index a6d0026ba316551ff0819493b84ae8c9cb063289..f910230ebfab9c5315eb1c2bc99f0ca35b3cbe23 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169759552.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169759552.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169918548.gif b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169918548.gif index 0173419db3fd06cc5d328dd6931a2c76664f4596..b59ae3d79b2bc926634a50c1f3f6aecce247763c 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169918548.gif and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001169918548.gif differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170008198.gif b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170008198.gif index 26152ef9f22387729561bdddb9fa7d50019e08cb..c88150c77afccf736d42fe7253df27f2b1d27cd5 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170008198.gif and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170008198.gif differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170167520.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170167520.png new file mode 100644 index 0000000000000000000000000000000000000000..2441a46f00b3083dfaa8ec2dcdb1760aa7e2aeb7 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170167520.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170411978.gif b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170411978.gif index b07f4a9f456b5bccab8fe2a2dc997818efad5ddb..a7d9b572abb833cc7cd52e63d25c4c261a10af65 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170411978.gif and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001170411978.gif differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204537865.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204537865.png index f6b70b8f1ef886bf8ed6f26e148bddee0b163fd5..60160d18d66fef9a5b65a4c5675fe91873e95582 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204537865.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204537865.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204538065.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204538065.png index a84303ae2d68affe3f5702317d9f2bf951c90698..b775a2bf408dd710861afa0dfa9f756d5181e811 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204538065.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204538065.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204776353.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204776353.png index d0e9cf658b9b396873f24666945bb796384c2041..b27a7f5358c954fe7e1bd912358d29d456870c2a 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204776353.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001204776353.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001213968747.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001213968747.png index 767315d2f69278028746341e79f88ad179930338..b60416b59cb77e096d615ba1b25d2b14056abe00 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001213968747.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001213968747.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214128687.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214128687.png index 653da4be405165f59cbbe570e9d4d64747fa7495..3f2f15792563ec89015abce1fcf30248b3c0288e 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214128687.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214128687.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214210217.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214210217.png index aaa0cbf699a302616ce142f43ea3b96df99d75ac..18abb7b725fcf0172f189c0f1cf70e9c5ae31642 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214210217.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214210217.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214998349.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214998349.png index fb85274edcd9b0a66eaa9e3da3b77e543c048cc8..6a845d64a542809c05f008eef5d1e1ed9d1c22a5 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214998349.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001214998349.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215079443.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215079443.png index 49182b6d8f1c96dcbaac493179fdab3d4b9a7bd4..0a53a5742ac5fda3501a93f576b945e21bd2addf 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215079443.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215079443.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215199399.png b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215199399.png index 2923d13dcf52b856158b440de03df929abc4955b..c508bb8764c28f228e2c0a33dd6ee97e48dfe682 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215199399.png and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215199399.png differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215318403.gif b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215318403.gif index 8e9c75334361806ac822c8c0f2c377c1679c6ca3..aac78d2abe528a9781e7e07ed2e46a4bf56d7891 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215318403.gif and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001215318403.gif differ diff --git a/zh-cn/application-dev/ui/ts-custom-component-initialization.md b/zh-cn/application-dev/ui/ts-custom-component-initialization.md index 1433542af3203e3a54f6d883ef099b8f5438f52f..3858d3032581956f10904912e28012e72b1eb451 100644 --- a/zh-cn/application-dev/ui/ts-custom-component-initialization.md +++ b/zh-cn/application-dev/ui/ts-custom-component-initialization.md @@ -14,7 +14,7 @@ ``` -具体允许哪种方式取决于状态变量的装饰器: +具体允许哪种方式取决于[状态变量](../quick-start/arkts-state-mgmt-concepts.md)的装饰器: | 装饰器类型 | 本地初始化 | 通过构造函数参数初始化 | diff --git a/zh-cn/application-dev/ui/ui-ts-animation-feature.md b/zh-cn/application-dev/ui/ui-ts-animation-feature.md new file mode 100644 index 0000000000000000000000000000000000000000..16869cea7b746874566257d31d4e9448d0a3967f --- /dev/null +++ b/zh-cn/application-dev/ui/ui-ts-animation-feature.md @@ -0,0 +1,343 @@ +# 添加动画效果 + +动画主要包含了组件动画和页面间动画,并提供了[插值计算](../reference/apis/js-apis-curve.md)和[矩阵变换](../reference/apis/js-apis-matrix4.md)的动画能力接口,让开发者极大程度的自主设计动画效果。 + +在本节主要完成两个动画效果: + +1. 启动页的闪屏动画,即Logo图标的渐出和放大效果; +2. 食物列表页和食物详情页的共享元素转场动画效果。 + +## animateTo实现闪屏动画 + +组件动画包括属性动画和animateTo显式动画: + +1. 属性动画:设置组件通用属性变化的动画效果。 +2. 显式动画:设置组件从状态A到状态B的变化动画效果,包括样式、位置信息和节点的增加删除等,开发者无需关注变化过程,只需指定起点和终点的状态。animateTo还提供播放状态的回调接口,是对属性动画的增强与封装。 + +闪屏页面的动画效果是Logo图标的渐出和放大,动画结束后跳转到食物分类列表页面。接下来,我们就使用animateTo来实现启动页动画的闪屏效果。 + +1. 动画效果自动播放。闪屏动画的预期效果是,进入Logo页面后,animateTo动画效果自动开始播放,可以借助于组件显隐事件的回调接口来实现。调用Shape的onAppear方法,设置其显式动画。 + + ```ts + Shape() { + ... + } + .onAppear(() => { + animateTo() + }) + ``` + +2. 创建opacity和scale数值的成员变量,用装饰器@State修饰。表示其为有状态的数据,即改变会触发页面的刷新。 + + ```ts + @Entry + @Component + struct Logo { + @State private opacityValue: number = 0 + @State private scaleValue: number = 0 + build() { + Shape() { + ... + } + .scale({ x: this.scaleValue, y: this.scaleValue }) + .opacity(this.opacityValue) + .onAppear(() => { + animateTo() + }) + } + } + ``` + +3. 设置animateTo的动画曲线curve。Logo的加速曲线为先慢后快,使用贝塞尔曲线cubicBezier,cubicBezier(0.4, 0, 1, 1)。 + + 需要使用动画能力接口中的插值计算,首先要导入curves模块。 + + ```ts + import Curves from '@ohos.curves' + ``` + + @ohos.curves模块提供了线性Curve. Linear、阶梯step、三阶贝塞尔(cubicBezier)和弹簧(spring)插值曲线的初始化函数,可以根据入参创建一个插值曲线对象。 + + ```ts + @Entry + @Component + struct Logo { + @State private opacityValue: number = 0 + @State private scaleValue: number = 0 + private curve1 = Curves.cubicBezier(0.4, 0, 1, 1) + + build() { + Shape() { + ... + } + .scale({ x: this.scaleValue, y: this.scaleValue }) + .opacity(this.opacityValue) + .onAppear(() => { + animateTo({ + curve: this.curve1 + }) + }) + } + } + ``` + +4. 设置动画时长为1s,延时0.1s开始播放,设置显示动效event的闭包函数,即起点状态到终点状态为透明度opacityValue和大小scaleValue从0到1,实现Logo的渐出和放大效果。 + + ```ts + @Entry + @Component + struct Logo { + @State private opacityValue: number = 0 + @State private scaleValue: number = 0 + private curve1 = Curves.cubicBezier(0.4, 0, 1, 1) + + build() { + Shape() { + ... + } + .scale({ x: this.scaleValue, y: this.scaleValue }) + .opacity(this.opacityValue) + .onAppear(() => { + animateTo({ + duration: 2000, + curve: this.curve1, + delay: 100, + }, () => { + this.opacityValue = 1 + this.scaleValue = 1 + }) + }) + } + } + ``` + +5. 闪屏动画播放结束后定格1s,进入FoodCategoryList页面。设置animateTo的onFinish回调接口,调用定时器Timer的setTimeout接口延时1s后,调用router.replace,显示FoodCategoryList页面。 + + ```ts + import router from '@ohos.router' + + @Entry + @Component + struct Logo { + @State private opacityValue: number = 0 + @State private scaleValue: number = 0 + private curve1 = Curves.cubicBezier(0.4, 0, 1, 1) + + build() { + Shape() { + ... + } + .scale({ x: this.scaleValue, y: this.scaleValue }) + .opacity(this.opacityValue) + .onAppear(() => { + + animateTo({ + duration: 2000, + curve: this.curve1, + delay: 100, + onFinish: () => { + setTimeout(() => { + router.replace({ url: "pages/FoodCategoryList" }) + }, 1000); + } + }, () => { + this.opacityValue = 1 + this.scaleValue = 1 + }) + }) + } + } + ``` + + 整体代码如下。 + + ```ts + import Curves from '@ohos.curves' + import router from '@ohos.router' + + @Entry + @Component + struct Logo { + @State private opacityValue: number = 0 + @State private scaleValue: number = 0 + private curve1 = Curves.cubicBezier(0.4, 0, 1, 1) + private pathCommands1: string = 'M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z' + private pathCommands2: string = 'M270.6 128.1 h48.6 c51.6 0 98.4 21 132.3 54.6 a411 411 0 0 3 -45.6 123 c-25.2 45.6 -56.4 84 -87.6 110.4 a206.1 206.1 0 0 0 -47.7 -288 z' + + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Shape() { + Path() + .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36') + .fill(Color.White) + Path() + .commands(this.pathCommands1) + .fill('none') + .linearGradient( + { + angle: 30, + colors: [["#C4FFA0", 0], ["#ffffff", 1]] + }) + .clip(new Path().commands(this.pathCommands1)) + + Path() + .commands(this.pathCommands2) + .fill('none') + .linearGradient( + { + angle: 50, + colors: [['#8CC36A', 0.1], ["#B3EB90", 0.4], ["#ffffff", 0.7]] + }) + .clip(new Path().commands(this.pathCommands2)) + } + .height('630px') + .width('630px') + .scale({ x: this.scaleValue, y: this.scaleValue }) + .opacity(this.opacityValue) + .onAppear(() => { + animateTo({ + duration: 2000, + curve: this.curve1, + delay: 100, + onFinish: () => { + setTimeout(() => { + router.replace({ url: "pages/FoodCategoryList" }) + }, 1000); + } + }, () => { + this.opacityValue = 1 + this.scaleValue = 1 + }) + }) + + Text('Healthy Diet') + .fontSize(26) + .fontColor(Color.White) + .margin({ top: 300 }) + + Text('Healthy life comes from a balanced diet') + .fontSize(17) + .fontColor(Color.White) + .margin({ top: 4 }) + } + .width('100%') + .height('100%') + .linearGradient( + { + angle: 180, + colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]] + }) + } + } + ``` + + ![animation-feature](figures/animation-feature.gif) + +## 页面转场动画 + +食物分类列表页和食物详情页之间的共享元素转场,即点击FoodListItem/FoodGridItem后,食物缩略图会放大,随着页面跳转,到食物详情页的大图。 + +1. 设置FoodListItem和FoodGridItem的Image组件的共享元素转场方法(sharedTransition)。转场id为foodItem.id,转场动画时长为1s,延时0.1s播放,变化曲线为贝塞尔曲线Curves.cubicBezier(0.2, 0.2, 0.1, 1.0) ,需引入curves模块。 + + 共享转场时会携带当前元素的被设置的属性,所以创建Row组件,使其作为Image的父组件,设置背景颜色在Row上。 + + 在FoodListItem的Image组件上设置autoResize为false,因为image组件默认会根据最终展示的区域,去调整图源的大小,以优化图片渲染性能。在转场动画中,图片在放大的过程中会被重新加载,所以为了转场动画的流畅,autoResize设置为false。 + + ```ts + // FoodList.ets + import Curves from '@ohos.curves' + + @Component + struct FoodListItem { + private foodItem: FoodData + build() { + Navigator({ target: 'pages/FoodDetail' }) { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Row() { + Image(this.foodItem.image) + .objectFit(ImageFit.Contain) + .autoResize(false) + .height(40) + .width(40) + .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 }) + } + + .margin({ right: 16 }) + Text(this.foodItem.name) + .fontSize(14) + .flexGrow(1) + Text(this.foodItem.calories + ' kcal') + .fontSize(14) + } + .height(64) + } + .params({ foodData: this.foodItem }) + .margin({ right: 24, left:32 }) + } + } + + @Component + struct FoodGridItem { + private foodItem: FoodData + build() { + Column() { + Row() { + Image(this.foodItem.image) + .objectFit(ImageFit.Contain) + .autoResize(false) + .height(152) + .width('100%') + .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 }) + } + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Text(this.foodItem.name) + .fontSize(14) + .flexGrow(1) + .padding({ left: 8 }) + Text(this.foodItem.calories + 'kcal') + .fontSize(14) + .margin({ right: 6 }) + } + .height(32) + .width('100%') + .backgroundColor('#FFe5e5e5') + } + .height(184) + .width('100%') + .onClick(() => { + router.push({ url: 'pages/FoodDetail', params: { foodId: this.foodItem } }) + }) + } + } + + + ``` + +2. 设置FoodDetail页面的FoodImageDisplay的Image组件的共享元素转场方法(sharedTransition)。设置方法同上。 + + ```ts + import Curves from '@ohos.curves' + + @Component + struct FoodImageDisplay { + private foodItem: FoodData + build() { + Stack({ alignContent: Alignment.BottomStart }) { + Image(this.foodItem.image) + .objectFit(ImageFit.Contain) + .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 }) + Text(this.foodItem.name) + .fontSize(26) + .fontWeight(500) + .margin({ left: 26, bottom: 17.4 }) + } + .height(357) + } + } + ``` + + ![animation-feature1](figures/animation-feature1.gif) + + 通过对绘制组件和动画的学习,我们已完成了启动Logo的绘制、启动页动画和页面间的转场动画,声明式UI框架提供了丰富的动效接口,合理地应用和组合可以让应用更具有设计感。 + + diff --git a/zh-cn/application-dev/ui/ui-ts-building-category-grid-layout.md b/zh-cn/application-dev/ui/ui-ts-building-category-grid-layout.md index 1f8e88029e8a192fbed74060f3c72d1e21d2357b..8df2f7bd9c6d81a5fe9778a3cfeca9185e19f19c 100644 --- a/zh-cn/application-dev/ui/ui-ts-building-category-grid-layout.md +++ b/zh-cn/application-dev/ui/ui-ts-building-category-grid-layout.md @@ -1,16 +1,16 @@ # 构建食物分类Grid布局 - - 健康饮食应用在主页提供给用户两种食物显示方式:列表显示和网格显示。开发者将实现通过页签切换不同食物分类的网格布局。 1. 将Category枚举类型引入FoodCategoryList页面。 + ```ts import { Category, FoodData } from '../model/FoodData' ``` 2. 创建FoodCategoryList和FoodCategory组件,其中FoodCategoryList作为新的页面入口组件,在入口组件调用initializeOnStartup方法。 + ```ts @Component struct FoodList { @@ -19,7 +19,7 @@ ...... } } - + @Component struct FoodCategory { private foodItems: FoodData[] @@ -27,7 +27,7 @@ ...... } } - + @Entry @Component struct FoodCategoryList { @@ -39,13 +39,14 @@ ``` 3. 在FoodCategoryList组件内创建showList成员变量,用于控制List布局和Grid布局的渲染切换。需要用到条件渲染语句if...else...。 + ```ts @Entry @Component struct FoodCategoryList { private foodItems: FoodData[] = initializeOnStartup() private showList: boolean = false - + build() { Stack() { if (this.showList) { @@ -59,13 +60,14 @@ ``` 4. 在页面右上角创建切换List/Grid布局的图标。设置Stack对齐方式为顶部尾部对齐TopEnd,创建Image组件,设置其点击事件,即showList取反。 + ```ts @Entry @Component struct FoodCategoryList { private foodItems: FoodData[] = initializeOnStartup() private showList: boolean = false - + build() { Stack({ alignContent: Alignment.TopEnd }) { if (this.showList) { @@ -85,14 +87,15 @@ } ``` -5. 添加\@State装饰器。点击右上角的switch标签后,页面没有任何变化,这是因为showList不是有状态数据,它的改变不会触发页面的刷新。需要为其添加\@State装饰器,使其成为状态数据,它的改变会引起其所在组件的重新渲染。 +5. 添加@State装饰器。点击右上角的switch标签后,页面没有任何变化,这是因为showList不是有状态数据,它的改变不会触发页面的刷新。需要为其添加\@State装饰器,使其成为状态数据,它的改变会引起其所在组件的重新渲染。 + ```ts @Entry @Component struct FoodCategoryList { private foodItems: FoodData[] = initializeOnStartup() @State private showList: boolean = false - + build() { Stack({ alignContent: Alignment.TopEnd }) { if (this.showList) { @@ -110,7 +113,7 @@ }.height('100%') } } - + ``` 点击切换图标,FoodList组件出现,再次点击,FoodList组件消失。 @@ -118,6 +121,7 @@ ![zh-cn_image_0000001170411978](figures/zh-cn_image_0000001170411978.gif) 6. 创建显示所有食物的页签(All)。在FoodCategory组件内创建Tabs组件和其子组件TabContent,设置tabBar为All。设置TabBars的宽度为280,布局模式为Scrollable,即超过总长度后可以滑动。Tabs是一种可以通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图TabContent。 + ```ts @Component struct FoodCategory { @@ -137,13 +141,14 @@ ![zh-cn_image_0000001204538065](figures/zh-cn_image_0000001204538065.png) 7. 创建FoodGrid组件,作为TabContent的子组件。 + ```ts @Component struct FoodGrid { private foodItems: FoodData[] build() {} } - + @Component struct FoodCategory { private foodItems: FoodData[] @@ -162,6 +167,7 @@ ``` 8. 实现2 \* 6的网格布局(一共12个食物数据资源)。创建Grid组件,设置列数columnsTemplate('1fr 1fr'),行数rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr'),行间距和列间距rowsGap和columnsGap为8。创建Scroll组件,使其可以滑动。 + ```ts @Component struct FoodGrid { @@ -185,6 +191,7 @@ ``` 9. 创建FoodGridItem组件,展示食物图片、名称和卡路里,实现其UI布局,为GridItem的子组件。每个FoodGridItem高度为184,行间距为8,设置Grid总高度为(184 + 8) \* 6 - 8 = 1144。 + ```ts @Component struct FoodGridItem { @@ -196,7 +203,7 @@ .objectFit(ImageFit.Contain) .height(152) .width('100%') - }.backgroundColor('#FFf1f3f5') + } Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text(this.foodItem.name) .fontSize(14) @@ -214,7 +221,7 @@ .width('100%') } } - + @Component struct FoodGrid { private foodItems: FoodData[] @@ -239,123 +246,126 @@ } ``` - ![zh-cn_image_0000001170167520](figures/zh-cn_image_0000001170167520.gif) + ![zh-cn_image_0000001170167520](figures/zh-cn_image_0000001170167520.png) 10. 创建展示蔬菜(Category.Vegetable)、水果(Category.Fruit)、坚果(Category.Nut)、海鲜(Category.SeaFood)和甜品(Category.Dessert)分类的页签。 - ```ts - @Component - struct FoodCategory { - private foodItems: FoodData[] - build() { - Stack() { - Tabs() { - TabContent() { - FoodGrid({ foodItems: this.foodItems }) - }.tabBar('All') - - TabContent() { - FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Vegetable)) }) - }.tabBar('Vegetable') - - TabContent() { - FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Fruit)) }) - }.tabBar('Fruit') - - TabContent() { - FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Nut)) }) - }.tabBar('Nut') - - TabContent() { - FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Seafood)) }) - }.tabBar('Seafood') - - TabContent() { - FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Dessert)) }) - }.tabBar('Dessert') + + ```ts + @Component + struct FoodCategory { + private foodItems: FoodData[] + + build() { + Stack() { + Tabs() { + TabContent() { + FoodGrid({ foodItems: this.foodItems }) + }.tabBar('All') + + TabContent() { + FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Vegetable)) }) + }.tabBar('Vegetable') + + TabContent() { + FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Fruit)) }) + }.tabBar('Fruit') + + TabContent() { + FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Nut)) }) + }.tabBar('Nut') + + TabContent() { + FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Seafood)) }) + }.tabBar('Seafood') + + TabContent() { + FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Dessert)) }) + }.tabBar('Dessert') + } + .barWidth(280) + .barMode(BarMode.Scrollable) } - .barWidth(280) - .barMode(BarMode.Scrollable) } } - } - ``` + ``` 11. 设置不同食物分类的Grid的行数和高度。因为不同分类的食物数量不同,所以不能用'1fr 1fr 1fr 1fr 1fr 1fr '常量来统一设置成6行。 创建gridRowTemplate和HeightValue成员变量,通过成员变量设置Grid行数和高度。 - ```ts - @Component - struct FoodGrid { - private foodItems: FoodData[] - private gridRowTemplate : string = '' - private heightValue: number - build() { - Scroll() { - Grid() { - ForEach(this.foodItems, (item: FoodData) => { - GridItem() { - FoodGridItem({foodItem: item}) - } - }, (item: FoodData) => item.id.toString()) - } - .rowsTemplate(this.gridRowTemplate) - .columnsTemplate('1fr 1fr') - .columnsGap(8) - .rowsGap(8) - .height(this.heightValue) - } - .scrollBar(BarState.Off) - .padding({left: 16, right: 16}) - } - } - ``` - - 调用aboutToAppear接口计算行数(gridRowTemplate )和高度(heightValue)。 - - ```ts - aboutToAppear() { - var rows = Math.round(this.foodItems.length / 2); - this.gridRowTemplate = '1fr '.repeat(rows); - this.heightValue = rows * 192 - 8; - } - ``` - - 自定义组件提供了两个生命周期的回调接口aboutToAppear和aboutToDisappear。aboutToAppear的执行时机在创建自定义组件后,执行自定义组件build方法之前。aboutToDisappear在自定义组件销毁之前的时机执行。 + ```ts + @Component + struct FoodGrid { + private foodItems: FoodData[] + private gridRowTemplate: string = '' + private heightValue: number + + build() { + Scroll() { + Grid() { + ForEach(this.foodItems, (item: FoodData) => { + GridItem() { + FoodGridItem({ foodItem: item }) + } + }, (item: FoodData) => item.id.toString()) + } + .rowsTemplate(this.gridRowTemplate) + .columnsTemplate('1fr 1fr') + .columnsGap(8) + .rowsGap(8) + .height(this.heightValue) + } + .scrollBar(BarState.Off) + .padding({ left: 16, right: 16 }) + } + } + ``` - ![zh-cn_image_0000001215113569](figures/zh-cn_image_0000001215113569.png) + 调用aboutToAppear接口计算行数(gridRowTemplate )和高度(heightValue)。 - ```ts - @Component - struct FoodGrid { - private foodItems: FoodData[] - private gridRowTemplate : string = '' - private heightValue: number - - aboutToAppear() { - var rows = Math.round(this.foodItems.length / 2); - this.gridRowTemplate = '1fr '.repeat(rows); - this.heightValue = rows * 192 - 8; - } - - build() { - Scroll() { - Grid() { - ForEach(this.foodItems, (item: FoodData) => { - GridItem() { - FoodGridItem({foodItem: item}) - } - }, (item: FoodData) => item.id.toString()) - } - .rowsTemplate(this.gridRowTemplate) - .columnsTemplate('1fr 1fr') - .columnsGap(8) - .rowsGap(8) - .height(this.heightValue) - } - .scrollBar(BarState.Off) - .padding({left: 16, right: 16}) - } - } - ``` + ```ts + aboutToAppear() { + var rows = Math.round(this.foodItems.length / 2); + this.gridRowTemplate = '1fr '.repeat(rows); + this.heightValue = rows * 192 - 8; + } + ``` + + 自定义组件提供了两个生命周期的回调接口aboutToAppear和aboutToDisappear。aboutToAppear的执行时机在创建自定义组件后,执行自定义组件build方法之前。aboutToDisappear在自定义组件销毁之前的时机执行。 + + ![zh-cn_image_0000001215113569](figures/zh-cn_image_0000001215113569.png) + + ```ts + @Component + struct FoodGrid { + private foodItems: FoodData[] + private gridRowTemplate: string = '' + private heightValue: number + + aboutToAppear() { + var rows = Math.round(this.foodItems.length / 2); + this.gridRowTemplate = '1fr '.repeat(rows); + this.heightValue = rows * 192 - 8; + } + + build() { + Scroll() { + Grid() { + ForEach(this.foodItems, (item: FoodData) => { + GridItem() { + FoodGridItem({ foodItem: item }) + } + }, (item: FoodData) => item.id.toString()) + } + .rowsTemplate(this.gridRowTemplate) + .columnsTemplate('1fr 1fr') + .columnsGap(8) + .rowsGap(8) + .height(this.heightValue) + } + .scrollBar(BarState.Off) + .padding({ left: 16, right: 16 }) + } + } + ``` - ![zh-cn_image_0000001170008198](figures/zh-cn_image_0000001170008198.gif) + ![zh-cn_image_0000001170008198](figures/zh-cn_image_0000001170008198.gif) \ No newline at end of file diff --git a/zh-cn/application-dev/ui/ui-ts-building-category-list-layout.md b/zh-cn/application-dev/ui/ui-ts-building-category-list-layout.md index bb5ab8da2f5078e85109101f3f0a07257e609f40..5201e0e720b83083b0ceaeaac73ca95d15ed9290 100644 --- a/zh-cn/application-dev/ui/ui-ts-building-category-list-layout.md +++ b/zh-cn/application-dev/ui/ui-ts-building-category-list-layout.md @@ -1,23 +1,12 @@ # 构建食物列表List布局 - - 使用List组件和ForEach循环渲染,构建食物列表布局。 -1. 在pages目录新建页面FoodCategoryList.ets,将index.ets改名为FoodDetail.ets,并将其添加到config.json文件下的pages标签,位于第一序位的页面为首页。 - ```json - "js": [ - { - "pages": [ - "pages/FoodCategoryList", - "pages/FoodDetail" - ], - ] - ``` - +1. 在pages目录新建页面FoodCategoryList.ets,将index.ets改名为FoodDetail.ets。 + 2. 新建FoodList组件作为页面入口组件,FoodListItem为其子组件。List组件是列表组件,适用于重复同类数据的展示,其子组件为ListItem,适用于展示列表中的单元。 - ``` + ```ts @Component struct FoodListItem { build() {} @@ -43,7 +32,7 @@ ``` 4. FoodList和FoodListItem组件数值传递。在FoodList组件内创建类型为FoodData[]成员变量foodItems,调用initializeOnStartup方法为其赋值。在FoodListItem组件内创建类型为FoodData的成员变量foodItem。将父组件foodItems数组的第一个元素的foodItems[0]作为参数传递给FoodListItem。 - ``` + ```ts import { FoodData } from '../model/FoodData' import { initializeOnStartup } from '../model/FoodDataModels' @@ -68,20 +57,64 @@ ``` 5. 声明子组件FoodListItem 的UI布局。创建Flex组件,包含食物图片缩略图,食物名称,和食物对应的卡路里。 - ``` + ```ts import { FoodData } from '../model/FoodData' import { initializeOnStartup } from '../model/FoodDataModels' + @Component + struct FoodListItem { + private foodItem: FoodData + build() { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Image(this.foodItem.image) + .objectFit(ImageFit.Contain) + .height(40) + .width(40) + .margin({ right: 16 }) + Text(this.foodItem.name) + .fontSize(14) + .flexGrow(1) + Text(this.foodItem.calories + ' kcal') + .fontSize(14) + } + .height(64) + .margin({ right: 24, left:32 }) + } + } + + @Entry + @Component + struct FoodList { + private foodItems: FoodData[] = initializeOnStartup() + build() { + List() { + ListItem() { + FoodListItem({ foodItem: this.foodItems[0] }) + } + } + } + } + ``` + + + ![zh-cn_image_0000001204776353](figures/zh-cn_image_0000001204776353.png) + +6. 创建两个FoodListItem。在List组件创建两个FoodListItem,分别给FoodListItem传递foodItems数组的第一个元素this.foodItems[0]和第二个元素foodItem: this.foodItems[1]。 + + ```ts + import { FoodData } from '../model/FoodData' + import { initializeOnStartup } from '../model/FoodDataModels' + @Component struct FoodListItem { private foodItem: FoodData + build() { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Image(this.foodItem.image) .objectFit(ImageFit.Contain) .height(40) .width(40) - .backgroundColor('#FFf1f3f5') .margin({ right: 16 }) Text(this.foodItem.name) .fontSize(14) @@ -90,62 +123,21 @@ .fontSize(14) } .height(64) - .margin({ right: 24, left:32 }) + .margin({ right: 24, left: 32 }) } } - - @Entry - @Component - struct FoodList { - private foodItems: FoodData[] = initializeOnStartup() - build() { - List() { - ListItem() { - FoodListItem({ foodItem: this.foodItems[0] }) - } - } - } - } - ``` - - ![zh-cn_image_0000001204776353](figures/zh-cn_image_0000001204776353.png) - -6. 创建两个FoodListItem。在List组件创建两个FoodListItem,分别给FoodListItem传递foodItems数组的第一个元素this.foodItems[0]和第二个元素foodItem: this.foodItems[1]。 - ``` - import { FoodData } from '../model/FoodData' - import { initializeOnStartup } from '../model/FoodDataModels' - - @Component - struct FoodListItem { - private foodItem: FoodData - build() { - Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { - Image(this.foodItem.image) - .objectFit(ImageFit.Contain) - .height(40) - .width(40) - .backgroundColor('#FFf1f3f5') - .margin({ right: 16 }) - Text(this.foodItem.name) - .fontSize(14) - .flexGrow(1) - Text(this.foodItem.calories + ' kcal') - .fontSize(14) - } - .height(64) - .margin({ right: 24, left:32 }) - } - } - + @Entry @Component struct FoodList { private foodItems: FoodData[] = initializeOnStartup() + build() { List() { ListItem() { FoodListItem({ foodItem: this.foodItems[0] }) } + ListItem() { FoodListItem({ foodItem: this.foodItems[1] }) } @@ -153,36 +145,16 @@ } } ``` + + + ![zh-cn_image1_0000001204776353](figures/zh-cn_image1_0000001204776353.png) + +7. 单独创建每一个FoodListItem肯定是不合理的,这就需要引入[ForEach循环渲染](../quick-start/arkts-rendering-control.md#循环渲染)。 - ![zh-cn_image_0000001204537865](figures/zh-cn_image_0000001204537865.png) - -7. 单独创建每一个FoodListItem肯定是不合理的。这就需要引入ForEach循环渲染,ForEach语法如下。 - ``` - ForEach( - arr: any[], // Array to be iterated - itemGenerator: (item: any) => void, // child component generator - keyGenerator?: (item: any) => string // (optional) Unique key generator, which is recommended. - ) - ``` - - ForEach组有三个参数,第一个参数是需要被遍历的数组,第二个参数为生成子组件的lambda函数,第三个参数是键值生成器。出于性能原因,即使第三个参数是可选的,强烈建议开发者提供。keyGenerator使开发框架能够更好地识别数组更改,而不必因为item的更改重建全部节点。 - - 遍历foodItems数组循环创建ListItem组件,foodItems中每一个item都作为参数传递给FoodListItem组件。 - - ``` - ForEach(this.foodItems, item => { - ListItem() { - FoodListItem({ foodItem: item }) - } - }, item => item.id.toString()) - ``` - - 整体的代码如下。 - - ``` + ```ts import { FoodData } from '../model/FoodData' import { initializeOnStartup } from '../model/FoodDataModels' - + @Component struct FoodListItem { private foodItem: FoodData @@ -191,8 +163,7 @@ Image(this.foodItem.image) .objectFit(ImageFit.Contain) .height(40) - .width(40) - .backgroundColor('#FFf1f3f5') + .width(40) .margin({ right: 16 }) Text(this.foodItem.name) .fontSize(14) @@ -204,7 +175,7 @@ .margin({ right: 24, left:32 }) } } - + @Entry @Component struct FoodList { @@ -220,22 +191,25 @@ } } ``` - + 8. 添加FoodList标题。 + ``` @Entry @Component struct FoodList { private foodItems: FoodData[] = initializeOnStartup() + build() { Column() { - Flex({justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center}) { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text('Food List') .fontSize(20) - .margin({ left:20 }) + .margin({ left: 20 }) } .height('7%') .backgroundColor('#FFf1f3f5') + List() { ForEach(this.foodItems, item => { ListItem() { @@ -249,4 +223,4 @@ } ``` - ![zh-cn_image_0000001169678922](figures/zh-cn_image_0000001169678922.png) + ![zh-cn_image_0000001169678922](figures/zh-cn_image_0000001169678922.png) \ No newline at end of file diff --git a/zh-cn/application-dev/ui/ui-ts-creating-simple-page.md b/zh-cn/application-dev/ui/ui-ts-creating-simple-page.md index 18152fe84e1c3ff337bd9db0b41ce9c3359adcfa..7f23d6049a369519897eb92e35702a403d648065 100644 --- a/zh-cn/application-dev/ui/ui-ts-creating-simple-page.md +++ b/zh-cn/application-dev/ui/ui-ts-creating-simple-page.md @@ -2,12 +2,16 @@ 在这一小节中,我们将开始食物详情页的开发,学习如何通过容器组件Stack、Flex和基础组件Image、Text,构建用户自定义组件,完成图文并茂的食物介绍。 +在创建页面前,请先创建eTS工程,FA模型请参考[创建FA模型的eTS工程](../quick-start/start-with-ets-stage.md#创建ets工程),Stage模型请参考[创建Stage模型的eTS工程](..//quick-start/start-with-ets-fa.md#创建ets工程)。 + ## 构建Stack布局 1. 创建食物名称。 - 删掉工程模板的build方法的代码,创建Stack组件,将Text组件放进Stack组件的花括号中,使其成为Stack组件的子组件。Stack组件为堆叠组件,可以包含一个或多个子组件,其特点是后一个子组件覆盖前一个子组件。 - ``` + + 在index.ets文件中,创建Stack组件,将Text组件放进Stack组件的花括号中,使其成为Stack组件的子组件。Stack组件为堆叠组件,可以包含一个或多个子组件,其特点是后一个子组件覆盖前一个子组件。 + + ```ts @Entry @Component struct MyComponent { @@ -18,15 +22,15 @@ .fontWeight(500) } } - } +} ``` ![zh-cn_image_0000001214128687](figures/zh-cn_image_0000001214128687.png) - + 2. 食物图片展示。 创建Image组件,指定Image组件的url,Image组件是必选构造参数组件。为了让Text组件在Image组件上方显示,所以要先声明Image组件。图片资源放在resources下的rawfile文件夹内,引用rawfile下资源时使用`$rawfile('filename')`的形式,filename为rawfile目录下的文件相对路径。当前`$rawfile`仅支持Image控件引用图片资源。 - - ``` + + ```ts @Entry @Component struct MyComponent { @@ -42,30 +46,29 @@ ``` -![zh-cn_image_0000001168410342](figures/zh-cn_image_0000001168410342.png) + ![zh-cn_image_0000001168410342](figures/zh-cn_image_0000001168410342.png) 3. 通过资源访问图片。 除指定图片路径外,也可以使用引用媒体资源符$r引用资源,需要遵循resources文件夹的资源限定词的规则。右键resources文件夹,点击New>Resource Directory,选择Resource Type为Media(图片资源)。 将Tomato.png放入media文件夹内。就可以通过`$r('app.type.name')`的形式引用应用资源,即`$r('app.media.Tomato')`。 -``` -@Entry + ```ts + @Entry @Component struct MyComponent { build() { Stack() { - Image($r('app.media.Tomato')) - .objectFit(ImageFit.Contain) - .height(357) - Text('Tomato') - .fontSize(26) - .fontWeight(500) + Image($r('app.media.Tomato')) + .objectFit(ImageFit.Contain) + .height(357) + Text('Tomato') + .fontSize(26) + .fontWeight(500) } } } -``` - + ``` 4. 设置Image宽高,并且将image的objectFit属性设置为ImageFit.Contain,即保持图片长宽比的情况下,使得图片完整地显示在边界内。 如果Image填满了整个屏幕,原因如下: @@ -73,83 +76,53 @@ 2. Image的objectFit默认属性是ImageFit.Cover,即在保持长宽比的情况下放大或缩小,使其填满整个显示边界。 -``` -@Entry + ```ts + @Entry @Component struct MyComponent { build() { Stack() { - Image($r('app.media.Tomato')) - .objectFit(ImageFit.Contain) - .height(357) - Text('Tomato') - .fontSize(26) - .fontWeight(500) + Image($r('app.media.Tomato')) + .objectFit(ImageFit.Contain) + .height(357) + Text('Tomato') + .fontSize(26) + .fontWeight(500) } } } -``` - + ``` - ![zh-cn_image_0000001214210217](figures/zh-cn_image_0000001214210217.png) + ![zh-cn_image_0000001214210217](figures/zh-cn_image_0000001214210217.png) 5. 设置食物图片和名称布局。设置Stack的对齐方式为底部起始端对齐,Stack默认为居中对齐。设置Stack构造参数alignContent为Alignment.BottomStart。其中Alignment和FontWeight一样,都是框架提供的内置枚举类型。 -``` -@Entry + ```ts + @Entry @Component struct MyComponent { build() { Stack({ alignContent: Alignment.BottomStart }) { - Image($r('app.media.Tomato')) - .objectFit(ImageFit.Contain) - .height(357) - Text('Tomato') - .fontSize(26) - .fontWeight(500) + Image($r('app.media.Tomato')) + .objectFit(ImageFit.Contain) + .height(357) + Text('Tomato') + .fontSize(26) + .fontWeight(500) } } } -``` - - - ![zh-cn_image_0000001168728872](figures/zh-cn_image_0000001168728872.png) - -6. 通过设置Stack的背景颜色来改变食物图片的背景颜色,设置颜色有四种方式: - 1. 通过框架提供的Color内置枚举值来设置,比如backgroundColor(Color.Red),即设置背景颜色为红色。 - 2. string类型参数,支持的颜色格式有:rgb、rgba和HEX颜色码。比如backgroundColor('\#0000FF'),即设置背景颜色为蓝色,backgroundColor('rgb(255, 255, 255)'),即设置背景颜色为白色。 - 3. number类型参数,支持十六进制颜色值。比如backgroundColor(0xFF0000),即设置背景颜色为红色。 - - 4. Resource类型参数请参考[资源访问](ts-resource-access.md) 。 - - -``` -@Entry - @Component - struct MyComponent { - build() { - Stack({ alignContent: Alignment.BottomStart }) { - Image($r('app.media.Tomato')) - .objectFit(ImageFit.Contain) - .height(357) - Text('Tomato') - .fontSize(26) - .fontWeight(500) - } - .backgroundColor('#FFedf2f5') - } -} -``` + ``` + ![zh-cn_image_0000001168728872](figures/zh-cn_image_0000001168728872.png) - ![zh-cn_image_0000001168888822](figures/zh-cn_image_0000001168888822.png) +6. 调整Text组件的外边距margin,使其距离左侧和底部有一定的距离。margin是简写属性,可以统一指定四个边的外边距,也可以分别指定。具体设置方式如下: -7. 调整Text组件的外边距margin,使其距离左侧和底部有一定的距离。margin是简写属性,可以统一指定四个边的外边距,也可以分别指定。具体设置方式如下: 1. 参数为Length时,即统一指定四个边的外边距,比如margin(20),即上、右、下、左四个边的外边距都是20。 2. 参数为{top?: Length, right?: Length, bottom?: Length, left?:Length},即分别指定四个边的边距,比如margin({ left: 26, bottom: 17.4 }),即左边距为26,下边距为17.4。 -``` -@Entry + ```ts + @Entry @Component struct MyComponent { build() { @@ -161,20 +134,19 @@ .fontSize(26) .fontWeight(500) .margin({left: 26, bottom: 17.4}) - } - .backgroundColor('#FFedf2f5') + } } } -``` + ``` + ![zh-cn_image_0000001213968747](figures/zh-cn_image_0000001213968747.png) - ![zh-cn_image_0000001213968747](figures/zh-cn_image_0000001213968747.png) +7. 调整组件间的结构,语义化组件名称。创建页面入口组件为FoodDetail,在FoodDetail中创建Column,设置水平方向上居中对齐 alignItems(HorizontalAlign.Center)。MyComponent组件名改为FoodImageDisplay,为FoodDetail的子组件。 -8. 调整组件间的结构,语义化组件名称。创建页面入口组件为FoodDetail,在FoodDetail中创建Column,设置水平方向上居中对齐 alignItems(HorizontalAlign.Center)。MyComponent组件名改为FoodImageDisplay,为FoodDetail的子组件。 Column是子组件竖直排列的容器组件,本质为线性布局,所以只能设置交叉轴方向的对齐。 -``` -@Component + ```ts + @Component struct FoodImageDisplay { build() { Stack({ alignContent: Alignment.BottomStart }) { @@ -185,11 +157,10 @@ .fontWeight(500) .margin({ left: 26, bottom: 17.4 }) } - .height(357) - .backgroundColor('#FFedf2f5') + .height(357) } } - + @Entry @Component struct FoodDetail { @@ -200,9 +171,7 @@ .alignItems(HorizontalAlign.Center) } } -``` - - + ``` ## 构建Flex布局 @@ -210,8 +179,8 @@ 1. 创建ContentTable组件,使其成为页面入口组件FoodDetail的子组件。 -``` -@Component + ```ts + @Component struct FoodImageDisplay { build() { Stack({ alignContent: Alignment.BottomStart }) { @@ -223,15 +192,15 @@ .fontWeight(500) .margin({ left: 26, bottom: 17.4 }) } - .backgroundColor('#FFedf2f5') } } - + @Component struct ContentTable { - build() {} + build() { + } } - + @Entry @Component struct FoodDetail { @@ -243,8 +212,7 @@ .alignItems(HorizontalAlign.Center) } } -``` - + ``` 2. 创建Flex组件展示Tomato两类成分。 一类是热量Calories,包含卡路里(Calories);一类是营养成分Nutrition,包含蛋白质(Protein)、脂肪(Fat)、碳水化合物(Carbohydrates)和维生素C(VitaminC)。 @@ -253,8 +221,8 @@ 已省略FoodImageDisplay代码,只针对ContentTable进行扩展。 -``` -@Component + ```ts + @Component struct ContentTable { build() { Flex() { @@ -270,7 +238,7 @@ .padding({ top: 30, right: 30, left: 30 }) } } - + @Entry @Component struct FoodDetail { @@ -282,15 +250,15 @@ .alignItems(HorizontalAlign.Center) } } -``` - - - ![zh-cn_image_0000001169759552](figures/zh-cn_image_0000001169759552.png) - + ``` + + + ![zh-cn_image_0000001169759552](figures/zh-cn_image_0000001169759552.png) + 3. 调整布局,设置各部分占比。分类名占比(layoutWeight)为1,成分名和成分含量一共占比(layoutWeight)2。成分名和成分含量位于同一个Flex中,成分名占据所有剩余空间flexGrow(1)。 -``` -@Component + ```ts + @Component struct FoodImageDisplay { build() { Stack({ alignContent: Alignment.BottomStart }) { @@ -301,11 +269,10 @@ .fontSize(26) .fontWeight(500) .margin({ left: 26, bottom: 17.4 }) - } - .backgroundColor('#FFedf2f5') + } } } - + @Component struct ContentTable { build() { @@ -327,7 +294,7 @@ .padding({ top: 30, right: 30, left: 30 }) } } - + @Entry @Component struct FoodDetail { @@ -339,16 +306,15 @@ .alignItems(HorizontalAlign.Center) } } -``` - - - ![zh-cn_image_0000001215079443](figures/zh-cn_image_0000001215079443.png) - + ``` + + ![zh-cn_image_0000001215079443](figures/zh-cn_image_0000001215079443.png) + 4. 仿照热量分类创建营养成分分类。营养成分部分(Nutrition)包含:蛋白质(Protein)、脂肪(Fat)、碳水化合物(Carbohydrates)和维生素C(VitaminC)四个成分,后三个成分在表格中省略分类名,用空格代替。 设置外层Flex为竖直排列FlexDirection.Column, 在主轴方向(竖直方向)上等距排列FlexAlign.SpaceBetween,在交叉轴方向(水平轴方向)上首部对齐排列ItemAlign.Start。 -``` -@Component + ```ts + @Component struct ContentTable { build() { Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) { @@ -427,7 +393,7 @@ .padding({ top: 30, right: 30, left: 30 }) } } - + @Entry @Component struct FoodDetail { @@ -439,8 +405,7 @@ .alignItems(HorizontalAlign.Center) } } -``` - + ``` 5. 使用自定义构造函数\@Builder简化代码。可以发现,每个成分表中的成分单元其实都是一样的UI结构。 ![zh-cn_image_0000001169599582](figures/zh-cn_image_0000001169599582.png) @@ -449,8 +414,8 @@ 在ContentTable内声明\@Builder修饰的IngredientItem方法,用于声明分类名、成分名称和成分含量UI描述。 -``` - @Component + ```ts + @Component struct ContentTable { @Builder IngredientItem(title:string, name: string, value: string) { Flex() { @@ -469,13 +434,12 @@ } } } -``` - + ``` - 在ContentTable的build方法内调用IngredientItem接口,需要用this去调用该Component作用域内的方法,以此来区分全局的方法调用。 + 在ContentTable的build方法内调用IngredientItem接口,需要用this去调用该Component作用域内的方法,以此来区分全局的方法调用。 -``` -@Component + ```ts + @Component struct ContentTable { ...... build() { @@ -490,13 +454,12 @@ .padding({ top: 30, right: 30, left: 30 }) } } -``` - + ``` - ContentTable组件整体代码如下。 + ContentTable组件整体代码如下。 -``` -@Component + ```ts + @Component struct ContentTable { @Builder IngredientItem(title:string, name: string, value: string) { Flex() { @@ -514,21 +477,20 @@ .layoutWeight(2) } } - - build() { - Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) { - this.IngredientItem('Calories', 'Calories', '17kcal') - this.IngredientItem('Nutrition', 'Protein', '0.9g') - this.IngredientItem('', 'Fat', '0.2g') - this.IngredientItem('', 'Carbohydrates', '3.9g') - this.IngredientItem('', 'VitaminC', '17.8mg') - } - .height(280) - .padding({ top: 30, right: 30, left: 30 }) - } - + + build() { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) { + this.IngredientItem('Calories', 'Calories', '17kcal') + this.IngredientItem('Nutrition', 'Protein', '0.9g') + this.IngredientItem('', 'Fat', '0.2g') + this.IngredientItem('', 'Carbohydrates', '3.9g') + this.IngredientItem('', 'VitaminC', '17.8mg') + } + .height(280) + .padding({ top: 30, right: 30, left: 30 }) + } } - + @Entry @Component struct FoodDetail { @@ -540,10 +502,9 @@ .alignItems(HorizontalAlign.Center) } } -``` - + ``` - ![zh-cn_image_0000001215199399](figures/zh-cn_image_0000001215199399.png) + ![zh-cn_image_0000001215199399](figures/zh-cn_image_0000001215199399.png) 通过学习Stack布局和Flex布局已完成食物的图文展示和营养成分表,构建出第一个普通视图的食物详情页。在下一个章节中,将开发食物分类列表页,并完成食物分类列表页面和食物详情页面的跳转和数据传递,现在我们就进入下一个章节的学习吧。 diff --git a/zh-cn/application-dev/ui/ui-ts-drawing-feature.md b/zh-cn/application-dev/ui/ui-ts-drawing-feature.md new file mode 100644 index 0000000000000000000000000000000000000000..915748a8acec87ff5a2ef16820467d3999e4d432 --- /dev/null +++ b/zh-cn/application-dev/ui/ui-ts-drawing-feature.md @@ -0,0 +1,404 @@ +# 绘制图形 + +绘制能力主要是通过框架提供的绘制组件来支撑,支持svg标准绘制命令。 + +本节主要学习如何使用绘制组件,绘制详情页食物成分标签(基本几何图形)和应用Logo(自定义图形)。 + +## 绘制基本几何图形 + +绘制组件封装了一些常见的基本几何图形,比如矩形Rect、圆形Circle、椭圆形Ellipse等,为开发者省去了路线计算的过程。 + +FoodDetail页面的食物成分表里,给每一项成分名称前都加上一个圆形的图标作为成分标签。 + +1. 创建Circle组件,在每一项含量成分前增加一个圆形图标作为标签。设置Circle的直径为 6vp。修改FoodDetail页面的ContentTable组件里的IngredientItem方法,在成分名称前添加Circle。 + + ```ts + // FoodDetail.ets + @Component + struct ContentTable { + private foodItem: FoodData + + @Builder IngredientItem(title:string, colorValue: string, name: string, value: string) { + Flex() { + Text(title) + .fontSize(17.4) + .fontWeight(FontWeight.Bold) + .layoutWeight(1) + Flex({ alignItems: ItemAlign.Center }) { + Circle({width: 6, height: 6}) + .margin({right: 12}) + .fill(colorValue) + Text(name) + .fontSize(17.4) + .flexGrow(1) + Text(value) + .fontSize(17.4) + } + .layoutWeight(2) + } + } + + build() { + ...... + } + } + ``` + +2. 每个成分的标签颜色不一样,所以我们在build方法中,调用IngredientItem,给每个Circle填充不一样的颜色。 + + ```ts + // FoodDetail.ets + @Component + struct ContentTable { + private foodItem: FoodData + + @Builder IngredientItem(title:string, colorValue: string, name: string, value: string) { + Flex() { + Text(title) + .fontSize(17.4) + .fontWeight(FontWeight.Bold) + .layoutWeight(1) + Flex({ alignItems: ItemAlign.Center }) { + Circle({width: 6, height: 6}) + .margin({right: 12}) + .fill(colorValue) + Text(name) + .fontSize(17.4) + .flexGrow(1) + Text(value) + .fontSize(17.4) + } + .layoutWeight(2) + } + } + + build() { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) { + this.IngredientItem('Calories', '#FFf54040', 'Calories', this.foodItem.calories + 'kcal') + this.IngredientItem('Nutrition', '#FFcccccc', 'Protein', this.foodItem.protein + 'g') + this.IngredientItem(' ', '#FFf5d640', 'Fat', this.foodItem.fat + 'g') + this.IngredientItem(' ', '#FF9e9eff', 'Carbohydrates', this.foodItem.carbohydrates + 'g') + this.IngredientItem(' ', '#FF53f540', 'VitaminC', this.foodItem.vitaminC + 'mg') + } + .height(280) + .padding({ top: 30, right: 30, left: 30 }) + } + } + ``` + + ![drawing-feature](figures/drawing-feature.png) + +## 绘制自定义几何图形 + +除绘制基础几何图形,开发者还可以使用Path组件来绘制自定义的路线,下面进行绘制应用的Logo图案。 + +1. 在pages文件夹下创建新的eTS页面Logo.ets。 + + ![drawing-feature1](figures/drawing-feature1.png) + +2. Logo.ets中删掉模板代码,创建Logo Component。 + + ```ts + @Entry + @Component + struct Logo { + build() { + } + } + ``` + +3. 创建Flex组件为根节点,宽高设置为100%,设置其在主轴方向和交叉轴方向的对齐方式都为Center,创建Shape组件为Flex子组件。 + + Shape组件是所有绘制组件的父组件。如果需要组合多个绘制组件成为一个整体,需要创建Shape作为其父组件。 + + 我们要绘制的Logo的大小630px * 630px。声明式UI范式支持多种长度单位的设置,在前面的章节中,我们直接使用number作为参数,即采用了默认长度单位vp,虚拟像素单位。vp和设备分辨率以及屏幕密度有关。比如设备分辨率为1176 * 2400,屏幕基准密度(resolution)为3,vp = px / resolution,则该设备屏幕宽度是392vp。 + + 但是绘制组件采用svg标准,默认采取px为单位的,为方便统一,在这绘制Logo这一部分,统一采取px为单位。声明式UI框架同样也支持px单位,入参类型为string,设置宽度为630px,即210vp,设置方式为width('630px')或者width(210)。 + + ```ts + @Entry + @Component + struct Logo { + build() { + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Shape() { + + } + .height('630px') + .width('630px') + } + .width('100%') + .height('100%') + } + } + ``` + +4. 给页面填充渐变色。设置为线性渐变,偏移角度为180deg,三段渐变 #BDE895 -->95DE7F --> #7AB967,其区间分别为[0, 0.1], (0.1, 0.6], (0.6, 1]。 + + ```ts + .linearGradient( + { + angle: 180, + colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]] + }) + ``` + + ![drawing-feature2](figures/drawing-feature2.png) + + ```ts + @Entry + @Component + struct Logo { + build() { + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Shape() { + + } + .height('630px') + .width('630px') + } + .width('100%') + .height('100%') + .linearGradient( + { + angle: 180, + colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]] + }) + } + } + ``` + + ![drawing-feature3](figures/drawing-feature3.png) + +5. 绘制第一条路线Path,设置其绘制命令。 + + ```ts + Path() + .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36') + ``` + + Path的绘制命令采用svg标准,上述命令可分解为: + + ```ts + M162 128.7 + ``` + + 将笔触移动到(Moveto)坐标点(162, 128.7)。 + + ```ts + a222 222 0 0 1 100.8 374.4 + ``` + + 画圆弧线(elliptical arc)半径rx,ry为222,x轴旋转角度x-axis-rotation为0,角度大小large-arc-flag为0,即小弧度角,弧线方向(sweep-flag)为1,即逆时针画弧线,小写a为相对位置,即终点坐标为(162 + 100.8 = 262.8, 128.7 + 374.4 = 503.1)。 + + ```ts + H198 + ``` + + 画水平线(horizontal lineto)到198,即画(262.8, 503.1)到(198, 503.1)的水平线。 + + ```ts + a36 36 0 0 3 -36 -36 + ``` + + 画圆弧线(elliptical arc),含义同上,结束点为(198 - 36 = 162, 503.1 - 36 = 467.1)。 + + ```ts + V128.7 + ``` + + 画垂直线(vertical lineto)到128.7,即画(162, 467.1)到(162, 128.7)的垂直线。 + + ```ts + z + ``` + + 关闭路径(closepath)。 + + ![drawing-feature4](figures/drawing-feature4.png) + + 填充颜色为白色。 + + ```ts + .fill(Color.White) + ``` + + ```ts + @Entry + @Component + struct Logo { + build() { + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Shape() { + Path() + .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36') + .fill(Color.White) + } + .height('630px') + .width('630px') + } + .width('100%') + .height('100%') + .linearGradient( + { + angle: 180, + colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]] + }) + } + } + ``` + + ![drawing-feature5](figures/drawing-feature5.png) + +6. 在Shape组件内绘制第二个Path。第二条Path的背景色为渐变色,但是渐变色的填充是其整体的box,所以需要clip将其裁剪,入参为Shape,即按照Shape的形状进行裁剪。 + + ```ts + Path() + .commands('M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z') + .fill('none') + .linearGradient( + { + angle: 30, + colors: [["#C4FFA0", 0], ["#ffffff", 1]] + }) + .clip(new Path().commands('M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z')) + ``` + + Path的绘制命令比较长,可以将其作为组件的成员变量,通过this调用。 + + ```ts + @Entry + @Component + struct Logo { + private pathCommands1:string = 'M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z' + build() { + ...... + Path() + .commands(this.pathCommands1) + .fill('none') + .linearGradient( + { + angle: 30, + colors: [["#C4FFA0", 0], ["#ffffff", 1]] + }) + .clip(new Path().commands(this.pathCommands1)) + ...... + } + } + ``` + + ![drawing-feature6](figures/drawing-feature6.png) + +7. 在Shape组件内绘制第二个Path。 + + ```ts + @Entry + @Component + struct Logo { + private pathCommands1:string = 'M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z' + private pathCommands2:string = 'M270.6 128.1 h48.6 c51.6 0 98.4 21 132.3 54.6 a411 411 0 0 3 -45.6 123 c-25.2 45.6 -56.4 84 -87.6 110.4 a206.1 206.1 0 0 0 -47.7 -288 z' + build() { + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Shape() { + Path() + .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36') + .fill(Color.White) + + Path() + .commands(this.pathCommands1) + .fill('none') + .linearGradient( + { + angle: 30, + colors: [["#C4FFA0", 0], ["#ffffff", 1]] + }) + .clip(new Path().commands(this.pathCommands1)) + + Path() + .commands(this.pathCommands2) + .fill('none') + .linearGradient( + { + angle: 50, + colors: [['#8CC36A', 0.1], ["#B3EB90", 0.4], ["#ffffff", 0.7]] + }) + .clip(new Path().commands(this.pathCommands2)) + } + .height('630px') + .width('630px') + } + .width('100%') + .height('100%') + .linearGradient( + { + angle: 180, + colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]] + }) + } + } + ``` + + ![drawing-feature7](figures/drawing-feature7.png) + + 完成应用Logo的绘制。Shape组合了三个Path组件,通过svg命令绘制出一个艺术的叶子,寓意绿色健康饮食方式。 + +8. 添加应用的标题和slogan。 + + ```ts + @Entry + @Component + struct Logo { + private pathCommands1:string = 'M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z' + private pathCommands2:string = 'M270.6 128.1 h48.6 c51.6 0 98.4 21 132.3 54.6 a411 411 0 0 3 -45.6 123 c-25.2 45.6 -56.4 84 -87.6 110.4 a206.1 206.1 0 0 0 -47.7 -288 z' + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Shape() { + Path() + .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36') + .fill(Color.White) + + Path() + .commands(this.pathCommands1) + .fill('none') + .linearGradient( + { + angle: 30, + colors: [["#C4FFA0", 0], ["#ffffff", 1]] + }) + .clip(new Path().commands(this.pathCommands1)) + + Path() + .commands(this.pathCommands2) + .fill('none') + .linearGradient( + { + angle: 50, + colors: [['#8CC36A', 0.1], ["#B3EB90", 0.4], ["#ffffff", 0.7]] + }) + .clip(new Path().commands(this.pathCommands2)) + } + .height('630px') + .width('630px') + + Text('Healthy Diet') + .fontSize(26) + .fontColor(Color.White) + .margin({ top:300 }) + + Text('Healthy life comes from a balanced diet') + .fontSize(17) + .fontColor(Color.White) + .margin({ top:4 }) + } + .width('100%') + .height('100%') + .linearGradient( + { + angle: 180, + colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]] + }) + } + } + ``` + + ![drawing-feature8](figures/drawing-feature8.png) \ No newline at end of file