提交 d3ce9de4 编写于 作者: Y yamila

ui cherryPick 3.2beta

Signed-off-by: Nyamila <tianyu55@huawei.com>
上级 3522b713
......@@ -3,37 +3,31 @@
- [方舟开发框架(ArkUI)概述](arkui-overview.md)
- UI开发(基于ArkTS的声明式开发范式)
- [概述](ui-ts-overview.md)
- 框架说明
- 文件组织
- [目录结构](ts-framework-directory.md)
- [应用代码文件访问规则](ts-framework-file-access-rules.md)
- 资源管理
- [资源文件的分类](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)
- 常见组件开发指导
- [Button开发指导](ui-ts-basic-components-button.md)
- [Web开发指导](ui-ts-components-web.md)
- 常见布局开发指导
- [弹性布局](ui-ts-layout-flex.md)
- [栅格布局](ui-ts-layout-grid-container.md)
- [媒体查询](ui-ts-layout-mediaquery.md)
- 体验声明式UI
- [创建声明式UI工程](ui-ts-creating-project.md)
- [初识Component](ui-ts-components.md)
- [声明式UI开发指导](ui-ts-developing-intro.md)
- 声明式UI开发实例
- [创建简单视图](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-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.md)
- [添加动画效果](ui-ts-animation-feature.md)
- 常见布局开发指导
- 自适应布局
- [线性布局](ui-ts-layout-linear.md)
- [层叠布局](ui-ts-layout-stack.md)
- [弹性布局](ui-ts-layout-flex.md)
- [网格布局](ui-ts-layout-grid.md)
- 响应式布局
- [栅格布局(新)](ui-ts-layout-grid-container-new.md)
- [栅格布局](ui-ts-layout-grid-container.md)
- [媒体查询](ui-ts-layout-mediaquery.md)
- [自定义组件的生命周期](ui-ts-custom-component-lifecycle-callbacks.md)
- [Web组件开发指导](ui-ts-components-web.md)
- [性能提升的推荐方法](ui-ts-performance-improvement-recommendation.md)
- UI开发(兼容JS的类Web开发范式)
- [概述](ui-js-overview.md)
- 框架说明
......
# 组件创建和重新初始化
## 初始创建和渲染
1. 创建父组件ParentComp;
2. 本地初始化ParentComp的状态变量isCountDown;
3. 执行ParentComp的build函数;
4. 创建Column内置组件;
1. 创建Text内置组件,设置其文本展示内容,并将Text组件实例添加到Column中;
2. 判断if条件,创建true分支上的组件;
1. 创建Image内置组件,并设置其图片源地址;
2. 使用给定的构造函数创建TimerComponent;
1. 创建TimerComponent对象;
2. 本地初始化成员变量初始值;
3. 使用TimerComponent构造函数提供的参数更新成员变量的值;
4. 执行TimerComponent的aboutToAppear函数;
5. 执行TimerComponent的build函数,创建相应的UI描述结构;
3. 创建Button内置组件,设置相应的内容。
## 状态更新
用户单击按钮时:
1. ParentComp的isCountDown状态变量的值更改为false;
2. 执行ParentComp的build函数;
3. Column内置组件会被框架重用并执行重新初始化;
4. Column的子组件会重用内存中的对象,但会进行重新初始化;
1. Text内置组件会被重用,但使用新的文本内容进行重新初始化;
2. 判断if条件,使用false分支上的组件;
1. 原来true分支上的组件不在使用,这些组件会进行销毁;
1. 创建的Image内置组件实例进行销毁;
2. TimerComponent组件实例进行销毁,aboutToDisappear函数被调用;
2. 创建false分支上的组件;
1. 创建Image内置组件,并设置其图片源地址;
2. 使用给定的构造函数重新创建TimerComponent;
3. 新创建的TimerComponent进行初始化,并调用aboutToAppear函数和build函数。
3. Button内置组件会被重用,但使用新的图片源地址。
## 示例
```ts
// xxx.ets
@Entry
@Component
struct ParentComp {
@State isCountDown: boolean = true
build() {
Column() {
Text(this.isCountDown ? 'Count Down' : 'Stopwatch')
if (this.isCountDown) {
Image($r("app.media.countdown")).width(200).height(200)
TimerComponent({counter: 10, changePerSec: -1, showInColor: Color.Red})
} else {
Image($r("app.media.stopwatch")).width(200).height(200)
TimerComponent({counter: 0, changePerSec: +1, showInColor: Color.Black })
}
Button(this.isCountDown ? 'Switch to Stopwatch' : 'Switch to Count Down')
.onClick(() => {this.isCountDown = !this.isCountDown})
}
}
}
// Manage and display a count down / stop watch timer
@Component
struct TimerComponent {
@State counter: number = 0
private changePerSec: number = -1
private showInColor: Color = Color.Black
private timerId : number = -1
build() {
Text(`${this.counter}sec`)
.fontColor(this.showInColor)
}
aboutToAppear() {
this.timerId = setInterval(() => {this.counter += this.changePerSec}, 1000)
}
aboutToDisappear() {
if (this.timerId > 0) {
clearTimeout(this.timerId)
this.timerId = -1
}
}
}
```
![](figures/zh-cn_image_0000001118642023.gif)
\ No newline at end of file
# 自定义组件成员变量初始化
组件的成员变量可以通过两种方式初始化:
- 本地初始化,例如:
```ts
@State counter: Counter = new Counter()
```
- 在构造组件时通过构造参数初始化,例如:
```ts
MyComponent({counter: $myCounter})
```
具体允许哪种方式取决于状态变量的装饰器:
| 装饰器类型 | 本地初始化 | 通过构造函数参数初始化 |
| ------------ | ----- | ----------- |
| @State | 必须 | 可选 |
| @Prop | 禁止 | 必须 |
| @Link | 禁止 | 必须 |
| @StorageLink | 必须 | 禁止 |
| @StorageProp | 必须 | 禁止 |
| @Provide | 必须 | 可选 |
| @Consume | 禁止 | 禁止 |
| @ObjectLink | 禁止 | 必须 |
| 常规成员变量 | 推荐 | 可选 |
从上表中可以看出:
- @State变量需要本地初始化,初始化的值可以被构造参数覆盖;
- @Prop和@Link变量必须且仅通过构造函数参数进行初始化。
通过构造函数方法初始化成员变量,需要遵循如下规则:
| 从父组件中的变量(下)到子组件中的变量(右) | @State | @Link | @Prop | 常规变量 |
| ---------------------- | ------ | ----- | ----- | ---- |
| @State | 不允许 | 允许 | 允许 | 允许 |
| @Link | 不允许 | 允许 | 不推荐 | 允许 |
| @Prop | 不允许 | 不允许 | 允许 | 允许 |
| @StorageLink | 不允许 | 允许 | 不允许 | 允许 |
| @StorageProp | 不允许 | 不允许 | 不允许 | 允许 |
| 常规变量 | 允许 | 不允许 | 不允许 | 允许 |
从上表中可以看出:
- 父组件的常规变量可以用于初始化子组件的@State变量,但不能用于初始化@Link或@Prop变量。
- 父组件的@State变量可以初始化子组件的@Prop、@Link(通过$)或常规变量,但不能初始化子组件的@State变量。
- 父组件的@Link变量可以初始化子组件的@Link或常规变量。但是初始化子组件的@State成员是语法错误,此外不建议初始化@prop。
- 父组件的@Prop变量可以初始化子组件的常规变量或@Prop变量,但不能初始化子组件的@State或@Link变量。
- @StorageLink和@StorageProp不允许由父组件中传递到子组件。
- 除了上述规则外,还需要遵循TS的强类型规则。
## 示例
```ts
// xxx.ets
class ClassA {
public a:number
constructor(a: number) {
this.a = a
}
}
@Entry
@Component
struct Parent {
@State parentState: ClassA = new ClassA(1)
build() {
Column() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
CompA({ aState: new ClassA(2), aLink: $parentState })
}
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
CompA({ aLink: $parentState })
}
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
CompA({ aState: new ClassA(3), aLink: $parentState })
}
}
}
}
@Component
struct CompA {
@State aState: any = false
@Link aLink: ClassA
build() {
Column() {
CompB({ bLink: $aLink, bProp: this.aState })
CompB({ bLink: $aState, bProp: false })
}
}
}
@Component
struct CompB {
@Link bLink: ClassA
@Prop bProp: boolean
build() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text(JSON.stringify(this.bLink.a)).fontSize(30)
Text(JSON.stringify(this.bProp)).fontSize(30).fontColor(Color.Red)
}.margin(10)
}
}
```
# 目录结构
FA应用的ArkTS模块(entry/src/main)的典型开发目录结构如下:
![zh-cn_image_0000001182200571](figures/zh-cn_image_0000001182200571.png)
**目录结构中文件分类如下:**
.ets结尾的ArkTS(extended TypeScript)文件,用于描述UI布局、样式、事件交互和页面逻辑。
**各个文件夹和文件的作用:**
- **app.ets**文件用于全局应用逻辑和应用生命周期管理。
- **pages**目录用于存放所有页面。
- **common**目录用于存放公共代码文件,比如:自定义组件和公共方法。
> **说明:**
>
> - 资源目录resources文件夹位于src/main下,此目录下资源文件的详细规范以及子目录结构规范参看[资源文件的分类](ui-ts-basic-resource-file-categories.md)。
>- 页面支持导入TypeScript和JavaScript文件。
**js标签配置:**
开发框架需要在配置文件中标识相关的js标签,其中的每个元素代表一个JS模块的信息,包含了实例名称、页面路由、视图窗口等。
> **说明:**
>
> FA模型请参考 [表22 js对象的内部结构说明](../quick-start/package-structure.md#module对象的内部结构)。
>
> Stage模型请参考 [表3 module对象内部结构说明](../quick-start/stage-structure.md#module对象内部结构)。
# 文件访问规则
应用代码中文件访问方法主要有下面两种:
- **相对路径**:使用相对路径引用代码文件,以"../"访问上一级目录,以"./"访问当前目录,也可以省略不写。
- **绝对路径**:使用当前模块根路径引用代码文件,比如:common/utils/utils。
## 示例
```
common
│ └─ utils
│ └─ utils.ets
└─ pages
└─ index.ets
```
如上所示,将utils放在common中,示例中index.ets访问utils.ets文件
```ts
// xxx.ets
import { FoodData, FoodList } from "../common/utils/utils";
@Entry
@Component
struct FoodCategoryList {
private foodItems: FoodData[] = [
new FoodData("Tomato"),
new FoodData("Strawberry"),
new FoodData("Cucumber")
]
build() {
Column() {
FoodList({ foodItems: this.foodItems })
}
}
}
```
被导入文件utils.ets:
```ts
//common/utils/utils.ets
export class FoodData {
name: string;
constructor(name: string) {
this.name = name;
}
}
@Component
export struct FoodList {
private foodItems: FoodData[]
build() {
Column() {
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text('Food List')
.fontSize(20)
}
.width(200)
.height(56)
.backgroundColor('#FFf1f3f5')
List() {
ForEach(this.foodItems, item => {
ListItem() {
Text(item.name)
.fontSize(14)
}
}, item => item.toString())
}
}
}
}
```
\ No newline at end of file
# 资源访问
## 访问应用资源
在工程中,通过```"$r('app.type.name')"```的形式引用应用资源。app代表是应用内resources目录中定义的资源;type代表资源类型(或资源的存放位置),可以取“color”、“float”、“string”、“plural”、“media”,name代表资源命名,由开发者定义资源时确定。
引用rawfile下资源时使用```"$rawfile('filename')"```的形式,filename需要表示为rawfile目录下的文件相对路径,文件名需要包含后缀,路径开头不可以以"/"开头。
> **说明:**
>
> 资源描述符不能拼接使用,仅支持普通字符串如`'app.type.name'`。
>
> `$r`返回值为Resource对象,可通过[getString](../reference/apis/js-apis-resource-manager.md#getstring) 方法获取对应的字符串。
在xxx.ets文件中,可以使用在resources目录中定义的资源。
```ts
Text($r('app.string.string_hello'))
.fontColor($r('app.color.color_hello'))
.fontSize($r('app.float.font_hello'))
}
Text($r('app.string.string_world'))
.fontColor($r('app.color.color_world'))
.fontSize($r('app.float.font_world'))
}
Text($r('app.string.message_arrive', "five of the clock")) // 引用string资源,$r的第二个参数用于替换%s
.fontColor($r('app.color.color_hello'))
.fontSize($r('app.float.font_hello'))
}
Text($r('app.plural.eat_apple', 5, 5)) // plural$r引用,第一个指定plural资源,第二个参数指定单复数的数量,此处第三个数字为对%d的替换
.fontColor($r('app.color.color_world'))
.fontSize($r('app.float.font_world'))
}
Image($r('app.media.my_background_image')) // media资源的$r引用
Image($rawfile('test.png')) // rawfile$r引用rawfile目录下图片
Image($rawfile('newDir/newTest.png')) // rawfile$r引用rawfile目录下图片
```
## 访问系统资源
系统资源包含色彩、圆角、字体、间距、字符串及图片等。通过使用系统资源,不同的开发者可以开发出具有相同视觉风格的应用。
开发者可以通过```“$r('sys.type.resource_id')”```的形式引用系统资源。sys代表是系统资源;type代表资源类型,可以取“color”、“float”、“string”、“media”;resource_id代表资源id。
```ts
Text('Hello')
.fontColor($r('sys.color.ohos_id_color_emphasize'))
.fontSize($r('sys.float.ohos_id_text_size_headline1'))
.fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
.backgroundColor($r('sys.color.ohos_id_color_palette_aux1'))
Image($r('sys.media.ohos_app_icon'))
.border({color: $r('sys.color.ohos_id_color_palette_aux1'), radius: $r('sys.float.ohos_id_corner_radius_button'), width: 2})
.margin({top: $r('sys.float.ohos_id_elements_margin_horizontal_m'), bottom: $r('sys.float.ohos_id_elements_margin_horizontal_l')})
.height(200)
.width(300)
```
## 资源文件示例
color.json文件的内容如下:
```json
{
"color": [
{
"name": "color_hello",
"value": "#ffff0000"
},
{
"name": "color_world",
"value": "#ff0000ff"
}
]
}
```
float.json文件的内容如下:
```json
{
"float":[
{
"name":"font_hello",
"value":"28.0fp"
},
{
"name":"font_world",
"value":"20.0fp"
}
]
}
```
string.json文件的内容如下:
```json
{
"string":[
{
"name":"string_hello",
"value":"Hello"
},
{
"name":"string_world",
"value":"World"
},
{
"name":"message_arrive",
"value":"We will arrive at %s."
}
]
}
```
plural.json文件的内容如下:
```json
{
"plural":[
{
"name":"eat_apple",
"value":[
{
"quantity":"one",
"value":"%d apple"
},
{
"quantity":"other",
"value":"%d apples"
}
]
}
]
}
```
## 相关实例
针对访问应用资源,有以下相关实例可供参考:
- [`ResourceManager`:资源管理器(ArkTS)(API8)](https://gitee.com/openharmony/applications_app_samples/tree/master/common/ResourceManager)
# 添加动画效果
动画主要包含了组件动画和页面间动画,并提供了[插值计算](../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框架提供了丰富的动效接口,合理地应用和组合可以让应用更具有设计感。
# Button
Button是按钮组件,通常用于响应用户的点击操作,如提交表单,其类型包括胶囊按钮、圆形按钮、普通按钮。具体用法请参考[Button](../reference/arkui-ts/ts-basic-components-button.md)
## 创建按钮
Button通过调用接口来创建,接口调用有以下两种形式:
- 创建包含子组件的按钮
`Button(options?: {type?: ButtonType, stateEffect?: boolean})`,该接口用于创建包含子组件的按钮,其中type用于设置Button类型,stateEffect属性设置Button是否开启点击效果。
```
Button({ type: ButtonType.Normal, stateEffect: true }) {
Row() {
Image($r('app.media.loading')).width(20).height(20).margin({ left: 12 })
Text('loading').fontSize(12).fontColor(0xffffff).margin({ left: 5, right: 12 })
}.alignItems(VerticalAlign.Center)
}.borderRadius(8).backgroundColor(0x317aff).width(90)
```
![zh-cn_image_0000001260555857](figures/zh-cn_image_0000001260555857.png)
- 创建不包含子组件的按钮
`Button(label?: string, options?: { type?: ButtonType, stateEffect?: boolean })`,该接口用于创建不包含子组件的按钮,其中label确定所创建的Button是否包含子组件。
```
Button('Ok', { type: ButtonType.Normal, stateEffect: true })
.borderRadius(8)
.backgroundColor(0x317aff)
.width(90)
```
![zh-cn_image_0000001215796030](figures/zh-cn_image_0000001215796030.png)
## 设置按钮类型
Button有三种可选类型,分别为Capsule(胶囊类型)、Circle(圆形按钮)和Normal(普通按钮),通过type进行设置。
- 胶囊按钮(默认类型)
```ts
Button('Disable', { type: ButtonType.Capsule, stateEffect: false })
.backgroundColor(0x317aff)
.width(90)
```
![zh-cn_image_0000001215645452](figures/zh-cn_image_0000001215645452.png)
- 圆形按钮
```ts
Button('Circle', { type: ButtonType.Circle, stateEffect: false })
.backgroundColor(0x317aff)
.width(90)
.height(90)
```
![zh-cn_image_0000001215965420](figures/zh-cn_image_0000001215965420.png)
## 自定义样式
- 设置边框弧度
一般使用通用属性来自定义按钮样式。例如通过borderRadius属性设置按钮的边框弧度。
```ts
Button('circle border', { type: ButtonType.Normal })
.borderRadius(20)
```
![zh-cn_image_0000001190463780](figures/zh-cn_image_0000001190463780.png)
- 设置文本样式
通过添加文本样式设置按钮文本的展示样式。
```ts
Button('font style', { type: ButtonType.Normal })
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(800)
```
![zh-cn_image_0000001189744672](figures/zh-cn_image_0000001189744672.png)
- 设置背景颜色
添加backgroundColor属性设置按钮的背景颜色。
```ts
Button('background color').backgroundColor(0xF55A42)
```
![zh-cn_image_0000001235146483](figures/zh-cn_image_0000001235146483.png)
- 用作功能型按钮
为删除操作创建一个按钮。
```ts
Button({ type: ButtonType.Circle, stateEffect: true }) {
Image($r('app.media.ic_public_delete_filled')).width(30).height(30)
}.width(55).height(55).margin({ left: 20 }).backgroundColor(0xF55A42)
```
![zh-cn_image_0000001260373911](figures/zh-cn_image_0000001260373911.png)
## 添加事件
Button组件通常用于触发某些操作,可以在绑定onClick事件来响应点击操作后的自定义行为。
```ts
Button('Ok', { type: ButtonType.Normal, stateEffect: true })
.onClick(()=>{
console.info('Button onClick')
})
```
## 场景示例
- 用于启动操作
可以将按钮用于启动操作的任何用户界面元素。按钮会根据用户的操作触发相应的事件。如,在List容器里边通过点击按钮进行页面跳转:
```ts
// xxx.ets
import router from '@ohos.router'
@Entry
@Component
struct ButtonCase1 {
build() {
List({ space: 4 }) {
ListItem() {
Button("First").onClick(() => {
router.push({ url: 'xxx' })
})
}
ListItem() {
Button("Second").onClick(() => {
router.push({ url: 'yyy' })
})
}
ListItem() {
Button("Third").onClick(() => {
router.push({ url: 'zzz' })
})
}
}
.listDirection(Axis.Vertical)
.backgroundColor(0xDCDCDC).padding(20)
}
}
```
​ ![zh-cn_image_0000001235626467](figures/zh-cn_image_0000001235626467.png)
- 用于表单的提交
在用户登录/注册页面,用户的登录或注册的提交操作会用按钮。
```ts
// xxx.ets
@Entry
@Component
struct ButtonCase2 {
build() {
Column() {
TextInput({ placeholder: 'input your username' }).margin({ top: 20 })
TextInput({ placeholder: 'input your password' }).type(InputType.Password).margin({ top: 20 })
Button('Register').width(300).margin({ top: 20 })
}.padding(20)
}
}
```
![zh-cn_image_0000001190466492](figures/zh-cn_image_0000001190466492.png)
# 资源文件的分类
应用开发中使用的各类资源文件,需要放入特定子目录中存储管理。
## resources目录
应用的资源文件(字符串、图片、音频等)统一存放于resources目录下,便于开发者使用和维护。resources目录包括两大类目录,一类为base目录与限定词目录,另一类为rawfile目录,详见resources目录分类,stage模型多工程情况下共有的资源文件放到AppScope下的resources目录。
资源目录示例:
```
resources
|---base // 默认存在的目录
| |---element
| | |---string.json
| |---media
| | |---icon.png
|---en_GB-vertical-car-mdpi // 限定词目录示例,需要开发者自行创建
| |---element
| | |---string.json
| |---media
| | |---icon.png
|---rawfile
```
**表1** resources目录分类
| 分类 | base目录 | 限定词目录 | rawfile目录 |
| ---- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- |
| 组织形式 | base目录是默认存在的目录。当应用的resources目录中没有与设备状态匹配的限定词目录时,会自动引用该目录中的资源文件。<br/>base目录的二级子目录为**资源组目录**,用于存放字符串、颜色、布尔值等基础元素,以及媒体、动画、布局等资源文件,具体要求参见[资源组目录](#资源组目录)。 | 限定词目录需要开发者自行创建。目录名称由一个或多个表征应用场景或设备特征的限定词组合而成,具体要求参见[限定词目录](#限定词目录)<br/>限定词目录的二级子目录为**资源组目录**,用于存放字符串、颜色、布尔值等基础元素,以及媒体、动画、布局等资源文件,具体要求参见[资源组目录](#资源组目录)。 | 支持创建多层子目录,目录名称可以自定义,文件夹内可以自由放置各类资源文件。<br/>rawfile目录的文件不会根据设备状态去匹配不同的资源。 |
| 编译方式 | 目录中的资源文件会被编译成二进制文件,并赋予资源文件ID。 | 目录中的资源文件会被编译成二进制文件,并赋予资源文件ID。 | 目录中的资源文件会被直接打包进应用,不经过编译,也不会被赋予资源文件ID。 |
| 引用方式 | 通过指定资源类型(type)和资源名称(name)来引用。 | 通过指定资源类型(type)和资源名称(name)来引用。 | 通过指定文件路径和文件名来引用。 |
## 限定词目录
限定词目录可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度,限定词之间通过下划线(_)或者中划线(-)连接。开发者在创建限定词目录时,需要掌握限定词目录的命名要求,以及限定词目录与设备状态的匹配规则。
**限定词目录的命名要求**
- 限定词的组合顺序:_移动国家码_移动网络码-语言_文字_国家或地区-横竖屏-设备类型-颜色模式-屏幕密度_。开发者可以根据应用的使用场景和设备特征,选择其中的一类或几类限定词组成目录名称。
- 限定词的连接方式:语言、文字、国家或地区之间采用下划线(_)连接,移动国家码和移动网络码之间也采用下划线(_)连接,除此之外的其他限定词之间均采用中划线(-)连接。例如:**zh_Hant_CN****zh_CN-car-ldpi**
- 限定词的取值范围:每类限定词的取值必须符合限定词取值要求表中的条件,否则,将无法匹配目录中的资源文件。
**表2** 限定词取值要求
| 限定词类型 | 含义与取值说明 |
| ----------- | ---------------------------------------- |
| 移动国家码和移动网络码 | 移动国家码(MCC)和移动网络码(MNC)的值取自设备注册的网络。MCC后面可以跟随MNC,使用下划线(_)连接,也可以单独使用。例如:mcc460表示中国,mcc460_mnc00表示中国_中国移动。<br/>详细取值范围,请查阅**ITU-T&nbsp;E.212**(国际电联相关标准)。 |
| 语言 | 表示设备使用的语言类型,由2~3个小写字母组成。例如:zh表示中文,en表示英语,mai表示迈蒂利语。<br/>详细取值范围,请查阅**ISO&nbsp;639**(ISO制定的语言编码标准)。 |
| 文字 | 表示设备使用的文字类型,由1个大写字母(首字母)和3个小写字母组成。例如:Hans表示简体中文,Hant表示繁体中文。<br/>详细取值范围,请查阅**ISO&nbsp;15924**(ISO制定的文字编码标准)。 |
| 国家或地区 | 表示用户所在的国家或地区,由2~3个大写字母或者3个数字组成。例如:CN表示中国,GB表示英国。<br/>详细取值范围,请查阅**ISO&nbsp;3166-1**(ISO制定的国家和地区编码标准)。 |
| 横竖屏 | 表示设备的屏幕方向,取值如下:<br/>-&nbsp;vertical:竖屏<br/>-&nbsp;horizontal:横屏 |
| 设备类型 | 表示设备的类型,取值如下:<br/>-&nbsp;car:车机<br/>-&nbsp;tv:智慧屏<br/>-&nbsp;wearable:智能穿戴 |
| 颜色模式 | 表示设备的颜色模式,取值如下:<br/>-&nbsp;dark:深色模式<br/>-&nbsp;light:浅色模式 |
| 屏幕密度 | 表示设备的屏幕密度(单位为dpi),取值如下:<br/>-&nbsp;sdpi:表示小规模的屏幕密度(Small-scale&nbsp;Dots&nbsp;Per&nbsp;Inch),适用于dpi取值为(0,&nbsp;120]的设备。<br/>-&nbsp;mdpi:表示中规模的屏幕密度(Medium-scale&nbsp;Dots&nbsp;Per&nbsp;Inch),适用于dpi取值为(120,&nbsp;160]的设备。<br/>-&nbsp;ldpi:表示大规模的屏幕密度(Large-scale&nbsp;Dots&nbsp;Per&nbsp;Inch),适用于dpi取值为(160,&nbsp;240]的设备。<br/>-&nbsp;xldpi:表示特大规模的屏幕密度(Extra&nbsp;Large-scale&nbsp;Dots&nbsp;Per&nbsp;Inch),适用于dpi取值为(240,&nbsp;320]的设备。<br/>-&nbsp;xxldpi:表示超大规模的屏幕密度(Extra&nbsp;Extra&nbsp;Large-scale&nbsp;Dots&nbsp;Per&nbsp;Inch),适用于dpi取值为(320,&nbsp;480]的设备。<br/>-&nbsp;xxxldpi:表示超特大规模的屏幕密度(Extra&nbsp;Extra&nbsp;Extra&nbsp;Large-scale&nbsp;Dots&nbsp;Per&nbsp;Inch),适用于dpi取值为(480,&nbsp;640]的设备。 |
**限定词目录与设备状态的匹配规则**
- 在为设备匹配对应的资源文件时,限定词目录匹配的优先级从高到低依次为:移动国家码和移动网络码 &gt; 区域(可选组合:语言、语言_文字、语言_国家或地区、语言_文字_国家或地区)&gt; 横竖屏 &gt; 设备类型 &gt; 颜色模式 &gt; 屏幕密度。
- 如果限定词目录中包含**移动国家码和移动网络码、语言、文字、横竖屏、设备类型、颜色模式**限定词,则对应限定词的取值必须与当前的设备状态完全一致,该目录才能够参与设备的资源匹配。例如,限定词目录“zh_CN-car-ldpi”不能参与“en_US”设备的资源匹配。
## 资源组目录
base目录与限定词目录下面可以创建资源组目录(包括element、media、animation、layout、graphic、profile),用于存放特定类型的资源文件,详见资源组目录说明。
**表3** 资源组目录说明
| 资源组目录 | 目录说明 | 资源文件 |
| ------- | ---------------------------------------- | ---------------------------------------- |
| element | 表示元素资源,以下每一类数据都采用相应的JSON文件来表征。<br/>-&nbsp;boolean,布尔型<br/>-&nbsp;color,颜色<br/>-&nbsp;float,浮点型<br/>-&nbsp;intarray,整型数组<br/>-&nbsp;integer,整型<br/>-&nbsp;pattern,样式<br/>-&nbsp;plural,复数形式<br/>-&nbsp;strarray,字符串数组<br/>-&nbsp;string,字符串 | element目录中的文件名称建议与下面的文件名保持一致。每个文件中只能包含同一类型的数据。<br/>-&nbsp;boolean.json<br/>-&nbsp;color.json<br/>-&nbsp;float.json<br/>-&nbsp;intarray.json<br/>-&nbsp;integer.json<br/>-&nbsp;pattern.json<br/>-&nbsp;plural.json<br/>-&nbsp;strarray.json<br/>-&nbsp;string.json |
| media | 表示媒体资源,包括图片、音频、视频等非文本格式的文件。 | 文件名可自定义,例如:icon.png。 |
| rawfile | 表示其他类型文件,在应用构建为hap包后,以原始文件形式保存,不会被集成到resources.index文件中。 | 文件名可自定义。 |
### 媒体资源类型说明
表1 图片资源类型说明
| 格式 | 文件后缀名 |
| ---- | ----- |
| JPEG | .jpg |
| PNG | .png |
| GIF | .gif |
| SVG | .svg |
| WEBP | .webp |
| BMP | .bmp |
表2 音视频资源类型说明
| 格式 | 支持的文件类型 |
| ------------------------------------ | --------------- |
| H.263 | .3gp <br>.mp4 |
| H.264 AVC <br> Baseline Profile (BP) | .3gp <br>.mp4 |
| MPEG-4 SP | .3gp |
| VP8 | .webm <br> .mkv |
## 创建资源文件
在resources目录下,可按照限定词目录和资源组目录的说明创建子目录和目录内的文件。
同时,DevEco Studio也提供了创建资源目录和资源文件的界面。
- 创建资源目录及资源文件
在resources目录右键菜单选择“New > Resource File”,此时可同时创建目录和文件。
文件默认创建在base目录的对应资源组下。如果选择了限定词,则会按照命名规范自动生成限定词+资源组目录,并将文件创建在目录中。
目录名自动生成,格式固定为“限定词.资源组”,例如创建一个限定词为横竖屏类别下的竖屏,资源组为绘制资源的目录,自动生成的目录名称为“vertical.graphic”。
![create-resource-file-1](figures/create-resource-file-1.png)
- 创建资源目录
在resources目录右键菜单选择“New > Resource Directory”,此时可创建资源目录。
选择资源组类型,设置限定词,创建后自动生成目录名称。目录名称格式固定为“限定词.资源组”,例如创建一个限定词为横竖屏类别下的竖屏,资源组为绘制资源的目录,自动生成的目录名称为“vertical.graphic”。
![create-resource-file-2](figures/create-resource-file-2.png)
- 创建资源文件
在资源目录的右键菜单选择“New > XXX Resource File”,即可创建对应资源组目录的资源文件。
例如,在element目录下可新建Element Resource File。
![create-resource-file-3](figures/create-resource-file-3.png)
\ No newline at end of file
# 构建食物分类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
# 构建食物列表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
......@@ -13,7 +13,7 @@
2. 定义食物数据的存储模型FoodData和枚举变量Category,FoodData类包含食物id、名称(name)、分类(category)、图片(image)、热量(calories)、蛋白质(protein)、脂肪(fat)、碳水(carbohydrates)和维生素C(vitaminC)属性。
ArkTS语言是在ts语言的基础上的扩展,同样支持ts语法。
```
```ts
enum Category {
Fruit,
Vegetable,
......@@ -52,7 +52,7 @@
4. 创建食物资源数据。在model文件夹下创建FoodDataModels.ets,在该页面中声明食物成分数组FoodComposition。以下示例创建了两个食物数据。
```
```ts
const FoodComposition: any[] = [
{ 'name': 'Tomato', 'image': $r('app.media.Tomato'), 'category': Category.Vegetable, 'calories': 17, 'protein': 0.9, 'fat': 0.2, 'carbohydrates': 3.9, 'vitaminC': 17.8 },
{ 'name': 'Walnut', 'image': $r('app.media.Walnut'), 'category': Category.Nut, 'calories': 654 , 'protein': 15, 'fat': 65, 'carbohydrates': 14, 'vitaminC': 1.3 }
......@@ -62,7 +62,7 @@
实际开发中,开发者可以自定义更多的数据资源,当食物资源很多时,建议使用数据懒加载LazyForEach。
5. 创建initializeOnStartUp方法来初始化FoodData的数组。在FoodDataModels.ets中使用了定义在FoodData.ets的FoodData和Category,所以要将FoodData.ets的FoodData类export,在FoodDataModels.ets内import FoodData和Category。
```
```ts
// FoodData.ets
export enum Category {
......
......
# 初识Component
在自定义组件之前,需要先了解什么是[组件和装饰器](#组件和装饰器),并进行初始化组件。然后通过[修改组件属性和构造参数](#修改组件属性和构造参数),实现一个自定义组件。
## 组件和装饰器
在声明式UI中,所有的页面都是由组件构成。组件的数据结构为struct,装饰器@Component是组件化的标志。用@Component修饰的struct表示这个结构体有了组件化的能力。
自定义组件的声明方式为:
```ts
@Component
struct MyComponent {}
```
在IDE创建工程模板中,MyComponent就是一个可以居中显示文字的自定义组件。开发者可以在Component的build方法里描述自己的UI结构,但需要遵循Builder的接口约束。
```ts
interface Builder {
build: () => void
}
```
@Entry修饰的Component表示该Component是页面的总入口,也可以理解为页面的根节点。值得注意的是,一个页面有且仅能有一个@Entry,只有被@Entry修饰的组件或者其子组件,才会在页面上显示。
@Component和@Entry都是基础且十分重要的装饰器。简单地理解,装饰器就是某一种修饰,给被装饰的对象赋予某一种能力,比如@Entry就是页面入口的能力,@Component就是组件化能力。
在了解了组件和装饰器这两个重要概念后,接下来可以开始开发健康饮食应用。
## 修改组件属性和构造参数
开发者创建系统组件时,会显示其默认样式。开发者可以通过更改组件的属性样式来改变组件的视图显示。
1. 修改Text组件的fontSize属性来更改组件的字体大小,将字体大小设置为26,通过fontWeight属性更改字体粗细,将其设置为500。fontWeight属性支持三种设置方式:
1. number类型的取值范围为100到900,取值间隔为100,默认为400,取值越大,字体越粗。
2. FontWeight为内置枚举类型,取值支持FontWeight.Lighter、FontWeight.Normal、FontWeight.Regular、FontWeight.Medium、FontWeight.Bold、FontWeight.Bolder。FontWeight.Normal即为400数值的字体粗细。
3. string类型仅支持number类型取值的字符串形式,例如"400",以及"bold"、"bolder"、"lighter"、"regular"、"medium",分别对应FontWeight中相应的枚举值。设置其他字符串则为无效,保持默认字体粗细显示。
属性方法要紧随组件,通过“.”运算符连接,也可以通过链式调用的方式配置组件的多个属性。
```
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Hello World')
.fontSize(26)
.fontWeight(500)
}
.width('100%')
.height('100%')
}
}
```
![zh-cn_image_0000001168728272](figures/zh-cn_image_0000001168728272.png)
2. 修改Text组件的显示内容“Hello World”为“Tomato”,通过修改Text组件的构造参数来实现。
```
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
.width('100%')
.height('100%')
}
}
```
![zh-cn_image_0000001168888224](figures/zh-cn_image_0000001168888224.png)
# 创建声明式UI工程
创建工程之前,首先需要安装DevEco Studio。
1. 打开DevEco Studio,点击Create Project。如果已有一个工程,则点击File &gt; New &gt; New project。
![zh-cn_image_0000001168956332](figures/zh-cn_image_0000001168956332.png)
2.
进入选择Ability Template界面,选择Empty Ability。
![zh-cn_image_0000001168059158](figures/zh-cn_image_0000001168059158.png)
3. 进入配置工程界面,将工程名字改为HealthyDiet,Project Type选择Application,Compile API选择8,UI Syntax选择eTS。DevEco Studio会默认将工程保存在C盘,如果要更改工程保存位置,点击Save Location的文件夹图标,自行指定工程创建位置。配置完成后点击Finish。
![zh-cn_image_0000001167746622](figures/zh-cn_image_0000001167746622.png)
4. 工程创建完成后,打开app.ets。
app.ets提供了应用生命周期的接口:onCreate和onDestroy,分别在应用创建之初和应用被销毁时调用。在app.ets里可以声明全局变量,并且声明的数据和方法是整个应用共享的。
```
export default {
onCreate() {
console.info('Application onCreate')
},
onDestroy() {
console.info('Application onDestroy')
},
}
```
5. 在工程导航栏里,打开index.ets。该页面展示了当前的UI描述,声明式UI框架会自动生成一个组件化的struct,这个struct遵循Builder接口声明,在build方法里面声明当前的布局和组件。
```
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Hello World')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height('100%')
}
}
```
6. 点击右侧的Previewer按钮,打开预览窗口。可以看到预览窗口中“Hello World”居中加粗显示。
如果没有Previewer按钮,点击settings &gt; SDK Manager &gt; OpenHarmony SDK&gt; Tools 查看是否安装了Previewer。
![zh-cn_image_0000001214595111](figures/zh-cn_image_0000001214595111.png)
7. 应用安装到设备上运行应用。
将设备连接电脑,等IDE识别到物理设备后,点击Run 'entry'按钮。
![zh-cn_image_0000001148858818](figures/zh-cn_image_0000001148858818.png)
在安装之前,需要配置应用签名。安装成功后,点击屏幕上的Run图标打开应用,可以看到居中加粗显示的“Hello World”。
![zh-cn_image_0000001158896538](figures/zh-cn_image_0000001158896538.png)
......@@ -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 {
......@@ -22,11 +26,11 @@
```
![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&gt;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布局已完成食物的图文展示和营养成分表,构建出第一个普通视图的食物详情页。在下一个章节中,将开发食物分类列表页,并完成食物分类列表页面和食物详情页面的跳转和数据传递,现在我们就进入下一个章节的学习吧。
......
# 自定义组件的生命周期
自定义组件的生命周期回调函数用于通知用户该自定义组件的生命周期,这些回调函数是私有的,在运行时由开发框架在特定的时间进行调用,不能从应用程序中手动调用这些回调函数。
> **说明:**
>
> - 允许在生命周期函数中使用Promise和异步回调函数,比如网络资源获取,定时器设置等;
>
> - 不允许在生命周期函数中使用async await。
## aboutToAppear
aboutToAppear?(): void
aboutToAppear函数在创建自定义组件的新实例后,在执行其build函数之前执行。允许在aboutToAppear函数中改变状态变量,更改将在后续执行build函数中生效。
## aboutToDisappear
aboutToDisappear?(): void
aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
**示例1:**
```ts
// xxx.ets
@Entry
@Component
struct CountDownTimerComponent {
@State countDownFrom: number = 10
private timerId: number = -1
aboutToAppear(): void {
this.timerId = setInterval(() => {
if (this.countDownFrom <= 1) {
clearTimeout(this.timerId) // countDownFrom小于等于1时清除计时器
}
this.countDownFrom -= 1
}, 1000) // countDownFrom每1s减1
}
aboutToDisappear(): void {
if (this.timerId > 0) {
clearTimeout(this.timerId)
this.timerId = -1
}
}
build() {
Column() {
Text(`${this.countDownFrom} sec left`)
.fontSize(30)
.margin(30)
}.width('100%')
}
}
```
![aboutToAppear](figures/aboutToAppear.gif)
上述示例表明,生命周期函数对于允许CountDownTimerComponent管理其计时器资源至关重要,类似的函数也包括异步从网络请求加载资源。
## onPageShow
onPageShow?(): void
页面每次显示时触发一次,包括路由过程、应用进入前后台等场景,仅@Entry修饰的自定义组件生效。
## onPageHide
onPageHide?(): void
页面每次隐藏时触发一次,包括路由过程、应用进入前后台等场景,仅@Entry修饰的自定义组件生效。
## onBackPress
onBackPress?(): void
当用户点击返回按钮时触发,仅@Entry修饰的自定义组件生效。返回true表示页面自己处理返回逻辑,不进行页面路由,返回false表示使用默认的路由返回逻辑。不设置返回值按照false处理。
**示例2:**
```ts
// xxx.ets
@Entry
@Component
struct IndexComponent {
@State textColor: Color = Color.Black
onPageShow() {
this.textColor = Color.Blue
console.info('IndexComponent onPageShow')
}
onPageHide() {
this.textColor = Color.Transparent
console.info('IndexComponent onPageHide')
}
onBackPress() {
this.textColor = Color.Red
console.info('IndexComponent onBackPress')
}
build() {
Column() {
Text('Hello World')
.fontColor(this.textColor)
.fontSize(30)
.margin(30)
}.width('100%')
}
}
```
![lifecycle](figures/lifecycle.PNG)
# 声明式UI开发指导
## 开发说明
声明式UI的工程结构还请参考[OpenHarmony APP工程结构介绍](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-project-overview-0000001218440650#section133380945818)。其中,.ets结尾的ArkTS文件用于描述UI布局、样式、事件交互和页面逻辑,支持导入TypeScript和JavaScript文件。资源目录resources文件夹位于src/main下,此目录下资源文件的详细规范以及子目录结构规范参看[资源分类与访问](../quick-start/resource-categories-and-access.md)
在开发页面之前,请先[学习ArkTS语言](../quick-start/arkts-get-started.md)了解声明式UI的基本语法。
在开发页面时,可先根据使用场景,在[常见布局](ui-ts-layout-linear.md)中选择合适的布局。再根据页面需要实现的内容,为页面添加系统内置组件,更新组件状态。页面开发过程中请参考[自定义组件的生命周期](ui-ts-custom-component-lifecycle-callbacks.md)了解如何添加需要的生命周期回调方法。
也可在页面中添加[绘图](../reference/arkui-ts/ts-drawing-components-circle.md)[动画](../reference/arkui-ts/ts-animatorproperty.md),丰富页面的展现形态。还可以使用[路由](../reference/apis/js-apis-router.md)实现多个页面之间的跳转和数据传递。
另外请参考[性能提升的推荐方法](ui-ts-performance-improvement-recommendation.md),避免低性能代码对应用的性能造成负面影响。
## 创建页面
请先根据页面预期效果选择布局结构创建页面,并在页面中添加基础的系统内置组件。下述示例采用了[弹性布局(Flex)](ui-ts-layout-flex.md),对页面中的Text组件进行横纵向居中布局显示。
```ts
// xxx.ets
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Hello')
}
.width('100%')
.height('100%')
}
}
```
## 修改组件样式
创建系统内置组件时,若不设置属性方法,则会显示其默认样式。通过更改组件的属性样式或者组件支持的[通用属性](../reference/arkui-ts/ts-universal-attributes-size.md)样式,设置可以改变组件的UI显示。
1. 通过修改Text组件的构造参数,将Text组件的显示内容修改为“Tomato”。
2. 修改Text组件的fontSize属性更改组件的字体大小,将字体大小设置为26,通过fontWeight属性更改字体粗细,将其设置为500。fontWeight属性支持三种设置方式:
a. number类型的取值范围为100到900,取值间隔为100,默认为400,取值越大,字体越粗。
b. FontWeight为内置枚举类型,取值支持FontWeight.Lighter、FontWeight.Normal、FontWeight.Regular、FontWeight.Medium、FontWeight.Bold、FontWeight.Bolder。FontWeight.Normal即为400数值的字体粗细。
c. string类型仅支持number类型取值的字符串形式,例如"400",以及"bold"、"bolder"、"lighter"、"regular"、"medium",分别对应FontWeight中相应的枚举值。设置其他字符串则为无效,保持默认字体粗细显示。
属性方法要紧随组件,通过“.”操作符连接,也可以通过链式调用的方式配置组件的多个属性。
```ts
// xxx.ets
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Tomato')
.fontSize(26)
.fontWeight(500)
}
.width('100%')
.height('100%')
}
}
```
![zh-cn_image_0000001168888224](figures/zh-cn_image_0000001168888224.png)
## 组件成员变量初始化
自定义组件的成员变量可以通过[本地初始化](../quick-start/arkts-restrictions-and-extensions.md#自定义组件成员变量初始化的方式与约束)[在构造组件时通过构造参数初始化](../quick-start/arkts-restrictions-and-extensions.md#自定义组件成员变量初始化的方式与约束)两种方式实现,具体允许哪种方式取决于该变量所使用的装饰器:
**示例:**
```ts
// xxx.ets
class ClassA {
public str: string
constructor(str: string) {
this.str = str
}
}
@Entry
@Component
struct Parent {
// Parent的变量parentState进行本地初始化
@State parentState: ClassA = new ClassA('hello')
build() {
Column() {
Row() {
CompA({ aState: new ClassA('Tomato'), aLink: $parentState })
}
// aState在CompA中已进行初始化,因此可以缺省
Row() {
CompA({ aLink: $parentState })
}
}.width('100%')
}
}
@Component
struct CompA {
// CompA中的变量aState进行本地初始化,aLink在Parent中使用时通过构造参数初始化
@State aState: any = new ClassA('CompA')
@Link aLink: ClassA
build() {
Row() {
Text(JSON.stringify(this.aState)).fontSize(20).margin(30)
Text(JSON.stringify(this.aLink)).fontSize(20).margin(30)
}
}
}
```
![component](figures/component.PNG)
## 组件的状态更新
组件的状态可以通过动态修改组件成员变量的值来更新,下面以示例来进行说明。
**示例:**
```ts
// xxx.ets
@Entry
@Component
struct ParentComp {
@State isCountDown: boolean = true
build() {
Column() {
Text(this.isCountDown ? 'Count Down' : 'Stopwatch').fontSize(20).margin(20)
if (this.isCountDown) {
// 图片资源放在media目录下
Image($r("app.media.countdown")).width(120).height(120)
TimerComponent({ counter: 10, changePerSec: -1, showInColor: Color.Red })
} else {
// 图片资源放在media目录下
Image($r("app.media.stopwatch")).width(120).height(120)
TimerComponent({ counter: 0, changePerSec: +1, showInColor: Color.Black })
}
Button(this.isCountDown ? 'Switch to Stopwatch' : 'Switch to Count Down')
.onClick(() => {
this.isCountDown = !this.isCountDown
})
}.width('100%')
}
}
// 自定义计时器/倒计时组件
@Component
struct TimerComponent {
@State counter: number = 0
private changePerSec: number = -1
private showInColor: Color = Color.Black
private timerId: number = -1
build() {
Text(`${this.counter}sec`)
.fontColor(this.showInColor)
.fontSize(20)
.margin(20)
}
aboutToAppear() {
this.timerId = setInterval(() => {
this.counter += this.changePerSec
}, 1000)
}
aboutToDisappear() {
if (this.timerId > 0) {
clearTimeout(this.timerId)
this.timerId = -1
}
}
}
```
![component](figures/component.gif)
**初始创建和渲染:**
1. 创建父组件ParentComp;
2. 本地初始化ParentComp的状态变量isCountDown;
3. 执行ParentComp的build函数;
4. 创建Column组件;
1. 创建Text组件,设置其文本展示内容,并将Text组件实例添加到Column中;
2. 判断if条件,创建true条件下的元素;
1. 创建Image组件,并设置其图片源地址;
2. 使用给定的构造函数创建TimerComponent;
1. 创建TimerComponent对象;
2. 本地初始化成员变量初始值;
3. 使用TimerComponent构造函数提供的参数更新成员变量的值;
4. 执行TimerComponent的aboutToAppear函数;
5. 执行TimerComponent的build函数,创建相应的UI描述结构;
3. 创建Button内置组件,设置相应的内容。
**状态更新:**
用户单击按钮时:
1. ParentComp的isCountDown状态变量的值更改为false;
2. 执行ParentComp的build函数;
3. Column组件被重用并执行重新初始化;
4. Column的子组件会重用内存中的对象,但会进行重新初始化;
1. Text组件会被重用,但使用新的文本内容进行重新初始化;
2. 判断if条件,使用false条件下的元素;
1. 原来true条件的组件不再使用,将这些组件销毁;
1. 销毁Image组件实例;
2. 销毁TimerComponent组件实例,aboutToDisappear函数被调用;
2. 创建false条件下的组件;
1. 创建Image组件,并设置其图片源地址;
2. 使用给定的构造函数重新创建TimerComponent;
3. 初始化TimerComponent,并调用aboutToAppear函数和build函数。
3. 重用Button组件,使用新的图片源地址。
# 绘制图形
绘制能力主要是通过框架提供的绘制组件来支撑,支持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
# 弹性布局
弹性布局(Flex布局)是自适应布局中使用最为灵活的布局。弹性布局提供一种更加有效的方式来对容器中的子组件进行排列、对齐和分配空白空间。弹性布局
Flex组件用于创建弹性布局,开发者可以通过Flex的接口创建容器组件,进而对容器内的其他元素进行弹性布局,例如:使三个元素在容器内水平居中,垂直等间隔分散。
- 容器: [Flex组件](../reference/arkui-ts/ts-container-flex.md)作为Flex布局的容器,用于设置布局相关属性。
- 子组件: Flex组件内的子组件自动成为布局的子组件。
- 主轴: 水平方向的轴线,子组件默认沿着主轴排列。主轴开始的位置称为主轴起始端,结束位置称为主轴终点端。
- 交叉轴: 垂直方向的轴线。交叉始的位置称为主轴首部,结束位置称为交叉轴尾部。
## 创建弹性布局
![](figures/flex.png)
接口的调用形式如下:
## 容器组件属性
`Flex(options?: { direction?: FlexDirection, wrap?: FlexWrap, justifyContent?: FlexAlign, alignItems?: ItemAlign, alignContent?: FlexAlign })`
通过参数direction定义弹性布局的布局方向,justifyContent定义弹性布局方向上的子组件对齐方式, alignContent定义与布局方向垂直的方向上的子组件对齐方式,wrap定义内容超过一行时是否换行。
## 弹性布局方向
通过Flex组件提供的Flex接口创建弹性布局。如下:
弹性布局有两个方向,子组件放置的方向是主轴,与主轴垂直的方向是交叉轴。通过direction参数设置容器主轴的方向,可选值有:
`Flex(options?: { direction?: FlexDirection, wrap?: FlexWrap, justifyContent?: FlexAlign, alignItems?: ItemAlign, alignContent?: FlexAlign })`
- FlexDirection.Row(默认值):主轴为水平方向,子组件从起始端沿着水平方向开始排布。
```ts
Flex({ direction: FlexDirection.Row }) {
Text('1').width('33%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(50).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218579606](figures/zh-cn_image_0000001218579606.png)
### 弹性布局方向
参数direction决定主轴的方向,即子组件的排列方向。可选值有:
- FlexDirection.RowReverse:主轴为水平方向,子组件从终点端沿着FlexDirection.Row相反的方向开始排布。
![](figures/direction.png)
```ts
Flex({ direction: FlexDirection.RowReverse }) {
Text('1').width('33%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(50).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
- FlexDirection.Row(默认值):主轴为水平方向,子组件从起始端沿着水平方向开始排布。
![zh-cn_image_0000001218739566](figures/zh-cn_image_0000001218739566.png)
```ts
Flex({ direction: FlexDirection.Row }) {
Text('1').width('33%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(50).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218579606](figures/zh-cn_image_0000001218579606.png)
- FlexDirection.RowReverse:主轴为水平方向,子组件从终点端沿着FlexDirection. Row相反的方向开始排布。
```ts
Flex({ direction: FlexDirection.RowReverse }) {
Text('1').width('33%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(50).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218739566](figures/zh-cn_image_0000001218739566.png)
- FlexDirection.Column:主轴为垂直方向,子组件从起始端沿着垂直方向开始排布。
```ts
Flex({ direction: FlexDirection.Column }) {
Text('1').width('100%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('100%').height(50).backgroundColor(0xD2B48C)
Text('3').width('100%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263019457](figures/zh-cn_image_0000001263019457.png)
- FlexDirection.ColumnReverse:主轴为垂直方向,子组件从终点端沿着FlexDirection.Column相反的方向开始排布。
```ts
Flex({ direction: FlexDirection.ColumnReverse }) {
Text('1').width('100%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('100%').height(50).backgroundColor(0xD2B48C)
Text('3').width('100%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263339459](figures/zh-cn_image_0000001263339459.png)
## 弹性布局换行
默认情况下,子组件在Flex容器中都排在一条线(又称"轴线")上。通过wrap参数设置其他换行方式,可选值有:
- FlexWrap.NoWrap : 不换行。如果子元素的宽度总和大于父元素的宽度,则子元素会被压缩宽度。
```ts
Flex({ wrap: FlexWrap.NoWrap }) {
Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
Text('3').width('50%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263139409](figures/zh-cn_image_0000001263139409.png)
- FlexWrap.Wrap:换行。
```ts
Flex({ wrap: FlexWrap.Wrap }) {
Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
Text('3').width('50%').height(50).backgroundColor(0xD2B48C)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218419614](figures/zh-cn_image_0000001218419614.png)
- FlexWrap.WrapReverse:换行,且与行排列方向相反。
```ts
Flex({ wrap: FlexWrap.WrapReverse}) {
Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
Text('3').width('50%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263259399](figures/zh-cn_image_0000001263259399.png)
## 弹性布局对齐方式
### 主轴对齐
可以通过justifyContent参数设置在主轴的对齐方式,可选值有:
- FlexAlign.Start: 元素在主轴方向首端对齐, 第一个元素与行首对齐,同时后续的元素与前一个对齐。
```ts
Flex({ justifyContent: FlexAlign.Start }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218259634](figures/zh-cn_image_0000001218259634.png)
- FlexAlign.Center: 元素在主轴方向中心对齐,第一个元素与行首的距离与最后一个元素与行尾距离相同。
```ts
Flex({ justifyContent: FlexAlign.Center }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218579608](figures/zh-cn_image_0000001218579608.png)
- FlexAlign.End: 元素在主轴方向尾部对齐, 最后一个元素与行尾对齐,其他元素与后一个对齐。
```ts
Flex({ justifyContent: FlexAlign.End }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218739568](figures/zh-cn_image_0000001218739568.png)
- FlexAlign.SpaceBetween: Flex主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素与行首对齐,最后一个元素与行尾对齐。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263019461](figures/zh-cn_image_0000001263019461.png)
- FlexAlign.SpaceAround: Flex主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。
```ts
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263339461](figures/zh-cn_image_0000001263339461.png)
- FlexAlign.SpaceEvenly: Flex主轴方向元素等间距布局, 相邻元素之间的间距、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。
```ts
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263139411](figures/zh-cn_image_0000001263139411.png)
### 交叉轴对齐
可以通过alignItems参数设置在交叉轴的对齐方式,可选值有:
```ts
Flex({ direction: FlexDirection.Column }) {
Text('1').width('100%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('100%').height(50).backgroundColor(0xD2B48C)
Text('3').width('100%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263019457](figures/zh-cn_image_0000001263019457.png)
- FlexDirection.ColumnReverse:主轴为垂直方向,子组件从终点端沿着FlexDirection. Column相反的方向开始排布。
```ts
Flex({ direction: FlexDirection.ColumnReverse }) {
Text('1').width('100%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('100%').height(50).backgroundColor(0xD2B48C)
Text('3').width('100%').height(50).backgroundColor(0xF5DEB3)
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263339459](figures/zh-cn_image_0000001263339459.png)
### 弹性布局换行
默认情况下,子组件在Flex容器中都排在一条线(又称"轴线")上。通过wrap参数设置子组件换行方式。可选值有:
- FlexWrap. NoWrap(默认值): 不换行。如果子组件的宽度总和大于父元素的宽度,则子组件会被压缩宽度。
```ts
Flex({ wrap: FlexWrap.NoWrap }) {
Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
Text('3').width('50%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263139409](figures/zh-cn_image_0000001263139409.png)
- FlexWrap. Wrap:换行,每一行子组件按照主轴方向排列。
```ts
Flex({ wrap: FlexWrap.Wrap }) {
Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
Text('3').width('50%').height(50).backgroundColor(0xD2B48C)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218419614](figures/zh-cn_image_0000001218419614.png)
- FlexWrap. WrapReverse:换行,每一行子组件按照主轴反方向排列。
```ts
Flex({ wrap: FlexWrap.WrapReverse}) {
Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
Text('3').width('50%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263259399](figures/zh-cn_image_0000001263259399.png)
### 弹性布局对齐方式
#### 主轴对齐
通过justifyContent参数设置在主轴方向的对齐方式,存在下面六种情况:
![](figures/justifyContent.png)
- FlexAlign.Start(默认值): 子组件在主轴方向起始端对齐, 第一个子组件与父元素边沿对齐,其他元素与前一个元素对齐。
```ts
Flex({ justifyContent: FlexAlign.Start }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218259634](figures/mainStart.png)
- FlexAlign.Center: 子组件在主轴方向居中对齐。
```ts
Flex({ justifyContent: FlexAlign.Center }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218579608](figures/mainCenter.png)
- FlexAlign.End: 子组件在主轴方向终点端对齐, 最后一个子组件与父元素边沿对齐,其他元素与后一个元素对齐。
```ts
Flex({ justifyContent: FlexAlign.End }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218739568](figures/mainEnd.png)
- FlexAlign.SpaceBetween: Flex主轴方向均匀分配弹性元素,相邻子组件之间距离相同。第一个子组件和最后一个子组件与父元素边沿对齐。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263019461](figures/mainSpacebetween.png)
- FlexAlign.SpaceAround: Flex主轴方向均匀分配弹性元素,相邻子组件之间距离相同。第一个子组件到主轴起始端的距离和最后一个子组件到主轴终点端的距离是相邻元素之间距离的一半。
```ts
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263339461](figures/mainSpacearound.png)
- FlexAlign.SpaceEvenly: Flex主轴方向元素等间距布局,相邻子组件之间的间距、第一个子组件与主轴起始端的间距、最后一个子组件到主轴终点端的间距均相等。
```ts
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
Text('2').width('20%').height(50).backgroundColor(0xD2B48C)
Text('3').width('20%').height(50).backgroundColor(0xF5DEB3)
}
.width('90%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263139411](figures/mainSpaceevenly.png)
#### 交叉轴对齐
容器和子组件都可以设置交叉轴对齐方式,且子组件设置的对齐方式优先级较高。
##### 容器组件设置交叉轴对齐
可以通过Flex组件的alignItems参数设置子组件在交叉轴的对齐方式,可选值有:
- ItemAlign.Auto: 使用Flex容器中默认配置。
```ts
Flex({ alignItems: ItemAlign.Auto }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
```ts
Flex({ alignItems: ItemAlign.Auto }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218419616](figures/zh-cn_image_0000001218419616.png)
![zh-cn_image_0000001218419616](figures/zh-cn_image_0000001218419616.png)
- ItemAlign.Start: 交叉轴方向首部对齐。
```ts
Flex({ alignItems: ItemAlign.Start }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263259401](figures/zh-cn_image_0000001263259401.png)
```ts
Flex({ alignItems: ItemAlign.Start }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263259401](figures/zh-cn_image_0000001263259401.png)
- ItemAlign.Center: 交叉轴方向居中对齐。
```ts
Flex({ alignItems: ItemAlign.Center }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218259636](figures/zh-cn_image_0000001218259636.png)
```ts
Flex({ alignItems: ItemAlign.Center }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218259636](figures/zh-cn_image_0000001218259636.png)
- ItemAlign.End:交叉轴方向底部对齐。
```ts
Flex({ alignItems: ItemAlign.End }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218579610](figures/zh-cn_image_0000001218579610.png)
```ts
Flex({ alignItems: ItemAlign.End }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218579610](figures/zh-cn_image_0000001218579610.png)
- ItemAlign.Stretch:交叉轴方向拉伸填充,在未设置尺寸时,拉伸到容器尺寸。
```ts
Flex({ alignItems: ItemAlign.Stretch }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218739570](figures/zh-cn_image_0000001218739570.png)
```ts
Flex({ alignItems: ItemAlign.Stretch }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001218739570](figures/zh-cn_image_0000001218739570.png)
- ItemAlign. Baseline:交叉轴方向文本基线对齐。
```ts
Flex({ alignItems: ItemAlign.Baseline }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
```
![zh-cn_image_0000001263019463](figures/zh-cn_image_0000001263019463.png)
##### 子组件设置交叉轴对齐
子组件的alignSelf属性也可以设置子组件在父容器交叉轴的对齐格式,且会覆盖Flex布局容器中alignItems默认配置。如下例所示:
- ItemAlign.Baseline:交叉轴方向文本基线对齐。
```ts
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { //容器组件设置子组件居中
Text('alignSelf Start').width('25%').height(80)
.alignSelf(ItemAlign.Start)
.backgroundColor(0xF5DEB3)
Text('alignSelf Baseline')
.alignSelf(ItemAlign.Baseline)
.width('25%')
.height(80)
.backgroundColor(0xD2B48C)
Text('alignSelf Baseline').width('25%').height(100)
.backgroundColor(0xF5DEB3)
.alignSelf(ItemAlign.Baseline)
Text('no alignSelf').width('25%').height(100)
.backgroundColor(0xD2B48C)
Text('no alignSelf').width('25%').height(100)
.backgroundColor(0xF5DEB3)
}.width('90%').height(220).backgroundColor(0xAFEEEE)
```
![](figures/alignself.png)
上例中,Flex容器中alignItems设置交叉轴子组件的对齐方式为居中,子组件自身设置了alignSelf属性的情况,覆盖父组件的alignItem值,表现为alignSelf的定义。
#### 内容对齐
可以通过alignContent参数设置子组件各行在交叉轴剩余空间内的对齐方式,只在多行的flex布局中生效,可选值有:
- FlexAlign.Start: 子组件各行与交叉轴起点对齐。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.Start }) {
Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
}
.width('90%')
.height(100)
.backgroundColor(0xAFEEEE)
```
![crossStart.png](figures/crossStart.png)
- FlexAlign.Center: 子组件各行在交叉轴方向居中对齐。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.Center }) {
Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
}
.width('90%')
.height(100)
.backgroundColor(0xAFEEEE)
```
![crossCenter.png](figures/crossCenter.png)
- FlexAlign.End: 子组件各行与交叉轴终点对齐。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.End }) {
Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
}
.width('90%')
.height(100)
.backgroundColor(0xAFEEEE)
```
![crossEnd.png](figures/crossEnd.png)
- FlexAlign.SpaceBetween: 子组件各行与交叉轴两端对齐,各行间垂直间距平均分布。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.SpaceBetween }) {
Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
}
.width('90%')
.height(100)
.backgroundColor(0xAFEEEE)
```
![crossSpacebetween.png](figures/crossSpacebetween.png)
- FlexAlign.SpaceAround: 子组件各行间距相等,是元素首尾行与交叉轴两端距离的两倍。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.SpaceAround }) {
Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
}
.width('90%')
.height(100)
.backgroundColor(0xAFEEEE)
```
![crossSpacearound.png](figures/crossSpacearound.png)
- FlexAlign.SpaceEvenly: 子组件各行间距,子组件首尾行与交叉轴两端距离都相等。
```ts
Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.SpaceAround }) {
Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
}
.width('90%')
.height(100)
.backgroundColor(0xAFEEEE)
```
![crossSpaceevenly.png](figures/crossSpaceevenly.png)
### 弹性布局的自适应拉伸
在弹性布局父组件尺寸不够大的时候,通过子组件的下面几个属性设置其再父容器的占比,达到自适应布局能力。
- flexBasis: 设置子组件在父容器主轴方向上的基准尺寸。如果设置了该值,则子项占用的空间为设置的值;如果没设置或者为auto,那子项的空间为width/height的值。
```ts
Flex({ alignItems: ItemAlign.Baseline }) {
Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)
Text('2').width('33%').height(40).backgroundColor(0xD2B48C)
Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
}
.size({width: '90%', height: 80})
.padding(10)
.backgroundColor(0xAFEEEE)
Flex() {
Text('flexBasis("auto")')
.flexBasis('auto') // 未设置width以及flexBasis值为auto,内容自身宽松
.height(100)
.backgroundColor(0xF5DEB3)
Text('flexBasis("auto")'+' width("40%")')
.width('40%')
.flexBasis('auto') //设置width以及flexBasis值auto,使用width的值
.height(100)
.backgroundColor(0xD2B48C)
Text('flexBasis(100)') // 未设置width以及flexBasis值为100,宽度为100vp
.flexBasis(100)
.height(100)
.backgroundColor(0xF5DEB3)
Text('flexBasis(100)')
.flexBasis(100)
.width(200) // flexBasis值为100,覆盖width的设置值,宽度为100vp
.height(100)
.backgroundColor(0xD2B48C)
}.width('90%').height(120).padding(10).backgroundColor(0xAFEEEE)
```
![](figures/flexbasis.png)
![zh-cn_image_0000001263019463](figures/zh-cn_image_0000001263019463.png)
- flexGrow: 设置父容器的剩余空间分配给此属性所在组件的比例。用于"瓜分"父组件的剩余空间。
### 内容对齐
可以通过alignContent参数设置在换行组件的行在交叉轴剩余空间内的对齐方式,可选值有:
- FlexAlign.Start: 左对齐。
- FlexAlign.Center: 居中对齐。
- FlexAlign.End: 右对齐。
- FlexAlign.SpaceBetween: flex items之间的距离相等,与main start、main end两端对齐。
```ts
Flex() {
Text('flexGrow(1)')
.flexGrow(2)
.width(100)
.height(100)
.backgroundColor(0xF5DEB3)
Text('flexGrow(3)')
.flexGrow(2)
.width(100)
.height(100)
.backgroundColor(0xD2B48C)
Text('no flexGrow')
.width(100)
.height(100)
.backgroundColor(0xF5DEB3)
}.width(400).height(120).padding(10).backgroundColor(0xAFEEEE)
```
![](figures/flexgrow.png)
- FlexAlign.SpaceAround: flex items之间的距离相等,flex items与main start、main end之间的距离等于flex items之间距离的一半。
上图中,父容器宽度400vp, 三个子组件原始宽度为100vp,综合300vp,剩余空间100vp根据flexGrow值的占比分配给子组件,未设置flexGrow的子组件不参与“瓜分”。
第一个元素以及第二个元素以2:3分配剩下的100vp。第一个元素为100vp+100vp*2/5=140vp,第二个元素为100vp+100vp*3/5=160vp。
- FlexAlign.SpaceEvenly: flex items之间的距离相等,flex items与main start、main end之间的距离等于flex items之间的距离
- flexShrink: 当父容器空间不足时,子组件的压缩比例
```ts
Flex({ direction: FlexDirection.Row }) {
Text('flexShrink(3)')
.flexShrink(3)
.width(200)
.height(100)
.backgroundColor(0xF5DEB3)
Text('no flexShrink')
.width(200)
.height(100)
.backgroundColor(0xD2B48C)
Text('flexShrink(2)')
.flexShrink(2)
.width(200)
.height(100)
.backgroundColor(0xF5DEB3)
}.width(400).height(120).padding(10).backgroundColor(0xAFEEEE)
```
![](figures/flexshrink.png)
## 场景示例
可使用弹性布局做出子元素排列方式为水平方向排列,且子元素的总宽度超出父元素的宽度不换行,子元素在水平方向两端对齐,中间间距由除首尾外的子元素平分,竖直方向上子元素居中的效果。
使用弹性布局,可以实现子组件沿水平方向排列,两端对齐,子组件间距平分,竖直方向上子组件居中的效果。示例如下:
```ts
@Entry
@Entry
@Component
struct FlexExample {
build() {
......@@ -358,7 +567,6 @@ struct FlexExample {
}
.height(70)
.width('90%')
.padding(10)
.backgroundColor(0xAFEEEE)
}.width('100%').margin({ top: 5 })
}.width('100%')
......@@ -366,9 +574,7 @@ struct FlexExample {
}
```
![zh-cn_image_0000001261605867](figures/zh-cn_image_0000001261605867.png)
![zh-cn_image_0000001261605867](figures/flexExample.png)
## 相关实例
......
# 栅格布局
栅格组件[GridRow](../reference/arkui-ts/ts-container-gridrow.md)[GridCol](../reference/arkui-ts/ts-container-gridcol.md)
相对于[GridContainer](../reference/arkui-ts/ts-container-gridcontainer.md)提供了更灵活、更全面的栅格系统实现方案。GridRow为栅格容器组件,只与栅格子组件GridCol在栅格布局场景中使用。
## 栅格容器GridRow
栅格容器有columns、gutter、direction、breakpoints四个属性。
- columns: 栅格布局的主要定位工具,设置栅格布局的总列数。
- gutter: 设置元素之间的距离,决定内容间的紧密程度。
- direction: 设置栅格子组件在栅格容器中的排列方向。
- breakpoints:以设备宽度为基准,将应用宽度分成了几个不同的区间,即不同的断点。开发者可根据需要在不同的区间下实现不同的页面布局效果。
首先通过设置断点,得到一系列断点区间;然后,借助栅格组件能力监听应用窗口大小的变化,判断应用当前处于哪个断点区间,最后调整应用的布局。
### 栅格系统断点
断点以设备宽度为基准,将应用宽度分成了几个不同的区间,即不同的断点。开发者根据需求在不同的区间实现不同的页面布局效果。
[栅格系统默认断点](ui-ts-layout-grid-container.md#系统栅格断点)将设备宽度分为xs、sm、md、lg四类,尺寸范围如下:
| 断点名称 | 取值范围(vp)|
| --------| ------ |
| xs | [0, 320) |
| sm | [320, 520) |
| md | [520, 840) |
| lg | [840, +∞) |
在GridRow新栅格组件中,允许开发者使用breakpoints自定义修改断点的取值范围,最多支持6个断点,除了默认的四个断点外,
还可以启用xl,xxl两个断点,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备的布局设置。
| 断点名称 | 设备描述 |
| ----- | ---------------------------------------- |
| xs | 最小宽度类型设备。 |
| sm | 小宽度类型设备。 |
| md | 中等宽度类型设备。 |
| lg | 大宽度类型设备。 |
| xl | 特大宽度类型设备。 |
| xxl | 超大宽度类型设备。 |
- 针对断点位置,开发者根据实际使用场景,通过一个单调递增数组设置。由于breakpoints最多支持六个断点,单调递增数组长度最大为5。
```ts
breakpoints: {value: ["100vp", "200vp"]}
```
表示启用xs、sm、md共3个断点,小于100vp为xs,100vp-200vp为sm,大于200vp为md。
```ts
breakpoints: {value: ["320vp", "520vp", "840vp", "1080vp"]}
```
表示启用xs、sm、md、lg、xl共5个断点,小于320vp为xs,320vp-520vp为sm,520vp-840vp为md,840vp-1080vp为lg,大于1080vp为xl。
- 栅格系统通过监听窗口或容器的尺寸变化进行断点,通过reference设置断点切换参考物。 考虑到应用可能以非全屏窗口的形式显示,以应用窗口宽度为参照物更为通用。
下例中,使用栅格的默认列数12列,通过断点设置将应用宽度分成六个区间,在各区间中,每个栅格子元素占用的列数均不同。效果如图:
```ts
GridRow({
breakpoints: {
value: ['200vp', '300vp', '400vp', '500vp', '600vp'],
reference: BreakpointsReference.WindowSize
}
}) {
ForEach(this.bgColors, (color, index) => {
GridCol({
span: {
xs: 2,
sm: 3,
md: 4,
lg: 6,
xl: 8,
xxl: 12
}
}) {
Row() {
Text(${index})
}.width("100%").height("50vp")
}.backgroundColor(color)
})
}
```
![](figures/breakpoints.gif)
### 栅格布局的总列数
GridRow中通过columns设置栅格布局的总列数。
- columns默认值为12,当未设置columns时,在任何断点下,栅格布局被分成12列。
```ts
GridRow() {
ForEach(this.bgColors, (item, index) => {
GridCol() {
Row() {
Text(`${index + 1}`)
}.width("100%").height("50")
}.backgroundColor(item)
})
}
```
![](figures/columns1.png)
- 当columns类型为number时,栅格布局在任何尺寸设备下都被分为columns列。下面分别设置栅格布局列数为4和8,子元素默认占一列,效果如下:
```ts
Row() {
GridRow({ columns: 4 }) {
ForEach(this.bgColors, (item, index) => {
GridCol() {
Row() {
Text(`${index + 1}`)
}.width("100%").height("50")
}.backgroundColor(item)
})
}
.width("100%").height("100%")
.onBreakpointChange((breakpoint) => {
this.currentBp = breakpoint
})
}
.height(160)
.border({ color: Color.Blue, width: 2 })
.width('90%')
Row() {
GridRow({ columns: 8 }) {
ForEach(this.bgColors, (item, index) => {
GridCol() {
Row() {
Text(`${index + 1}`)
}.width("100%").height("50")
}.backgroundColor(item)
})
}
.width("100%").height("100%")
.onBreakpointChange((breakpoint) => {
this.currentBp = breakpoint
})
}
.height(160)
.border({ color: Color.Blue, width: 2 })
.width('90%')
```
![](figures/columns2.png)
- 当columns类型为GridRowColumnOption时,支持下面六种不同尺寸(xs, sm, md, lg, xl, xxl)设备的总列数设置,各个尺寸下数值可不同。
```ts
GridRow({ columns: { sm: 4, md: 8 }, breakpoints: { value: ['200vp', '300vp', '400vp', '500vp', '600vp'] } }) {
ForEach(this.bgColors, (item, index) => {
GridCol() {
Row() {
Text(`${index + 1}`)
}.width("100%").height("50")
}.backgroundColor(item)
})
}
```
![](figures/columns3.gif)
如上,若只设置sm, md的栅格总列数,则较小的尺寸使用默认columns值12,较大的尺寸使用前一个尺寸的columns。这里只设置sm:8, md:10,则较小尺寸的xs:12,较大尺寸的参照md的设置,lg:10, xl:10, xxl:10。
### 栅格子组件间距
GridRow中通过gutter设置子元素在水平和垂直方向的间距。
- 当gutter类型为number时,同时设置栅格子组件间水平和垂直方向边距且相等。下例中,设置子组件水平与垂直方向距离相邻元素的间距为10。
```ts
GridRow({ gutter: 10 }){}
```
![](figures/gutter1.png)
- 当gutter类型为GutterOption时,单独设置栅格子组件水平垂直边距,x属性为水平方向间距,y为垂直方向间距。
```ts
GridRow({ gutter: { x: 20, y: 50 } }){}
```
![](figures/gutter2.png)
### 排列方向
通过GridRow的direction属性设置栅格子组件在栅格容器中的排列方向。
- 子组件默认从左往右排列。
```ts
GridRow({ direction: GridRowDirection.Row }){}
```
![](figures/direction1.png)
- 子组件从右往左排列。
```ts
GridRow({ direction: GridRowDirection.RowReverse }){}
```
![](figures/direction2.png)
## 栅格子组件GridCol
GridCol组件作为GridRow组件的子组件,通过给GridCol传参或者设置属性两种方式,设置span,offset,order的值。
- span的设置
```ts
GridCol({ span: 2 }){}
GridCol({ span: { xs: 1, sm: 2, md: 3, lg: 4 } }){}
GridCol(){}.span(2)
GridCol(){}.span({ xs: 1, sm: 2, md: 3, lg: 4 })
```
- offset的设置
```ts
GridCol({ offset: 2 }){}
GridCol({ offset: { xs: 2, sm: 2, md: 2, lg: 2 } }){}
GridCol(){}.offset(2)
GridCol(){}.offset({ xs: 1, sm: 2, md: 3, lg: 4 })
```
- order的设置
```ts
GridCol({ order: 2 }){}
GridCol({ order: { xs: 1, sm: 2, md: 3, lg: 4 } }){}
GridCol(){}.order(2)
GridCol(){}.order({ xs: 1, sm: 2, md: 3, lg: 4 })
```
下面使用传参的方式演示各属性的使用。
### span
子组件占栅格布局的列数,决定了子组件的宽度,默认为1。
- 当类型为number时,子组件在所有尺寸设备下占用的列数相同。
```ts
GridRow({ columns: 8 }) {
ForEach(this.bgColors, (color, index) => {
GridCol({ span: 2 }) {
Row() {
Text(${index})
}.width("100%").height("50vp")
}
.backgroundColor(color)
})
}
```
![](figures/span1.png)
- 当类型为GridColColumnOption时,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备中子组件所占列数设置,各个尺寸下数值可不同。
```ts
GridRow({ columns: 8 }) {
ForEach(this.bgColors, (color, index) => {
GridCol({ span: { xs: 1, sm: 2, md: 3, lg: 4 } }) {
Row() {
Text(${index})
}.width("100%").height("50vp")
}
.backgroundColor(color)
})
}
```
![](figures/span2.gif)
### offset
栅格子组件相对于前一个子组件的偏移列数,默认为0。
- 当类型为number时,子组件偏移相同列数。
```ts
GridRow() {
ForEach(this.bgColors, (color, index) => {
GridCol({ offset: 2 }) {
Row() {
Text("" + index)
}.width("100%").height("50vp")
}
.backgroundColor(color)
})
}
```
![](figures/offset1.png)
栅格默认分成12列,每一个子组件默认占1列,偏移2列,每个子组件及间距共占3列,一行放四个子组件。
- 当类型为GridColColumnOption时,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备中子组件所占列数设置,各个尺寸下数值可不同。
```ts
GridRow() {
ForEach(this.bgColors, (color, index) => {
GridCol({ offset: { xs: 1, sm: 2, md: 3, lg: 4 } }) {
Row() {
Text("" + index)
}.width("100%").height("50vp")
}
.backgroundColor(color)
})
}
```
![](figures/offset2.gif)
### order
栅格子组件的序号,决定子组件排列次序。当子组件不设置order或者设置相同的order, 子组件按照代码顺序展示。当子组件设置不同的order时,order较大的组件在前,较小的在后。
当子组件部分设置order,部分不设置order时,未设置order的子组件依次排序靠前,设置了order的子组件按照数值从大到小排列。
- 当类型为number时,子组件在任何尺寸下排序次序一致。
```ts
GridRow() {
GridCol({ order: 5 }) {
Row() {
Text("1")
}.width("100%").height("50vp")
}.backgroundColor(Color.Red)
GridCol({ order: 4 }) {
Row() {
Text("2")
}.width("100%").height("50vp")
}.backgroundColor(Color.Orange)
GridCol({ order: 3 }) {
Row() {
Text("3")
}.width("100%").height("50vp")
}.backgroundColor(Color.Yellow)
GridCol({ order: 2 }) {
Row() {
Text("4")
}.width("100%").height("50vp")
}.backgroundColor(Color.Green)
}
```
![](figures/order1.png)
- 当类型为GridColColumnOption时,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备中子组件排序次序设置。
```ts
GridRow() {
GridCol({ order: { xs:1, sm:5, md:3, lg:7}}) {
Row() {
Text("1")
}.width("100%").height("50vp")
}.backgroundColor(Color.Red)
GridCol({ order: { xs:2, sm:2, md:6, lg:1} }) {
Row() {
Text("2")
}.width("100%").height("50vp")
}.backgroundColor(Color.Orange)
GridCol({ order: { xs:3, sm:3, md:1, lg:6} }) {
Row() {
Text("3")
}.width("100%").height("50vp")
}.backgroundColor(Color.Yellow)
GridCol({ order: { xs:4, sm:4, md:2, lg:5} }) {
Row() {
Text("4")
}.width("100%").height("50vp")
}.backgroundColor(Color.Green)
}
```
![](figures/order2.gif)
# 网格布局
网格布局(GridLayout)是自适应布局中一种重要的布局,具备较强的页面均分能力,子组件占比控制能力。
通过[Grid](../reference/arkui-ts/ts-container-grid.md)容器组件和子组件[GridItem](../reference/arkui-ts/ts-container-griditem.md)实现,
Grid用于设置网格布局相关参数,GridItem定义子组件相关特征。优势如下:
1. 容器组件尺寸发生变化时,所有子组件以及间距等比例调整,实现布局的自适应能力。
2. 支持自定义网格布局行数和列数,以及每行每列尺寸占比。
3. 支持设置网格布局中子组件的行列间距。
4. 支持设置子组件横跨几行或者几列。
## 容器组件Grid设置
### 行列数量占比
通过Grid的组件的columnsTemplate和rowTemplate属性设置网格布局行列数量与尺寸占比。
下面以columnsTemplate为例,介绍该属性的设置,该属性值是一个由多个空格和'数字+fr'间隔拼接的字符串,fr的个数即网格布局的列数,fr前面的数值大小,用于计算该列在网格布局宽度上的占比,最终决定该列的宽度。
```ts
struct GridExample {
@State Number: Array<string> = ['1', '2', '3', '4']
build() {
Column({ space: 5 }) {
Grid() {
ForEach(this.Number, (num: string) => {
GridItem() {
Text(`列${num}`)
.fontSize(16)
.textAlign(TextAlign.Center)
.backgroundColor(0xd0d0d0)
.width('100%')
.height('100%')
.borderRadius(5)
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr')
.columnsGap(10)
.rowsGap(20)
.width('90%')
.backgroundColor(0xF0F0F0)
.height(100)
}.width('100%')
}
}
```
定义了四个等分的列,每列宽度相等。
```ts
struct GridExample {
@State Number: Array<string> = ['1', '2', '3', '4']
build() {
Column({ space: 5 }) {
Grid() {
ForEach(this.Number, (num: string) => {
GridItem() {
Text(`列${num}`)
.fontSize(16)
.textAlign(TextAlign.Center)
.backgroundColor(0xd0d0d0)
.width('100%')
.height('100%')
.borderRadius(5)
}
})
}
.columnsTemplate('1fr 2fr 3fr 4fr')
.rowsTemplate('1fr')
.columnsGap(10)
.rowsGap(20)
.width('90%')
.backgroundColor(0xF0F0F0)
.height(100)
}.width('100%')
}
}
```
定义了四列,每列宽度比值为1:2:3:4。
```ts
struct GridExample {
@State Number: Array<string> = ['1', '2', '3']
build() {
Column({ space: 5 }) {
Grid() {
ForEach(this.Number, (num: string) => {
GridItem() {
Text(`列${num}`)
.fontSize(16)
.textAlign(TextAlign.Center)
.backgroundColor(0xd0d0d0)
.width('100%')
.height('100%')
.borderRadius(5)
}
})
}
.columnsTemplate('4fr 2fr 3fr')
.rowsTemplate('1fr')
.columnsGap(10)
.rowsGap(20)
.width('90%')
.backgroundColor(0xF0F0F0)
.height(100)
}.width('100%')
}
}
```
定义了三列,每列宽度比值为4:2:3。
效果如下:
![](figures/columnTemplate.png)
### 排列方式
通过layoutDirection可以设置网格布局的主轴方向,决定子组件的排列方式。
可选值包括Row,RowReverse, Column, ColumnReverse四种情况。
效果如下:
![](figures/gridlayout.png)
### 行列间距
columnsGap用于设置网格子组件GridItem垂直方向的间距,rowsGap用于设置GridItem水平方向的间距。
```ts
Grid()
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(20)
```
![](figures/columnGap.png)
上图中,设置网格布局子组件间的垂直间距为20,水平间距为10。
## 网格子组件GridItem设置
### 设置子组件占的行列数
网格布局的行列标号从1开始,依次编号。
子组件横跨多行时,通过rowStart设置子组件起始行编号,rowEnd设置终点行编号。当rowStart值与rowEnd值相同时,子组件只占一个网格。示例如下:
```ts
Grid() {
GridItem() {
Text('5')
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.rowStart(2).rowEnd(3) // 5子组件从第二列到第三列
GridItem() {
Text('4')
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.columnStart(4).columnEnd(5) // 4从第四列到第五列
GridItem() {
Text('6')
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.columnStart(2).columnEnd(4) // 6从第二列到第四列
GridItem() {
Text('9')
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.columnStart(3).columnEnd(4) // 从第三列到第四列
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(20)
.width('90%')
.backgroundColor(0xF0F0F0)
.height('200vp')
.layoutDirection(GridDirection.Column)
```
![](figures/griditem.png)
## 场景示例
使用grid布局实现一个计算器的排布效果,代码如下:
```ts
@Entry
@Component
struct GridExample {
@State Number: Array<string> = ['1', '2', '3', '+', '4', '5', '6', '-', '7', '8', '9', '*', '0', '.', '/']
@Styles textStyle(){
.backgroundColor(0xd0d0d0)
.width('100%')
.height('100%')
.borderRadius(5)
}
build() {
Column({ space: 5 }) {
Grid() {
GridItem() {
Text('0')
.fontSize(30)
.textStyle()
}.columnStart(1).columnEnd(4)
GridItem() {
Text('清空')
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.columnStart(1).columnEnd(2)
GridItem() {
Text('回退')
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.columnStart(3).columnEnd(4)
ForEach(this.Number, (day: string) => {
if (day === '0') {
GridItem() {
Text(day)
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}.columnStart(1).columnEnd(2)
} else {
GridItem() {
Text(day)
.fontSize(16)
.textAlign(TextAlign.Center)
.textStyle()
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('2fr 1fr 1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(15)
.width('90%')
.backgroundColor(0xF0F0F0)
.height('70%')
}.width('100%').margin({ top: 5 })
}
}
```
在大屏设备上展示效果如下:
![](figures/gridExp1.png)
在小屏设备下展示效果如下:
![](figures/gridExp2.png)
# 线性布局
线性布局(LinearLayout)是开发中最常用的布局。线性布局的子组件在线性方向上(水平方向和垂直方向)依次排列。
通过线性容器[Row](../reference/arkui-ts/ts-container-row.md)[Column](../reference/arkui-ts/ts-container-column.md)实现线性布局。Column容器内子组件按照垂直方向排列,Row组件中,子组件按照水平方向排列。
## 线性布局的排列
线性布局的排列方向由所选容器组件决定。根据不同的排列方向,选择使用Row或Column容器创建线性布局,通过调整space,alignItems,justifyContent属性调整子组件的间距,水平垂直方向的对齐方式。
1. 通过space参数设置主轴(排列方向)上子组件的间距。达到各子组件在排列方向上的等间距效果。
2. 通过alignItems属性设置子组件在交叉轴(排列方向的垂直方向)的对齐方式。且在各类尺寸屏幕中,表现一致。其中,交叉轴为垂直方向时,取值为[VerticalAlign类型](../reference/arkui-ts/ts-appendix-enums.md#verticalalign),水平方向取值为[HorizontalAlign类型](../reference/arkui-ts/ts-appendix-enums.md#horizontalalign)
3. 通过justifyContent属性设置子组件在主轴(排列方向)上的对齐方式。实现布局的自适应均分能力。取值为[FlexAlign类型](../reference/arkui-ts/ts-appendix-enums.md#flexalign)
具体使用以及效果如下表所示:
|属性名|描述|Row效果图|Column效果图|
|------|---------------------------|----------------------------|---------------------------|
|space |- 横向布局中各子组件的在水平方向的间距<br> - 纵向布局中个子组件垂直方向间距| ![](figures/rowspace.png) | ![](figures/columnspace.png) |
|alignItems |容器排列方向的垂直方向上,子组件在父容器中的对齐方式|![](figures/rowalign.png) |![](figures/columnalign.png)|
|justifyContent |容器排列方向上,子组件在父容器中的对齐方式 |![](figures/rowjustify.png) |![](figures/columnjustify.png)|
## 自适应拉伸
在线性布局下,常用空白填充组件[Blank](../reference/arkui-ts/ts-basic-components-blank.md),在容器主轴方向自动填充空白空间,达到自适应拉伸效果。
```ts
@Entry
@Component
struct BlankExample {
build() {
Column() {
Row() {
Text('Bluetooth').fontSize(18)
Blank()
Toggle({ type: ToggleType.Switch, isOn: true })
}.backgroundColor(0xFFFFFF).borderRadius(15).padding({ left: 12 }).width('100%')
}.backgroundColor(0xEFEFEF).padding(20).width('100%')
}
}
```
![](figures/blank.gif)
## 自适应缩放
自适应缩放是指在各种不同大小设备中,子组件按照预设的比例,尺寸随容器尺寸的变化而变化。在线性布局中有下列方法实现:
1. 父容器尺寸确定时,设置了layoutWeight属性的子组件与兄弟元素占主轴尺寸按照权重进行分配,忽略元素本身尺寸设置,在任意尺寸设备下,自适应占满剩余空间。
```ts
@Entry
@Component
struct layoutWeightExample {
build() {
Column() {
Text('1:2:3').width('100%')
Row() {
Column() {
Text('layoutWeight(1)')
.textAlign(TextAlign.Center)
}.layoutWeight(2).backgroundColor(0xffd306).height('100%')
Column() {
Text('layoutWeight(2)')
.textAlign(TextAlign.Center)
}.layoutWeight(4).backgroundColor(0xffed97).height('100%')
Column() {
Text('layoutWeight(6)')
.textAlign(TextAlign.Center)
}.layoutWeight(6).backgroundColor(0xffd306).height('100%')
}.backgroundColor(0xffd306).height('30%')
Text('2:5:3').width('100%')
Row() {
Column() {
Text('layoutWeight(2)')
.textAlign(TextAlign.Center)
}.layoutWeight(2).backgroundColor(0xffd306).height('100%')
Column() {
Text('layoutWeight(5)')
.textAlign(TextAlign.Center)
}.layoutWeight(5).backgroundColor(0xffed97).height('100%')
Column() {
Text('layoutWeight(3)')
.textAlign(TextAlign.Center)
}.layoutWeight(3).backgroundColor(0xffd306).height('100%')
}.backgroundColor(0xffd306).height('30%')
}
}
}
```
![](figures/layoutWeight.gif)
3. 父容器尺寸确定时,使用百分比设置子组件以及兄弟组件的width宽度,可以保证各自元素在任意尺寸下的自适应占比。
```ts
@Entry
@Component
struct WidthExample {
build() {
Column() {
Row() {
Column() {
Text('left width 20%')
.textAlign(TextAlign.Center)
}.width('20%').backgroundColor(0xffd306).height('100%')
Column() {
Text('center width 50%')
.textAlign(TextAlign.Center)
}.width('50%').backgroundColor(0xffed97).height('100%')
Column() {
Text('right width 30%')
.textAlign(TextAlign.Center)
}.width('30%').backgroundColor(0xffd306).height('100%')
}.backgroundColor(0xffd306).height('30%')
}
}
}
```
![](figures/width.gif)
上例中,在任意大小的设备中,子组件的宽度占比固定。
## 定位能力
- 相对定位
使用组件的[offset属性](../reference/arkui-ts/ts-universal-attributes-location.md)可以实现相对定位,设置元素相对于自身的偏移量。设置该属性,不影响父容器布局,仅在绘制时进行位置调整。使用线性布局和offset可以实现大部分布局的开发。
```ts
@Entry
@Component
struct OffsetExample {
@Styles eleStyle() {
.size({ width: 120, height: '50' })
.backgroundColor(0xbbb2cb)
.border({ width: 1 })
}
build() {
Column({ space: 20 }) {
Row() {
Text('1').size({ width: '15%', height: '50' }).backgroundColor(0xdeb887).border({ width: 1 }).fontSize(16)
Text('2 offset(15, 30)')
.eleStyle()
.fontSize(16)
.align(Alignment.Start)
.offset({ x: 15, y: 30 })
Text('3').size({ width: '15%', height: '50' }).backgroundColor(0xdeb887).border({ width: 1 }).fontSize(16)
Text('4 offset(-10%, 20%)')
.eleStyle()
.fontSize(16)
.offset({ x: '-5%', y: '20%' })
}.width('90%').height(150).border({ width: 1, style: BorderStyle.Dashed })
}
.width('100%')
.margin({ top: 25 })
}
}
```
![](figures/offset.gif)
- 绝对定位
线性布局中可以使用组件的[positon属性](../reference/arkui-ts/ts-universal-attributes-location.md)实现绝对布局(AbsoluteLayout),设置元素左上角相对于父容器左上角偏移位置。对于不同尺寸的设备,使用绝对定位的适应性会比较差,在屏幕的适配上有缺陷。
```ts
@Entry
@Component
struct PositionExample {
@Styles eleStyle(){
.backgroundColor(0xbbb2cb)
.border({ width: 1 })
.size({ width: 120, height: 50 })
}
build() {
Column({ space: 20 }) {
// 设置子组件左上角相对于父组件左上角的偏移位置
Row() {
Text('position(30, 10)')
.eleStyle()
.fontSize(16)
.position({ x: 10, y: 10 })
Text('position(50%, 70%)')
.eleStyle()
.fontSize(16)
.position({ x: '50%', y: '70%' })
Text('position(10%, 90%)')
.eleStyle()
.fontSize(16)
.position({ x: '10%', y: '80%' })
}.width('90%').height('100%').border({ width: 1, style: BorderStyle.Dashed })
}
.width('90%').margin(25)
}
}
```
![](figures/position.gif)
## 自适应延伸
自适应延伸是在不同尺寸设备下,当页面显示内容个数不一并延伸到屏幕外时,可通过滚动条拖动展示。适用于线性布局中内容无法一屏展示的场景。常见以下两类实现方法。
- List组件
List子项过多一屏放不下时,未展示的子项通过滚动条拖动显示。通过scrollBar属性设置滚动条的常驻状态,edgeEffect属性设置拖动到极限的回弹效果。
纵向List:
```ts
@Entry
@Component
struct ListExample1 {
@State arr: string[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"]
@State alignListItem: ListItemAlign = ListItemAlign.Start
build() {
Column() {
List({ space: 20, initialIndex: 0 }) {
ForEach(this.arr, (item) => {
ListItem() {
Text('' + item)
.width('100%')
.height(100)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
.border({ width: 2, color: Color.Green })
}, item => item)
}
.border({ width: 2, color: Color.Red, style: BorderStyle.Dashed })
.scrollBar(BarState.On) // 滚动条常驻
.edgeEffect(EdgeEffect.Spring) // 滚动到边缘再拖动回弹效果
}.width('100%').height('100%').backgroundColor(0xDCDCDC).padding(20)
}
}
```
![](figures/listcolumn.gif)
横向List:
```ts
@Entry
@Component
struct ListExample2 {
@State arr: string[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"]
@State alignListItem: ListItemAlign = ListItemAlign.Start
build() {
Column() {
List({ space: 20, initialIndex: 0 }) {
ForEach(this.arr, (item) => {
ListItem() {
Text('' + item)
.height('100%')
.width(100)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
.border({ width: 2, color: Color.Green })
}, item => item)
}
.border({ width: 2, color: Color.Red, style: BorderStyle.Dashed })
.scrollBar(BarState.On) // 滚动条常驻
.edgeEffect(EdgeEffect.Spring) // 滚动到边缘再拖动回弹效果
.listDirection(Axis.Horizontal) // 列表水平排列
}.width('100%').height('100%').backgroundColor(0xDCDCDC).padding(20)
}
}
```
![](figures/listrow.gif)
- Scroll组件
线性布局中,当子组件的布局尺寸超过父组件的尺寸时,内容可以滚动。在Column或者Row外层包裹一个可滚动的容器组件Scroll实现。
纵向Scroll:
```ts
@Entry
@Component
struct ScrollExample {
scroller: Scroller = new Scroller();
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
build() {
Scroll(this.scroller) {
Column() {
ForEach(this.arr, (item) => {
Text(item.toString())
.width('90%')
.height(150)
.backgroundColor(0xFFFFFF)
.borderRadius(15)
.fontSize(16)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
}, item => item)
}.width('100%')
}
.backgroundColor(0xDCDCDC)
.scrollable(ScrollDirection.Vertical) // 滚动方向纵向
.scrollBar(BarState.On) // 滚动条常驻显示
.scrollBarColor(Color.Gray) // 滚动条颜色
.scrollBarWidth(30) // 滚动条宽度
.edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹
}
}
```
![](figures/scrollcolumn.gif)
横向Scroll:
```ts
@Entry
@Component
struct ScrollExample {
scroller: Scroller = new Scroller();
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
build() {
Scroll(this.scroller) {
Row() {
ForEach(this.arr, (item) => {
Text(item.toString())
.height('90%')
.width(150)
.backgroundColor(0xFFFFFF)
.borderRadius(15)
.fontSize(16)
.textAlign(TextAlign.Center)
.margin({ left: 10 })
}, item => item)
}.height('100%')
}
.backgroundColor(0xDCDCDC)
.scrollable(ScrollDirection.Horizontal) // 滚动方向横向
.scrollBar(BarState.On) // 滚动条常驻显示
.scrollBarColor(Color.Gray) // 滚动条颜色
.scrollBarWidth(30) // 滚动条宽度
.edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹
}
}
```
![](figures/scrollrow.gif)
# 层叠布局
层叠布局(StackLayout)用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。
通过层叠容器[Stack](../reference/arkui-ts/ts-container-stack.md)实现,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素显示。
## 对齐方式
设置子元素在容器内的对齐方式。支持左上,上中,右上,左,中,右,右下,中下,右下九种对齐方式,如下表所示:
|名称| 描述| 图示 |
|---| ---|---|
|TopStart| 顶部起始端 |![](figures/stacktopstart.png)|
Top |顶部横向居中 |![](figures/stacktop.png)|
TopEnd| 顶部尾端 |![](figures/stacktopend.png)|
Start| 起始端纵向居中 |![](figures/stackstart.png)|
Center| 横向和纵向居中 |![](figures/stackcenter.png)|
End| 尾端纵向居中 |![](figures/stackend.png)|
BottomStart |底部起始端 |![](figures/stackbottomstart.png)|
Bottom| 底部横向居中 |![](figures/stackbottom.png)|
BottomEnd| 底部尾端 |![](figures/stackbottomend.png)|
## Z序控制
Stack容器中兄弟组件显示层级关系可以通过[zIndex](../reference/arkui-ts/ts-universal-attributes-z-order.md)
属性改变。zIndex值越大,显示层级越高,即zIndex值大的组件会覆盖在zIndex值小的组件上方。
- 在层叠布局中,如果后面子元素尺寸大于前面子元素尺寸,则前面子元素完全隐藏。
```ts
Stack({ alignContent: Alignment.BottomStart }) {
Column() {
Text('Stack子元素1').textAlign(TextAlign.End).fontSize(20)
}.width(100).height(100).backgroundColor(0xffd306)
Column() {
Text('Stack子元素2').fontSize(20)
}.width(150).height(150).backgroundColor(Color.Pink)
Column() {
Text('Stack子元素3').fontSize(20)
}.width(200).height(200).backgroundColor(Color.Grey)
}.margin({ top: 100 }).width(350).height(350).backgroundColor(0xe0e0e0)
```
![](figures/stack2.png)
上图中,最后的子元素3的尺寸大于前面的所有子元素,所以,前面两个元素完全隐藏。改变子元素1,子元素2的zIndex属性后,可以将元素展示出来。
```ts
Stack({ alignContent: Alignment.BottomStart }) {
Column() {
Text('Stack子元素1').textAlign(TextAlign.End).fontSize(20)
}.width(100).height(100).backgroundColor(0xffd306).zIndex(2)
Column() {
Text('Stack子元素2').fontSize(20)
}.width(150).height(150).backgroundColor(Color.Pink).zIndex(1)
Column() {
Text('Stack子元素3').fontSize(20)
}.width(200).height(200).backgroundColor(Color.Grey)
}.margin({ top: 100 }).width(350).height(350).backgroundColor(0xe0e0e0)
```
![](figures/stack1.png)
# 性能提升的推荐方法
开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。
## 推荐使用数据懒加载
开发者在使用长列表时,如果直接采用循环渲染方式,如下所示,会一次性加载所有的列表元素,一方面会导致页面启动时间过长,影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负担。
```ts
@Entry
@Component
struct MyComponent {
@State arr: number[] = Array.from(Array(100), (v,k) =>k); //构造0-99的数组
build() {
List() {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`item value: ${item}`)
}
}, (item: number) => item.toString())
}
}
}
```
上述代码会在页面加载时将100个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示:
```ts
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2']
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data)
this.notifyDataAdd(index)
}
public pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List() {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(20).margin({ left: 10 })
}
}
.onClick(() => {
this.data.pushData('item value: ' + this.data.totalCount())
})
}, item => item)
}
}
}
```
![LazyForEach1](figures/LazyForEach1.gif)
上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。
## 使用条件渲染替代显隐控制
如下所示,开发者在使用visibility通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。
```ts
@Entry
@Component
struct MyComponent {
@State isVisible: Visibility = Visibility.Visible;
build() {
Column() {
Button("显隐切换")
.onClick(() => {
if (this.isVisible == Visibility.Visible) {
this.isVisible = Visibility.None
} else {
this.isVisible = Visibility.Visible
}
})
Row().visibility(this.isVisible)
.width(300).height(300).backgroundColor(Color.Pink)
}.width('100%')
}
}
```
要避免这一问题,可使用if条件渲染代替visibility属性变换,如下所示:
```ts
@Entry
@Component
struct MyComponent {
@State isVisible: boolean = true;
build() {
Column() {
Button("显隐切换")
.onClick(() => {
this.isVisible = !this.isVisible
})
if (this.isVisible) {
Row()
.width(300).height(300).backgroundColor(Color.Pink)
}
}.width('100%')
}
}
```
![isVisible](figures/isVisible.gif)
## 使用Column/Row替代Flex
由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。
```ts
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column }) {
Flex().width(300).height(200).backgroundColor(Color.Pink)
Flex().width(300).height(200).backgroundColor(Color.Yellow)
Flex().width(300).height(200).backgroundColor(Color.Grey)
}
}
}
```
上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。
```ts
@Entry
@Component
struct MyComponent {
build() {
Column() {
Row().width(300).height(200).backgroundColor(Color.Pink)
Row().width(300).height(200).backgroundColor(Color.Yellow)
Row().width(300).height(200).backgroundColor(Color.Grey)
}
}
}
```
![flex1](figures/flex1.PNG)
## 设置List组件的宽高
开发者在使用Scroll容器组件嵌套List子组件时,若不指定List的宽高尺寸,则默认全部加载,如下所示:
```ts
@Entry
@Component
struct MyComponent {
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
build() {
Scroll() {
List() {
ForEach(this.arr, (item) => {
ListItem() {
Text(`item value: ${item}`).fontSize(30).margin({ left: 10 })
}.height(100)
}, (item) => item.toString())
}
}.backgroundColor(Color.Pink)
}
}
```
因此,在这种场景下建议开发者设置List子组件的宽高,如下所示:
```ts
@Entry
@Component
struct MyComponent {
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
build() {
Scroll() {
List() {
ForEach(this.arr, (item) => {
ListItem() {
Text(`item value: ${item}`).fontSize(30).margin({ left: 10 })
}.height(100)
}, (item) => item.toString())
}.width('100%').height(500)
}.backgroundColor(Color.Pink)
}
}
```
![list1](figures/list1.gif)
## 减少应用滑动白块
应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。
如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。
如下是使用cachedCount参数的例子:
```ts
@Entry
@Component
struct MyComponent {
private source: MyDataSource = new MyDataSource();
build() {
List() {
LazyForEach(this.source, item => {
ListItem() {
Text("Hello" + item)
.fontSize(50)
.onAppear(() => {
console.log("appear:" + item)
})
}
})
}.cachedCount(3) // 扩大数值appear日志范围会变大
}
}
class MyDataSource implements IDataSource {
data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
public totalCount(): number {
return this.data.length
}
public getData(index: number): any {
return this.data[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
}
unregisterDataChangeListener(listener: DataChangeListener): void {
}
}
```
![list2](figures/list2.gif)
**使用说明:**
cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册