系列文章目录
文章目录
前言
ArkTS是HarmonyOS主力开发语言,基于TypeScript扩展:
- 支持声明式UI、自定义组件,提供状态管理和渲染控制。
- 继承并扩展了TypeScript的所有特性,使其成为TS的超集。
- 未来将持续增强并行、系统类型和分布式开发等能力。
具备TypeScript基础会更容易上手ArkTS。
Typescript官方网址
一、基本语法概述
1. 基本组成如下所示。
图2 ArkTS的基本组成
2. 扩展语法范式
- @Builder/@BuilderParam:封装和复用UI描述的特殊方法。
- @Extend/@Styles:扩展内置组件和封装属性样式。
- stateStyles:根据组件内部状态变化设置不同样式。
二、声明式UI描述
配置事件
事件方法以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。
使用箭头函数配置组件的事件方法
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
使用声明的箭头函数
可以直接调用,不需要bind(this)
。
fn = () => {
console.info(`counter: ${this.counter}`)
this.counter++
}
// ...
Button('add counter')
.onClick(this.fn)
三、创建自定义组件
在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。
自定义组件具有以下特点:
- 可组合:允许开发者组合使用系统组件、及其属性和方法。
- 可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
- 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。
自定义组件的基本用法
自定义组件的基本结构
struct
:自定义组件基于struct
实现,struct + 自定义组件名 + {...}
的组合构成自定义组件,不能有继承关系。对于struct
的实例化,可以省略new
。- 说明:自定义组件名、类名、函数名不能和系统组件名相同。
@Component({ freezeWhenInactive: true })
struct MyComponent {
}
从API version 11开始,@Component可以接受一个可选的bool类型参数。
build()函数
build()
函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()
函数。
@Entry
@Entry
装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry
装饰一个自定义组件。@Entry
可以接受一个可选的LocalStorage
的参数。
从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
名称 | 类型 | 必填 | 说明 |
---|---|---|---|
routeName | string | 否 | 表示作为命名路由页面的名字。 |
storage | LocalStorage | 否 | 页面级的UI状态存储。 |
useSharedStorage12+ | boolean | 否 | 是否使用LocalStorage.getShared()接口返回的LocalStorage实例对象,默认值false。 |
@Reusable
@Reusable装饰的自定义组件具备可复用能力。
@Reusable
@Component
struct MyComponent {
}
成员函数/变量
@Entry
@Component
struct Parent {
@State cnt: number = 0
submit: () => void = () => {
this.cnt++;
}
build() {
Column() {
Text(`${this.cnt}`)
Son({ submitArrow: this.submit })
}
}
}
@Component
struct Son {
submitArrow?: () => void
build() {
Row() {
Button('add')
.width(80)
.onClick(() => {
if (this.submitArrow) {
this.submitArrow()
}
})
}
.height(56)
}
}
build()函数
-
所有声明在
build()
函数的语言,我们统称为UI描述,UI描述需要遵循以下规则: -
@Entry装饰的自定义组件:
- 其
build()
函数下的根节点必须是唯一的,并且必须是容器组件。 ForEach
禁止作为根节点。
- 其
-
@Component装饰的自定义组件:
- 其
build()
函数下的根节点也是唯一且必要的,但可以是非容器组件。 - 同样,
ForEach
也禁止作为根节点。
- 其
组件示例
@Entry
@Component
struct MyComponent {
build() {
// 根节点唯一且必要,必须为容器组件
Row() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
build() {
// 根节点唯一且必要,可为非容器组件
Image('test.jpg')
}
}
构建函数中的限制
- 不允许声明本地变量:在
build()
函数内部,不允许声明任何本地变量。
build() {
// 反例:不允许声明本地变量
let a: number = 1;
}
- 禁止使用
console.info
:在UI描述中,直接使用console.info
是不被允许的,但可以在方法或函数内部使用。
build() {
// 反例:不允许console.info
console.info('print debug log');
}
- 不允许创建本地作用域:在
build()
函数内部,不应该使用花括号{}
来创建本地作用域。
build() {
// 反例:不允许本地作用域
{
...
}
}
- 不允许调用没有用@Builder装饰的方法:在
build()
函数中,只能调用用@Builder
装饰的方法或系统组件的参数为TS方法返回值的情况。
@Component
struct ParentComponent {
doSomeCalculations() {
}
calcTextValue(): string {
return 'Hello World';
}
@Builder doSomeRender() {
Text(`Hello World`)
}
build() {
Column() {
// 反例:不能调用没有用@Builder装饰的方法
this.doSomeCalculations();
// 正例:可以调用
this.doSomeRender();
// 正例:参数可以为调用TS方法的返回值
Text(this.calcTextValue())
}
}
}
- 禁止使用
switch
语法:在build()
函数中,不允许使用switch
语法进行条件判断,应使用if
语句代替。
build() {
Column() {
// 反例:不允许使用switch语法
switch (expression) {
case 1:
Text('...')
break;
case 2:
Image('...')
break;
default:
Text('...')
break;
}
}
}
- 禁止使用表达式:在
build()
函数中,不应使用条件表达式。
build() {
Column() {
// 反例:不允许使用表达式
(this.aVar > 10) ? Text('...') : Image('...')
}
}
- 不允许直接改变状态变量,反例如下
@Component
struct MyComponent {
@State textColor: Color = Color.Yellow;
@State columnColor: Color = Color.Green;
@State count: number = 1;
build() {
Column() {
// 应避免直接在Text组件内改变count的值
Text(`${this.count++}`)
.width(50)
.height(50)
.fontColor(this.textColor)
.onClick(() => {
this.columnColor = Color.Red;
})
Button("change textColor").onClick(() =>{
this.textColor = Color.Pink;
})
}
.backgroundColor(this.columnColor)
}
}
最小化更新(API9-至今版本): 当 this.columnColor 更改时,只有Column组件会更新,Text组件不会更改。 只当 this.textColor 更改时,会去更新整个Text组件,其所有属性函数都会执行,所以会看到Text(${this.count++})自增。因为目前UI以组件为单位进行更新,如果组件上某一个属性发生改变,会更新整体的组件。所以整体的更新链路是:this.textColor = Color.Pink -> Text组件整体更新->this.count++ ->Text组件整体更新。值得注意的是,这种写法在初次渲染时会导致Text组件渲染两次,从而对性能产生影响。
自定义组件通用样式
自定义组件可以通过链式调用的形式设置通用样式。例如:
@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
}
@Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}
说明:在ArkUI中,当给自定义组件设置样式时,实际上是给组件外部套了一个不可见的容器组件,样式是设置在这个容器组件上的,而非直接设置给组件内部的元素。因此,在上面的例子中,背景颜色红色实际上会应用到MyComponent2
组件所在的容器上,而不是直接应用到Button
组件上。
四、自定义组件与页面生命周期
- 自定义组件:使用
@Component
装饰的UI单元,可以组合多个系统组件实现UI的复用,并可以调用组件的生命周期。 - 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,
@Entry
装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry
。只有被@Entry
装饰的组件才可以调用页面的生命周期。
页面生命周期
被@Entry
装饰的组件(页面)生命周期提供以下接口:
onPageShow
:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。onPageHide
:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。onBackPress
:当用户点击返回按钮时触发。
组件生命周期
一般用@Component
装饰的自定义组件的生命周期提供以下接口:
aboutToAppear
:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()
函数之前执行。- onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现
aboutToDisappear
:在自定义组件析构销毁之前执行。不允许在aboutToDisappear
函数中改变状态变量,特别是@Link
变量的修改可能会导致应用程序行为不稳定。
生命周期流程
生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。
根据上面的描述,我们可以从自定义组件的初始创建、重新渲染和删除来详细解释生命周期的流程。
自定义组件的删除
如果if组件的分支改变,或者ForEach
循环渲染中数组的个数改变,组件将被删除:
- 在删除组件之前,将调用其
aboutToDisappear
生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。 - 自定义组件和它的变量将被删除,如果其有同步的变量,比如
@Link
、@Prop
、@StorageLink
,将从同步源上取消注册。 - 不建议在生命周期
aboutToDisappear
内使用async await
,如果在生命周期的aboutToDisappear
使用异步操作(Promise
或者回调方法),自定义组件将被保留在Promise
的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。
示例
以下示例展示了生命周期的调用时机:
// Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
@State btnColor: string = "#FF007DFF";
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageShow() {
console.info('Index onPageShow');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageHide() {
console.info('Index onPageHide');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress() {
console.info('Index onBackPress');
this.btnColor = "#FFEE0606";
return true; // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
}
// 组件生命周期
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// 组件生命周期
onDidBuild() {
console.info('MyComponent onDidBuild');
}
// 组件生命周期
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// this.showChild为true,创建Child子组件,执行Child aboutToAppear
if (this.showChild) {
Child()
}
Button('delete Child')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(() => {
// 更改this.showChild为false,删除Child子组件,执行Child aboutToDisappear
this.showChild = false;
})
// push到Page页面,执行onPageHide
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/Page' });
})
}
}
}
@Component
struct Child {
@State title: string = 'Hello World';
// 组件生命周期
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear');
}
// 组件生命周期
onDidBuild() {
console.info('[lifeCycle] Child onDidBuild');
}
// 组件生命周期
aboutToAppear() {
console.info('[lifeCycle] Child aboutToAppear');
}
build() {
Text(this.title)
.fontSize(50)
.margin(20)
.onClick(() => {
this.title = 'Hello ArkUI';
})
}
}
// Page.ets
@Entry
@Component
struct Page {
@State textColor: Color = Color.Black;
@State num: number = 0;
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageShow() {
this.num = 5;
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageHide() {
console.log("Page onPageHide");
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress() { // 不设置返回值按照false处理
this.textColor = Color.Grey;
this.num = 0;
}
// 组件生命周期
aboutToAppear() {
this.textColor = Color.Blue;
}
build() {
Column() {
Text(`num 的值为:${this.num}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(this.textColor)
.margin(20)
.onClick(() => {
this.num += 5;
})
}
.width('100%')
}
}
页面和组件生命周期说明
生命周期函数
- 只有使用
@Entry
装饰的节点(如MyComponent
)才能使页面级别的生命周期方法生效。 MyComponent
和其子组件Child
均声明了组件级别的生命周期函数。
应用冷启动初始化流程
MyComponent aboutToAppear
MyComponent build
- MyComponent onDidBuild
Child aboutToAppear
Child build
- Child onDidBuild
Index onPageShow
删除子组件
点击“delete Child”时,由于 if
绑定的 this.showChild
变成 false
,Child
组件会被删除。此时会执行 Child aboutToDisappear
方法。
跳转到新页面
- 点击“push to next page”时,调用
router.pushUrl
接口,跳转到另一个页面。 - 当前
Index
页面隐藏,执行Index onPageHide
。 - 由于使用的是
router.pushUrl
,Index
页面并未销毁,只是隐藏。 - 跳转到新页面后,会执行新页面的初始化生命周期流程。
替换当前页面
- 如果调用的是
router.replaceUrl
,则当前Index
页面会被销毁。 - 执行的生命周期流程为:
Index onPageHide
-->MyComponent aboutToDisappear
-->Child aboutToDisappear
。 - 之后执行初始化新页面的生命周期流程。
返回按钮
- 点击返回按钮,触发
Index onBackPress
生命周期方法。 - 触发返回操作会导致当前
Index
页面被销毁。
应用最小化或进入后台
- 最小化应用或应用进入后台时,触发
Index onPageHide
。 - 由于
Index
页面并未被销毁,不会执行组件的aboutToDisappear
方法。 - 应用回到前台时,执行
Index onPageShow
。
退出应用
- 退出应用时,执行
Index onPageHide
-->MyComponent aboutToDisappear
-->Child aboutToDisappear
生命周期流程。
自定义组件的自定义布局
如果需要通过测算的方式布局自定义组件内子组件的位置,建议使用以下接口:
onMeasureSize:组件每次布局时触发,计算子组件的尺寸,其执行时间先于onPlaceChildren。
onPlaceChildren:组件每次布局时触发,设置子组件的起始位置。
自定义组件成员属性访问限定符使用限制
当组件开发者不希望状态变量被外部初始化时,可以添加private限定符,提醒组件调用方不要初始化该状态变量。但是外部初始化也需要遵循装饰器自身的规则,具体规则见使用限制。
从API version 12开始,支持自定义组件成员属性访问限定符使用限制的规则。
五、@Builder装饰器:自定义构建函数
更轻量的UI元素复用机制@Builder
,@Builder
所装饰的函数遵循build()
函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build
方法里调用。
为了简化语言,我们将@Builder
装饰的函数也称为“自定义构建函数”。
装饰器使用说明
@Builder装饰器有两种使用方式,分别是定义在自定义组件内部的私有自定义构建函数和定义在全局的全局自定义构建函数。
自定义组件内自定义构建函数
@Entry
@Component
struct BuilderDemo {
@Builder
showTextBuilder() {
Text('Hello World')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
@Builder
showTextValueBuilder(param: string) {
Text(param)
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
build() {
Column() {
// 无参数
this.showTextBuilder()
// 有参数
this.showTextValueBuilder('Hello @Builder')
}
}
}
全局自定义构建函数
@Builder
function showTextBuilder() {
Text('Hello World')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
@Entry
@Component
struct BuilderDemo {
build() {
Column() {
showTextBuilder()
}
}
}
全局自定义构建函数允许在build方法和其他自定义构建函数中调用。
如果不涉及组件状态变化,建议使用全局的自定义构建方法。
参数传递规则
自定义构建函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:
- 参数的类型必须与参数声明的类型一致,不允许
undefined
、null
和返回undefined
、null
的表达式。 - 在@Builder修饰的函数内部,不允许改变参数值。
@Builder
内UI语法遵循UI语法规则。- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
- 调用@Builder装饰的函数默认按值传递
按引用传递参数
按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。
class Tmp {
paramA1: string = '';
}
@Builder function overBuilder(params: Tmp) {
Row() {
Text(`UseStateVarByReference: ${params.paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
// 在父组件中调用overBuilder组件时,
// 把this.label通过引用传递的方式传给overBuilder组件。
overBuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
// 单击Click me后,UI文本从Hello更改为ArkUI。
this.label = 'ArkUI';
})
}
}
}
按值传递参数
调用@Builder
装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder
方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。
@Builder function overBuilder(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
overBuilder(this.label)
}
}
}
六、@BuilderParam装饰器:引用@Builder函数
在前面的章节中,我们学习了如何创建一个自定义组件。这个自定义组件的UI结构是固定的,主要用于与使用方进行数据传递。但是,ArkUI还为我们提供了另一种更为轻量级的UI元素复用机制,即@Builder装饰器。
使用@Builder装饰的函数遵循build()函数的语法规则。开发者可以将重复使用的UI元素抽象成一个方法,并在build方法中调用它。为了简化描述,我们将@Builder装饰的函数也称为“自定义构建函数”。
当开发者创建了自定义组件并希望为其添加特定功能时,例如在组件中添加一个点击跳转操作,直接在组件内嵌入事件方法会导致所有引入该组件的地方都增加这个功能。为解决此问题,ArkUI引入了@BuilderParam装饰器。@BuilderParam用于装饰指向@Builder方法的变量,允许开发者在初始化自定义组件时对此属性进行赋值,从而为组件增加特定的功能。这个装饰器类似于slot占位符,用于声明任意UI描述的一个元素。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
装饰器使用说明
初始化@BuilderParam装饰的方法
@BuilderParam装饰的方法只能被自定义构建函数(即@Builder装饰的方法)初始化。
使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化@BuilderParam。
@Builder function overBuilder() {}
@Component
struct Child {
@Builder doNothingBuilder() {};
// 使用自定义组件的自定义构建函数初始化@BuilderParam
@BuilderParam customBuilderParam: () => void = this.doNothingBuilder;
// 使用全局自定义构建函数初始化@BuilderParam
@BuilderParam customOverBuilderParam: () => void = overBuilder;
build(){}
}
用父组件自定义构建函数初始化子组件@BuilderParam装饰的方法
@Component
struct Child {
// 使用父组件@Builder装饰的方法初始化子组件@BuilderParam
@BuilderParam customBuilderParam: () => void;
build() {
Column() {
this.customBuilderParam()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(`Parent builder `)
}
build() {
Column() {
Child({ customBuilderParam: this.componentBuilder })
}
}
}
注意:请确保this
的指向是正确的。
在以下示例中,Parent
组件在调用this.componentBuilder()
时,this
指向其所属组件,即“Parent”。@Builder componentBuilder()
传给子组件@BuilderParam customBuilderParam
,在Child
组件中调用this.customBuilderParam()
时,this
指向在Child
的label,即“Child”。
开发者需谨慎使用bind
来改变函数调用的上下文,因为这可能会使this
的指向变得混乱。
@Component
struct Child {
label: string = `Child`
@BuilderParam customBuilderParam: () => void;
build() {
Column() {
this.customBuilderParam()
}
}
}
@Entry
@Component
struct Parent {
label: string = `Parent`
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ customBuilderParam: this.componentBuilder })
}
}
}
使用场景
参数初始化组件
@BuilderParam装饰的方法可以是有参数和无参数的两种形式,需与指向的@Builder方法类型匹配。
@Builder function overBuilder($$ : {label: string }) {
Text($$.label)
.width(400)
.height(50)
.backgroundColor(Color.Green)
}
@Component
struct Child {
label: string = 'Child'
// 无参数类型,指向的componentBuilder也是无参数类型
@BuilderParam customBuilderParam: () => void;
// 有参数类型,指向的GlobalBuilder1也是有参数类型的方法
@BuilderParam customOverBuilderParam: ($$ : { label : string}) => void;
build() {
Column() {
this.customBuilderParam()
this.customOverBuilderParam({label: 'global Builder label' } )
}
七、@Styles装饰器:定义组件重用样式
在ArkUI中,为了简化代码和提高可维护性,我们引入了@Styles装饰器,它允许开发者将重复的样式设置提炼成一个方法,并在组件声明的位置直接调用。通过这种方式,我们可以快速定义并复用自定义样式。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
装饰器使用说明
- 当前@Styles仅支持通用属性和通用事件。
- @Styles方法不支持参数。例如,以下是一个反例:
// 反例: @Styles不支持参数
@Styles function globalFancy (value: number) {
.width(value)
}
- @Styles可以定义在组件内或全局。在全局定义时,需在方法名前面添加
function
关键字;在组件内定义时,则不需要添加function
关键字。 - @Styles定义的样式只能在当前文件内使用,不支持
export
。
示例
全局定义
// 全局
@Styles function functionName() {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
组件内定义
@Component
struct FancyUse {
@State heightValue: number = 100
// 定义在组件内的@Styles封装的样式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
build() {
Column({ space: 10 }) {
Text('FancyA')
.globalFancy()
.fontSize(30)
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
组件内@Styles的优先级
组件内@Styles的优先级高于全局@Styles。框架会优先查找当前组件内的@Styles,如果找不到,则会全局查找。
使用场景
以下示例中演示了组件内@Styles和全局@Styles的用法:
// 定义在全局的@Styles封装的样式
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@State heightValue: number = 100
// 定义在组件内的@Styles封装的样式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
build() {
Column({ space: 10 }) {
// 使用全局的@Styles封装的样式
Text('FancyA')
.globalFancy()
.fontSize(30)
// 使用组件内的@Styles封装的样式
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
在这个示例中,我们定义了一个全局的@Styles globalFancy
和一个组件内的@Styles fancy
。在build
方法中,我们分别在两个Text
组件上使用了这两个@Styles。由于组件内的@Styles优先级更高,因此FancyB
的样式将优先使用组件内的fancy
样式。
八、@Extend装饰器:定义扩展组件样式
在前文的示例中,可以使用@Styles用于样式的扩展,在@Styles的基础上,我们提供了@Extend,用于扩展原生组件样式。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
装饰器使用说明
语法
@Extend(UIComponentName) function functionName { ... }
使用规则
和@Styles不同,@Extend仅支持定义在全局,不支持在组件内部定义。
说明
只能在当前文件内使用,不支持export。
和@Styles不同,@Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend的方法。
// @Extend(Text)可以支持Text的私有属性fontColor
@Extend(Text) function fancy () {
.fontColor(Color.Red)
}
// superFancyText可以调用预定义的fancy
@Extend(Text) function superFancyText(size:number) {
.fontSize(size)
.fancy()
}
和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(16)
Text('Fancy')
.fancy(24)
}
}
}
@Extend装饰的方法的参数可以为function,作为Event事件的句柄。
@Extend(Text) function makeMeClick(onClick: () => void) {
.backgroundColor(Color.Blue)
.onClick(onClick)
}
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World';
onClickHandler() {
this.label = 'Hello ArkUI';
}
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.makeMeClick(this.onClickHandler.bind(this))
}
}
}
@Extend的参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染。
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
@State fontSizeValue: number = 20
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(this.fontSizeValue)
.onClick(() => {
this.fontSizeValue = 30
})
}
}
}
使用场景
以下示例声明了3个Text组件,每个Text组件均设置了fontStyle、fontWeight和backgroundColor样式。
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(100)
.backgroundColor(Color.Blue)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(200)
.backgroundColor(Color.Pink)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(300)
.backgroundColor(Color.Orange)
}.margin('20%')
}
}
@Extend将样式组合复用,示例如下。
@Extend(Text) function fancyText(weightValue: number, color: Color) {
.fontStyle(FontStyle.Italic)
.fontWeight(weightValue)
.backgroundColor(color)
}
通过@Extend组合样式后,使得代码更加简洁,增强可读性。
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fancyText(100, Color.Blue)
Text(`${this.label}`)
.fancyText(200, Color.Pink)
Text(`${this.label}`)
.fancyText(300, Color.Orange)
}.margin('20%')
}
}
九、stateStyles:多态样式
stateStyles是属性方法,可以根据UI内部状态来设置样式,类似于css伪类,但语法不同。ArkUI提供以下四种状态:
- focused:获焦态。
- normal:正常态。
- pressed:按压态。
- disabled:不可用态。
使用场景
基础场景
下面的示例展示了stateStyles最基本的使用场景。Button1处于第一个组件,Button2处于第二个组件。按压时显示为pressed态指定的黑色。使用Tab键走焦,先是Button1获焦并显示为focus态指定的粉色。当Button2获焦的时候,Button2显示为focus态指定的粉色,Button1失焦显示normal态指定的红色。
@Entry
@Component
struct StateStylesSample {
build() {
Column() {
Button('Button1')
.stateStyles({
focused: {
.backgroundColor(Color.Pink)
},
pressed: {
.backgroundColor(Color.Black)
},
normal: {
.backgroundColor(Color.Red)
}
})
.margin(20)
Button('Button2')
.stateStyles({
focused: {
.backgroundColor(Color.Pink)
},
pressed: {
.backgroundColor(Color.Black)
},
normal: {
.backgroundColor(Color.Red)
}
})
}.margin('30%')
}
}
图1 获焦态和按压态
@Styles和stateStyles联合使用
以下示例通过@Styles指定stateStyles的不同状态。
@Entry
@Component
struct MyComponent {
@Styles normalStyle() {
.backgroundColor(Color.Gray)
}
@Styles pressedStyle() {
.backgroundColor(Color.Red)
}
build() {
Column() {
Text('Text1')
.fontSize(50)
.fontColor(Color.White)
.stateStyles({
normal: this.normalStyle,
pressed: this.pressedStyle,
})
}
}
}
图2 正常态和按压态
在stateStyles里使用常规变量和状态变量
stateStyles可以通过this绑定组件内的常规变量和状态变量。
@Entry
@Component
struct CompWithInlineStateStyles {
@State focusedColor: Color = Color.Red;
normalColor: Color = Color.Green
build() {
Column() {
Button('clickMe').height(100).width(100)
.stateStyles({
normal: {
.backgroundColor(this.normalColor)
},
focused: {
.backgroundColor(this.focusedColor)
}
})
.onClick(() => {
this.focusedColor = Color.Pink
})
.margin('30%')
}
}
}
Button默认normal态显示绿色,第一次按下Tab键让Button获焦显示为focus态的红色,点击事件触发后,再次按下Tab键让Button获焦,focus态变为粉色。
图3 点击改变获焦态样式
总结
- 基本语法概述:这是关于编程语言或框架的基本语法的一个简要介绍。
- 声明式UI描述:指的是通过代码直接描述UI的结构和样式,而不是通过命令式编程来逐步构建UI。
- 自定义组件:允许开发者创建自己的UI组件,这些组件可以具有特定的功能、样式和行为。
- @Builder装饰器:这是一个用于自定义构建函数的装饰器。它可以帮助开发者更方便地创建和配置组件。
- @BuilderParam装饰器:这是一个与@Builder装饰器配合使用的装饰器,用于引用@Builder函数中的参数。
- @Styles装饰器:这个装饰器允许开发者定义组件的重用样式,使得这些样式可以在多个组件之间共享。
- @Extend装饰器:这个装饰器用于定义扩展组件的样式,允许开发者在基础样式的基础上进行修改或扩展。
- stateStyles:多态样式:指的是根据组件的不同状态(如:悬停、按下、禁用等)来定义不同的样式。