ArkUI 声明式语法
ArkUI 采用声明式语法描述 UI,与 HTML/CSS 完全不同。每个页面是一个 .ets 文件,通过 ArkTS 代码描述界面结构。
基本结构
// Index.ets
@Entry
@Component
struct Index {
// 1. 状态变量
@State message: string = 'Hello HarmonyOS'
// 2. 可选:UI 构建器
@Builder
header(text: string) {
Text(text)
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
// 3. build() 方法:描述 UI
build() {
Column() {
this.header(this.message)
Text('这是一段文本')
.fontSize(16)
.fontColor('#666666')
Button('点击我')
.onClick(() => {
this.message = '你好,鸿蒙!'
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}关键规则
- 文件第一个页面用
@Entry @Component struct Xxx - 非入口组件用
@Component struct Yyy(可被复用) build()内有且仅有一个根容器- 组件首字母大写(
Text、Button、Image) - 样式用属性方法链(点号连接),不是 CSS
常用组件
文本组件 Text
Text 组件用于显示文本内容,支持丰富的字体样式和排版属性。
完整属性列表
| 属性 | 类型 | 说明 |
|---|---|---|
fontSize | number / string / Resource | 字体大小,默认单位 vp |
fontColor | ResourceColor | 字体颜色 |
fontWeight | FontWeight / number / string | 字体粗细 |
fontStyle | FontStyle | 字体样式(正常/斜体) |
fontFamily | string / Resource | 字体族 |
textAlign | TextAlign | 文本水平对齐方式 |
textOverflow | { overflow: TextOverflow } | 文本溢出处理方式 |
maxLines | number | 最大显示行数 |
lineHeight | number / string / Resource | 行高 |
letterSpacing | number / string | 字符间距 |
textCase | TextCase | 文本大小写转换 |
decoration | { type: TextDecorationType, color?: ResourceColor } | 文本装饰线 |
baselineOffset | number | 基线偏移量 |
minFontSize | number / string / Resource | 最小字体大小 |
maxFontSize | number / string / Resource | 最大字体大小 |
copyOption | CopyOptions | 文本复制选项 |
textSelectable | boolean | 文本是否可选中 |
heightAdaptivePolicy | TextHeightAdaptivePolicy | 文本高度自适应策略 |
代码示例
// 基础文本
Text('普通文本')
.fontSize(16)
.fontColor('#333333')
// 粗体文本
Text('粗体文本')
.fontSize(20)
.fontWeight(FontWeight.Bold)
// 完整字体样式
Text('带样式的文本')
.fontSize(14)
.fontColor(Color.Red)
.fontStyle(FontStyle.Italic)
.fontWeight(FontWeight.Medium)
.fontFamily('HarmonyOS Sans')
.decoration({ type: TextDecorationType.Underline, color: Color.Blue })
// 多行文本 + 省略号
Text('这是一段很长的文本,超出部分会显示省略号,用于演示文本溢出处理效果')
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
// 文本对齐
Text('居中对齐文本')
.fontSize(16)
.textAlign(TextAlign.Center)
.width('100%')
// 行高与字间距
Text('行高与字间距示例\n第二行文本')
.fontSize(14)
.lineHeight(24)
.letterSpacing(2)
// 文本大小写
Text('hello world')
.fontSize(16)
.textCase(TextCase.UpperCase) // 转换为大写
// 使用 font 统一设置
Text('统一字体设置')
.font({
size: 20,
weight: FontWeight.Bold,
family: 'HarmonyOS Sans',
style: FontStyle.Normal
})Span 子组件(富文本)
Text() {
Span('红色文本 ').fontColor(Color.Red).fontSize(16)
Span('粗体文本 ').fontWeight(FontWeight.Bold).fontSize(16)
Span('下划线文本').decoration({ type: TextDecorationType.Underline }).fontSize(16)
}图片组件 Image
Image 组件用于显示图片,支持本地图片、网络图片和 PixelMap。
完整属性列表
| 属性 | 类型 | 说明 |
|---|---|---|
alt | string / Resource | 加载占位图 |
objectFit | ImageFit | 图片填充模式 |
objectRepeat | ImageRepeat | 图片重复模式 |
interpolation | ImageInterpolation | 图片插值模式 |
renderMode | ImageRenderMode | 图片渲染模式 |
sourceSize | { width: number, height: number } | 图片源尺寸 |
matchTextDirection | boolean | 是否匹配文本方向 |
fitOriginalSize | boolean | 是否适应原始尺寸 |
fillColor | ResourceColor | 填充颜色 |
autoResize | boolean | 是否自动调整大小 |
syncLoad | boolean | 是否同步加载 |
copyOption | CopyOptions | 复制选项 |
colorFilter | ColorFilter | 颜色滤镜 |
draggable | boolean | 是否可拖拽 |
borderRadius | number / BorderRadiuses | 圆角 |
shadow | ShadowOptions | 阴影 |
ImageFit 枚举值
| 枚举值 | 说明 |
|---|---|
ImageFit.Contain | 保持比例,完整显示 |
ImageFit.Cover | 保持比例,填满容器(可能裁剪) |
ImageFit.Fill | 拉伸填满容器 |
ImageFit.None | 保持原始尺寸 |
ImageFit.ScaleDown | 保持比例,缩小或保持原始尺寸 |
ImageFit.Matrix | 使用矩阵变换(Image 组件不支持) |
代码示例
// 本地图片(放 resources/base/media/ 目录)
Image($r('app.media.logo'))
.width(100)
.height(100)
.objectFit(ImageFit.Cover)
.borderRadius(8)
// 网络图片
Image('https://example.com/image.png')
.width('100%')
.height(200)
.alt($r('app.media.placeholder')) // 加载占位图
.objectFit(ImageFit.Cover)
// 完整图片属性
Image($r('app.media.photo'))
.width(200)
.height(200)
.objectFit(ImageFit.Cover)
.interpolation(ImageInterpolation.High) // 高质量插值
.renderMode(ImageRenderMode.Original) // 原始渲染模式
.borderRadius({ topLeft: 8, topRight: 8, bottomLeft: 0, bottomRight: 0 })
.shadow({ radius: 4, color: '#1F000000', offsetX: 0, offsetY: 2 })
// 图片填充模式对比
Column({ space: 10 }) {
Image($r('app.media.demo'))
.width(150)
.height(100)
.objectFit(ImageFit.Contain)
.backgroundColor('#F0F0F0')
Image($r('app.media.demo'))
.width(150)
.height(100)
.objectFit(ImageFit.Cover)
.backgroundColor('#F0F0F0')
Image($r('app.media.demo'))
.width(150)
.height(100)
.objectFit(ImageFit.Fill)
.backgroundColor('#F0F0F0')
}按钮组件 Button
Button 组件用于触发点击操作,支持多种类型和样式。
完整属性列表
| 属性 | 类型 | 说明 |
|---|---|---|
type | ButtonType | 按钮类型 |
stateEffect | boolean | 是否显示按压效果 |
fontSize | number / string / Resource | 字体大小 |
fontWeight | FontWeight / number / string | 字体粗细 |
fontColor | ResourceColor | 字体颜色 |
fontStyle | FontStyle | 字体样式 |
fontFamily | string / Resource | 字体族 |
labelStyle | LabelStyle | 标签样式 |
backgroundColor | ResourceColor | 背景颜色 |
borderRadius | number / BorderRadiuses | 圆角 |
border | BorderOptions | 边框 |
ButtonType 枚举值
| 枚举值 | 说明 |
|---|---|
ButtonType.Capsule | 胶囊型按钮 |
ButtonType.Circle | 圆形按钮 |
ButtonType.Normal | 普通矩形按钮 |
代码示例
// 普通按钮
Button('普通按钮')
.fontSize(16)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.width('80%')
.height(40)
// 胶囊按钮
Button('胶囊按钮')
.type(ButtonType.Capsule)
.width('80%')
.backgroundColor('#FF6B6B')
.fontColor(Color.White)
// 圆形按钮
Button() {
Image($r('app.media.icon_add'))
.width(24)
.height(24)
.fillColor(Color.White)
}
.type(ButtonType.Circle)
.width(60)
.height(60)
.backgroundColor('#4ECDC4')
// 带图标的按钮
Button() {
Row({ space: 8 }) {
Image($r('app.media.icon_search'))
.width(16)
.height(16)
.fillColor(Color.White)
Text('搜索')
.fontColor(Color.White)
.fontSize(14)
}
}
.width(120)
.height(40)
.backgroundColor('#007DFF')
.borderRadius(20)
// 禁用状态
Button('禁用按钮')
.enabled(false)
.backgroundColor('#CCCCCC')
.fontColor('#999999')
// 点击事件
Button('点击我')
.onClick(() => {
console.info('按钮被点击')
})输入框 TextInput / TextArea
TextInput 用于单行文本输入,TextArea 用于多行文本输入。
TextInput 完整属性列表
| 属性 | 类型 | 说明 |
|---|---|---|
type | InputType | 输入类型 |
placeholder | string / Resource | 占位提示文本 |
placeholderColor | ResourceColor | 占位文本颜色 |
text | string | 输入文本内容 |
caretColor | ResourceColor | 光标颜色 |
maxLength | number | 最大输入长度 |
inputFilter | string / Resource | 输入过滤器(正则) |
showPasswordIcon | boolean | 是否显示密码可见图标 |
showError | string | 错误提示文本 |
fontSize | number / string / Resource | 字体大小 |
fontColor | ResourceColor | 字体颜色 |
fontWeight | FontWeight / number / string | 字体粗细 |
fontStyle | FontStyle | 字体样式 |
fontFamily | string / Resource | 字体族 |
textAlign | TextAlign | 文本对齐方式 |
copyOption | CopyOptions | 复制选项 |
style | TextInputStyle | 输入框样式 |
InputType 枚举值
| 枚举值 | 说明 |
|---|---|
InputType.Normal | 基本输入模式 |
InputType.Password | 密码输入模式 |
InputType.Number | 数字输入模式 |
InputType.PhoneNumber | 电话号码输入模式 |
InputType.Email | 邮箱地址输入模式 |
InputType.NumberDecimal | 带小数点的数字输入 |
InputType.NumberPassword | 数字密码输入 |
InputType.URL | URL 输入模式 |
InputType.USER_NAME | 用户名输入模式 |
InputType.NewPassword | 新密码输入模式 |
代码示例
@State inputValue: string = ''
@State password: string = ''
@State phoneNumber: string = ''
// 基础输入框
TextInput({ placeholder: '请输入内容', text: this.inputValue })
.type(InputType.Normal)
.maxLength(20)
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding({ left: 12, right: 12 })
.onChange((value: string) => {
this.inputValue = value
})
// 密码输入框
TextInput({ placeholder: '请输入密码', text: this.password })
.type(InputType.Password)
.maxLength(16)
.showPasswordIcon(true)
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.onChange((value: string) => {
this.password = value
})
// 手机号输入框
TextInput({ placeholder: '请输入手机号', text: this.phoneNumber })
.type(InputType.PhoneNumber)
.maxLength(11)
.inputFilter('[0-9]*') // 只允许输入数字
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.onChange((value: string) => {
this.phoneNumber = value
})
// 带错误提示的输入框
TextInput({ placeholder: '请输入邮箱', text: $$this.inputValue })
.type(InputType.Email)
.showError('邮箱格式不正确')
.height(40)
// 多行文本输入
TextArea({ placeholder: '请输入描述', text: this.inputValue })
.height(100)
.maxLength(200)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.onChange((value: string) => {
this.inputValue = value
})
// 输入框焦点事件
TextInput({ placeholder: '焦点事件演示' })
.onFocus(() => {
console.info('输入框获得焦点')
})
.onBlur(() => {
console.info('输入框失去焦点')
})
.onSubmit((enterKey: EnterKeyType) => {
console.info(`点击了提交键: ${enterKey}`)
})开关组件 Toggle / Switch / Checkbox / Radio
Toggle 组件
Toggle 是一个通用开关组件,可以显示为开关、复选框或单选按钮。
| 属性 | 类型 | 说明 |
|---|---|---|
type | ToggleType | 开关类型 |
isOn | boolean | 是否选中 |
selectedColor | ResourceColor | 选中状态颜色 |
switchPointColor | ResourceColor | 开关圆点颜色 |
ToggleType 枚举值
| 枚举值 | 说明 |
|---|---|
ToggleType.Switch | 开关样式 |
ToggleType.Checkbox | 复选框样式 |
ToggleType.Button | 按钮样式 |
代码示例
@State isOn: boolean = false
@State isChecked: boolean = true
@State selectedOption: string = 'option1'
// Switch 开关
Toggle({ type: ToggleType.Switch, isOn: this.isOn })
.selectedColor('#007DFF')
.switchPointColor(Color.White)
.onChange((isOn: boolean) => {
this.isOn = isOn
console.info(`开关状态: ${isOn}`)
})
// Checkbox 复选框
Toggle({ type: ToggleType.Checkbox, isOn: this.isChecked })
.selectedColor('#007DFF')
.size({ width: 20, height: 20 })
.onChange((isOn: boolean) => {
this.isChecked = isOn
})
// Checkbox 带文本
Row({ space: 8 }) {
Toggle({ type: ToggleType.Checkbox, isOn: this.isChecked })
.selectedColor('#007DFF')
.size({ width: 20, height: 20 })
Text('同意用户协议')
.fontSize(14)
.fontColor('#666666')
}
.onClick(() => {
this.isChecked = !this.isChecked
})
// Radio 单选按钮(使用 ToggleType.Button 配合分组)
Column({ space: 12 }) {
Row({ space: 8 }) {
Toggle({ type: ToggleType.Button, isOn: this.selectedOption === 'option1' })
.selectedColor('#007DFF')
.onChange(() => {
this.selectedOption = 'option1'
})
Text('选项一')
.fontSize(14)
}
Row({ space: 8 }) {
Toggle({ type: ToggleType.Button, isOn: this.selectedOption === 'option2' })
.selectedColor('#007DFF')
.onChange(() => {
this.selectedOption = 'option2'
})
Text('选项二')
.fontSize(14)
}
}滑块与进度组件 Slider / Progress / LoadingProgress
Slider 滑块
| 属性 | 类型 | 说明 |
|---|---|---|
value | number | 当前值 |
min | number | 最小值 |
max | number | 最大值 |
step | number | 步长 |
style | SliderStyle | 滑块样式 |
direction | Axis | 方向 |
reverse | boolean | 是否反向 |
trackColor | ResourceColor | 轨道颜色 |
selectedColor | ResourceColor | 已选轨道颜色 |
blockColor | ResourceColor | 滑块颜色 |
showSteps | boolean | 是否显示步长标记 |
showTips | boolean | 是否显示提示 |
SliderStyle 枚举值
| 枚举值 | 说明 |
|---|---|
SliderStyle.OutSet | 滑块在轨道外 |
SliderStyle.InSet | 滑块在轨道内 |
SliderStyle.None | 无滑块 |
Progress 进度条
| 属性 | 类型 | 说明 |
|---|---|---|
value | number | 当前值 |
total | number | 总值 |
type | ProgressType | 进度条类型 |
color | ResourceColor | 进度颜色 |
style | ProgressStyle | 进度条样式 |
ProgressType 枚举值
| 枚举值 | 说明 |
|---|---|
ProgressType.Linear | 线性进度条 |
ProgressType.Ring | 环形进度条 |
ProgressType.Eclipse | 椭圆形进度条 |
ProgressType.ScaleRing | 刻度环形进度条 |
ProgressType.Capsule | 胶囊型进度条 |
代码示例
@State sliderValue: number = 50
@State progressValue: number = 30
// 基础滑块
Slider({ value: this.sliderValue, min: 0, max: 100, step: 1 })
.width('90%')
.selectedColor('#007DFF')
.trackColor('#E0E0E0')
.blockColor('#007DFF')
.onChange((value: number, mode: SliderChangeMode) => {
this.sliderValue = value
console.info(`滑块值: ${value}, 模式: ${mode}`)
})
// 带步长标记的滑块
Slider({ value: this.sliderValue, min: 0, max: 100, step: 10 })
.width('90%')
.showSteps(true)
.showTips(true)
.selectedColor('#007DFF')
// 线性进度条
Progress({ value: this.progressValue, total: 100, type: ProgressType.Linear })
.width('90%')
.height(8)
.color('#007DFF')
// 环形进度条
Progress({ value: this.progressValue, total: 100, type: ProgressType.Ring })
.width(80)
.height(80)
.color('#007DFF')
// 加载进度条
LoadingProgress()
.width(50)
.height(50)
.color('#007DFF')
// 加载进度条带文字
Column({ space: 8 }) {
LoadingProgress()
.width(40)
.height(40)
.color('#007DFF')
Text('加载中...')
.fontSize(14)
.fontColor('#999999')
}分隔与空白组件 Divider / Blank / Spacer
Divider 分隔线
| 属性 | 类型 | 说明 |
|---|---|---|
vertical | boolean | 是否为垂直分隔线 |
color | ResourceColor | 分隔线颜色 |
strokeWidth | number / string | 线宽 |
lineCap | LineCapStyle | 线端样式 |
Blank 空白填充
Blank 组件用于在 Row/Column/Flex 中填充空白区域,将其他组件推到一侧。
| 属性 | 类型 | 说明 |
|---|---|---|
color | ResourceColor | 空白区域颜色 |
min | number / string | 最小尺寸 |
Spacer 固定间距
Spacer 用于在布局中创建固定大小的间距。
代码示例
// 水平分隔线
Divider()
.width('100%')
.height(1)
.color('#E0E0E0')
// 垂直分隔线
Row({ space: 16 }) {
Text('左侧内容')
Divider()
.vertical(true)
.width(1)
.height(20)
.color('#E0E0E0')
Text('右侧内容')
}
// 使用 Blank 实现两端对齐
Row() {
Text('左侧标题')
.fontSize(16)
Blank() // 填充中间空白
Text('右侧操作')
.fontSize(14)
.fontColor('#007DFF')
}
.width('100%')
.padding(16)
// 使用 Spacer 创建固定间距
Column() {
Text('上方内容')
Spacer().height(20) // 20vp 间距
Text('下方内容')
}滚动与轮播组件 Scroll / Swiper
Scroll 滚动容器
| 属性 | 类型 | 说明 |
|---|---|---|
scrollable | ScrollDirection | 滚动方向 |
scrollBar | BarState | 滚动条状态 |
scrollBarColor | ResourceColor | 滚动条颜色 |
scrollBarWidth | number / string | 滚动条宽度 |
edgeEffect | EdgeEffect | 边缘效果 |
nestedScroll | NestedScrollOptions | 嵌套滚动配置 |
Swiper 轮播组件
| 属性 | 类型 | 说明 |
|---|---|---|
index | number | 当前索引 |
autoPlay | boolean | 是否自动播放 |
interval | number | 自动播放间隔(ms) |
loop | boolean | 是否循环 |
indicator | boolean / DotIndicator / DigitIndicator | 指示器 |
duration | number | 切换动画时长 |
vertical | boolean | 是否为纵向 |
itemSpace | number / string | 子组件间距 |
displayMode | SwiperDisplayMode | 显示模式 |
cachedCount | number | 缓存数量 |
curve | Curve / ICurve | 动画曲线 |
disableSwipe | boolean | 是否禁用滑动 |
代码示例
@State scrollOffset: number = 0
@State swiperIndex: number = 0
// 垂直滚动
Scroll() {
Column({ space: 16 }) {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], (item: number) => {
Text(`滚动项 ${item}`)
.width('100%')
.height(60)
.backgroundColor('#F5F5F5')
.textAlign(TextAlign.Center)
})
}
.width('100%')
}
.width('100%')
.height(300)
.scrollBar(BarState.Auto)
.edgeEffect(EdgeEffect.Spring)
.onScroll((xOffset: number, yOffset: number) => {
console.info(`滚动偏移: x=${xOffset}, y=${yOffset}`)
})
// 横向滚动
Scroll() {
Row({ space: 16 }) {
ForEach([1, 2, 3, 4, 5], (item: number) => {
Text(`标签 ${item}`)
.width(100)
.height(40)
.backgroundColor('#F0F0F0')
.textAlign(TextAlign.Center)
.borderRadius(20)
})
}
.padding(16)
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
// 基础轮播
Swiper() {
ForEach([1, 2, 3, 4], (item: number) => {
Text(`轮播项 ${item}`)
.width('100%')
.height(200)
.backgroundColor(item % 2 === 0 ? '#007DFF' : '#4ECDC4')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(24)
})
}
.width('100%')
.height(200)
.autoPlay(true)
.interval(3000)
.loop(true)
.indicator(true)
.duration(500)
.onChange((index: number) => {
this.swiperIndex = index
console.info(`当前轮播索引: ${index}`)
})
// 自定义指示器的轮播
Swiper() {
ForEach([1, 2, 3], (item: number) => {
Image($r(`app.media.banner${item}`))
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
})
}
.width('100%')
.height(200)
.indicator(
new DotIndicator()
.itemWidth(8)
.itemHeight(8)
.selectedItemWidth(16)
.selectedItemHeight(8)
.color('#CCCCCC')
.selectedColor('#007DFF')
)列表组件 List / Grid / WaterFlow
List 列表
| 属性 | 类型 | 说明 |
|---|---|---|
space | number / string | 列表项间距 |
initialIndex | number | 初始索引 |
divider | { strokeWidth: number, color?: ResourceColor, startMargin?: number, endMargin?: number } | 分隔线 |
scrollBar | BarState | 滚动条状态 |
edgeEffect | EdgeEffect | 边缘效果 |
listDirection | Axis | 列表方向 |
cachedCount | number | 缓存数量 |
alignListItem | ListItemAlign | 列表项对齐 |
sticky | StickyStyle | 吸顶样式 |
Grid 网格
| 属性 | 类型 | 说明 |
|---|---|---|
columnsTemplate | string | 列模板(如 '1fr 1fr 1fr') |
rowsTemplate | string | 行模板 |
columnsGap | number / string | 列间距 |
rowsGap | number / string | 行间距 |
scrollBar | BarState | 滚动条状态 |
scrollBarWidth | number / string | 滚动条宽度 |
edgeEffect | EdgeEffect | 边缘效果 |
layoutDirection | GridDirection | 布局方向 |
maxCount | number | 最大显示数量 |
minCount | number | 最小显示数量 |
cellLength | number / string | 单元格长度 |
multiSelectable | boolean | 是否多选 |
supportAnimation | boolean | 是否支持动画 |
cachedCount | number | 缓存数量 |
WaterFlow 瀑布流
| 属性 | 类型 | 说明 |
|---|---|---|
columnsTemplate | string | 列模板 |
rowsTemplate | string | 行模板 |
columnsGap | number / string | 列间距 |
rowsGap | number / string | 行间距 |
scrollBar | BarState | 滚动条状态 |
edgeEffect | EdgeEffect | 边缘效果 |
layoutDirection | FlexDirection | 布局方向 |
itemConstraintSize | ConstraintSizeOptions | 子组件约束尺寸 |
cachedCount | number | 缓存数量 |
代码示例
@State listItems: string[] = ['苹果', '香蕉', '橙子', '葡萄', '西瓜', '芒果']
@State gridItems: { title: string, color: string }[] = [
{ title: '项目1', color: '#FF6B6B' },
{ title: '项目2', color: '#4ECDC4' },
{ title: '项目3', color: '#45B7D1' },
{ title: '项目4', color: '#96CEB4' },
{ title: '项目5', color: '#FFEAA7' },
{ title: '项目6', color: '#DDA0DD' }
]
// 基础列表
List({ space: 8 }) {
ForEach(this.listItems, (item: string, index: number) => {
ListItem() {
Row() {
Text(`${index + 1}. ${item}`)
.fontSize(16)
Button('删除')
.fontSize(12)
.onClick(() => {
this.listItems.splice(index, 1)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
}
}, (item: string) => item)
}
.width('100%')
.divider({ strokeWidth: 1, color: '#EEEEEE' })
// 带吸顶标题的列表
List({ space: 0 }) {
ListItemGroup({ header: this.GroupHeader('水果') }) {
ForEach(['苹果', '香蕉', '橙子'], (item: string) => {
ListItem() {
Text(item)
.width('100%')
.height(50)
.padding({ left: 16 })
}
})
}
ListItemGroup({ header: this.GroupHeader('蔬菜') }) {
ForEach(['西红柿', '黄瓜', '白菜'], (item: string) => {
ListItem() {
Text(item)
.width('100%')
.height(50)
.padding({ left: 16 })
}
})
}
}
.width('100%')
.sticky(StickyStyle.Header)
@Builder
GroupHeader(title: string) {
Row() {
Text(title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height(40)
.backgroundColor('#F0F0F0')
.padding({ left: 16 })
}
// 网格布局
Grid() {
ForEach(this.gridItems, (item: { title: string, color: string }) => {
GridItem() {
Column() {
Text(item.title)
.fontSize(16)
.fontColor(Color.White)
}
.width('100%')
.height(100)
.backgroundColor(item.color)
.justifyContent(FlexAlign.Center)
.borderRadius(8)
}
})
}
.width('100%')
.columnsTemplate('1fr 1fr 1fr') // 三列等宽
.columnsGap(8)
.rowsGap(8)
.padding(8)
// 瀑布流布局
WaterFlow() {
LazyForEach(this.dataSource, (item: ItemType) => {
FlowItem() {
Column() {
Image(item.image)
.width('100%')
.objectFit(ImageFit.Cover)
Text(item.title)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.width('100%')
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
})
}width('100%')
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.padding(8)导航组件 Tabs / TabContent / Navigation
Tabs 标签页
| 属性 | 类型 | 说明 |
|---|---|---|
barPosition | BarPosition | 导航栏位置 |
vertical | boolean | 是否为纵向 |
scrollable | boolean | 是否可滚动 |
barMode | BarMode | 导航栏模式 |
barWidth | number / string | 导航栏宽度 |
barHeight | number / string | 导航栏高度 |
animationDuration | number | 动画时长 |
fadingEdge | boolean | 是否启用渐隐边缘 |
BarMode 枚举值
| 枚举值 | 说明 |
|---|---|
BarMode.Fixed | 固定模式,均分显示 |
BarMode.Scrollable | 可滚动模式 |
Navigation 导航
| 属性 | 类型 | 说明 |
|---|---|---|
title | string / Resource / CustomBuilder | 标题 |
subTitle | string / Resource | 副标题 |
hideTitleBar | boolean | 是否隐藏标题栏 |
hideBackButton | boolean | 是否隐藏返回按钮 |
titleMode | NavigationTitleMode | 标题模式 |
mode | NavigationMode | 导航模式 |
navBarWidth | number / string | 导航栏宽度 |
navBarPosition | NavBarPosition | 导航栏位置 |
代码示例
@State currentIndex: number = 0
// 底部标签页
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Text('首页内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.tabBar(this.TabBuilder('首页', $r('app.media.icon_home'), 0))
TabContent() {
Text('分类内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.tabBar(this.TabBuilder('分类', $r('app.media.icon_category'), 1))
TabContent() {
Text('购物车内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.tabBar(this.TabBuilder('购物车', $r('app.media.icon_cart'), 2))
TabContent() {
Text('我的内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.tabBar(this.TabBuilder('我的', $r('app.media.icon_mine'), 3))
}
.width('100%')
.height('100%')
.barMode(BarMode.Fixed)
.onChange((index: number) => {
this.currentIndex = index
})
@Builder
TabBuilder(title: string, icon: Resource, index: number) {
Column({ space: 4 }) {
Image(icon)
.width(24)
.height(24)
.fillColor(this.currentIndex === index ? '#007DFF' : '#999999')
Text(title)
.fontSize(12)
.fontColor(this.currentIndex === index ? '#007DFF' : '#999999')
}
.width('100%')
.height(56)
.justifyContent(FlexAlign.Center)
}
// 顶部标签页(可滚动)
Tabs({ barPosition: BarPosition.Start }) {
ForEach(['推荐', '热点', '科技', '娱乐', '体育', '财经', '游戏'], (item: string, index: number) => {
TabContent() {
Text(`${item}内容`)
.width('100%')
.height('100%')
}
.tabBar(item)
})
}
.width('100%')
.height('100%')
.barMode(BarMode.Scrollable)
// Navigation 导航
Navigation() {
Text('页面内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.title('页面标题')
.subTitle('副标题')
.hideBackButton(false)
.titleMode(NavigationTitleMode.Full)
.mode(NavigationMode.Stack)Web 网页组件
Web 组件用于在应用中嵌入网页内容。
| 属性 | 类型 | 说明 |
|---|---|---|
src | string / Resource | 网页地址 |
controller | WebviewController | 控制器 |
javaScriptAccess | boolean | 是否允许 JavaScript |
zoomAccess | boolean | 是否允许缩放 |
fileAccess | boolean | 是否允许访问文件 |
mixedMode | MixedMode | 混合内容模式 |
cacheMode | CacheMode | 缓存模式 |
darkMode | WebDarkMode | 深色模式 |
代码示例
@State webUrl: string = 'https://www.example.com'
controller: WebviewController = new WebviewController()
Web({ src: this.webUrl, controller: this.controller })
.width('100%')
.height('100%')
.javaScriptAccess(true)
.zoomAccess(true)
.fileAccess(false)
.onPageBegin((event) => {
console.info(`页面开始加载: ${event.url}`)
})
.onPageEnd((event) => {
console.info(`页面加载完成: ${event.url}`)
})
.onErrorReceive((event) => {
console.error(`页面加载错误: ${event.error.getErrorInfo()}`)
})
.onProgressChange((event) => {
console.info(`加载进度: ${event.newProgress}%`)
})
// Web 控制器方法
// this.controller.loadUrl('https://www.new-url.com')
// this.controller.refresh()
// this.controller.stop()
// this.controller.getUrl()
// this.controller.accessForward()
// this.accessBackward()
// this.controller.forward()
// this.controller.backward()Video 视频组件
Video 组件用于播放视频文件并控制其播放状态。
| 属性 | 类型 | 说明 |
|---|---|---|
src | string / Resource | 视频数据源 |
currentProgressRate | number / string / PlaybackSpeed | 播放倍速 |
previewUri | string / PixelMap / Resource | 预览图 |
controller | VideoController | 视频控制器 |
muted | boolean | 是否静音 |
autoPlay | boolean | 是否自动播放 |
controls | boolean | 是否显示控制栏 |
objectFit | ImageFit | 视频填充模式 |
loop | boolean | 是否循环播放 |
PlaybackSpeed 枚举值
| 枚举值 | 说明 |
|---|---|
PlaybackSpeed.Speed_Forward_0_75_X | 0.75倍速 |
PlaybackSpeed.Speed_Forward_1_00_X | 1倍速 |
PlaybackSpeed.Speed_Forward_1_25_X | 1.25倍速 |
PlaybackSpeed.Speed_Forward_1_75_X | 1.75倍速 |
PlaybackSpeed.Speed_Forward_2_00_X | 2倍速 |
VideoController 方法
| 方法 | 说明 |
|---|---|
start() | 开始播放 |
pause() | 暂停播放 |
stop() | 停止播放 |
setCurrentTime(value: number) | 设置当前播放位置 |
requestFullscreen() | 请求全屏 |
exitFullscreen() | 退出全屏 |
代码示例
@State videoSrc: string = 'https://www.example.com/video.mp4'
videoController: VideoController = new VideoController()
Video({
src: this.videoSrc,
controller: this.videoController,
previewUri: $r('app.media.video_preview')
})
.width('100%')
.height(200)
.autoPlay(false)
.controls(true)
.loop(false)
.objectFit(ImageFit.Contain)
.onStart(() => {
console.info('视频开始播放')
})
.onPause(() => {
console.info('视频暂停')
})
.onFinish(() => {
console.info('视频播放结束')
})
.onError(() => {
console.error('视频播放错误')
})
.onPrepared((event) => {
console.info(`视频准备完成,时长: ${event.duration}秒`)
})
.onUpdate((event) => {
console.info(`当前播放进度: ${event.time}秒`)
})
.onFullscreenChange((event) => {
console.info(`全屏状态变化: ${event.fullscreen}`)
})
// 自定义控制按钮
Row({ space: 16 }) {
Button('播放')
.onClick(() => {
this.videoController.start()
})
Button('暂停')
.onClick(() => {
this.videoController.pause()
})
Button('全屏')
.onClick(() => {
this.videoController.requestFullscreen()
})
}布局容器
线性布局 Column / Row
Column 和 Row 是最基础的布局容器,分别用于垂直和水平方向的线性排列。
Column 属性
| 属性 | 类型 | 说明 |
|---|---|---|
space | number / string | 子元素间距 |
justifyContent | FlexAlign | 主轴对齐方式 |
alignItems | HorizontalAlign | 交叉轴对齐方式 |
Row 属性
| 属性 | 类型 | 说明 |
|---|---|---|
space | number / string | 子元素间距 |
justifyContent | FlexAlign | 主轴对齐方式 |
alignItems | VerticalAlign | 交叉轴对齐方式 |
FlexAlign 枚举值
| 枚举值 | 说明 |
|---|---|
FlexAlign.Start | 起始对齐 |
FlexAlign.Center | 居中对齐 |
FlexAlign.End | 末尾对齐 |
FlexAlign.SpaceBetween | 两端对齐 |
FlexAlign.SpaceAround | 环绕对齐 |
FlexAlign.SpaceEvenly | 均匀对齐 |
代码示例
// 垂直布局
Column({ space: 12 }) {
Text('第一行')
Text('第二行')
Text('第三行')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.padding(16)
// 垂直布局 - 顶部对齐
Column({ space: 8 }) {
Text('标题')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('描述文本')
.fontSize(14)
.fontColor('#666666')
Button('操作按钮')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding(16)
// 水平布局
Row({ space: 16 }) {
Button('取消')
Button('确定')
.backgroundColor('#007DFF')
}
.width('100%')
.justifyContent(FlexAlign.End)
.padding(16)
// 水平布局 - 两端对齐
Row() {
Text('左侧标题')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Blank()
Text('右侧操作 >')
.fontSize(14)
.fontColor('#999999')
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
// 水平布局 - 环绕对齐
Row({ space: 8 }) {
ForEach(['标签1', '标签2', '标签3', '标签4'], (item: string) => {
Text(item)
.fontSize(12)
.padding({ top: 4, bottom: 4, left: 12, right: 12 })
.backgroundColor('#F0F0F0')
.borderRadius(12)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.padding(16)层叠布局 Stack
Stack 用于将子组件按顺序层叠放置,后放置的组件覆盖在先放置的组件之上。
| 属性 | 类型 | 说明 |
|---|---|---|
alignContent | Alignment | 内容对齐方式 |
Alignment 枚举值
| 枚举值 | 说明 |
|---|---|
Alignment.TopStart | 左上角 |
Alignment.Top | 顶部居中 |
Alignment.TopEnd | 右上角 |
Alignment.Start | 左侧居中 |
Alignment.Center | 居中 |
Alignment.End | 右侧居中 |
Alignment.BottomStart | 左下角 |
Alignment.Bottom | 底部居中 |
Alignment.BottomEnd | 右下角 |
代码示例
// 基础层叠布局
Stack({ alignContent: Alignment.Center }) {
Image($r('app.media.background'))
.width('100%')
.height('100%')
Text('叠加文字')
.fontSize(24)
.fontColor(Color.White)
.shadow({ radius: 4, color: '#80000000' })
}
.width('100%')
.height(300)
// 带角标的层叠布局
Stack({ alignContent: Alignment.TopEnd }) {
Image($r('app.media.avatar'))
.width(80)
.height(80)
.borderRadius(40)
Text('3')
.fontSize(12)
.fontColor(Color.White)
.width(20)
.height(20)
.backgroundColor(Color.Red)
.borderRadius(10)
.textAlign(TextAlign.Center)
}
.width(80)
.height(80)
// 复杂层叠布局
Stack({ alignContent: Alignment.Bottom }) {
// 底层:背景图
Image($r('app.media.card_bg'))
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(12)
// 中层:渐变遮罩
Column()
.width('100%')
.height(200)
.borderRadius(12)
.backgroundColor('#80000000')
// 顶层:文字内容
Column({ space: 8 }) {
Text('标题文本')
.fontSize(20)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
Text('描述文本')
.fontSize(14)
.fontColor('#CCCCCC')
}
.padding(16)
.margin({ bottom: 16 })
}
.width('100%')
.height(200)
.padding(16)弹性布局 Flex
Flex 是更灵活的布局容器,支持方向、换行等高级配置。
| 属性 | 类型 | 说明 |
|---|---|---|
direction | FlexDirection | 主轴方向 |
wrap | FlexWrap | 换行方式 |
justifyContent | FlexAlign | 主轴对齐 |
alignItems | ItemAlign | 交叉轴对齐 |
alignContent | FlexAlign | 多行对齐 |
FlexDirection 枚举值
| 枚举值 | 说明 |
|---|---|
FlexDirection.Row | 水平方向 |
FlexDirection.RowReverse | 水平反向 |
FlexDirection.Column | 垂直方向 |
FlexDirection.ColumnReverse | 垂直反向 |
FlexWrap 枚举值
| 枚举值 | 说明 |
|---|---|
FlexWrap.NoWrap | 不换行 |
FlexWrap.Wrap | 换行 |
FlexWrap.WrapReverse | 反向换行 |
ItemAlign 枚举值
| 枚举值 | 说明 |
|---|---|
ItemAlign.Auto | 自动 |
ItemAlign.Start | 起始对齐 |
ItemAlign.Center | 居中对齐 |
ItemAlign.End | 末尾对齐 |
ItemAlign.Stretch | 拉伸对齐 |
ItemAlign.Baseline | 基线对齐 |
代码示例
// 水平弹性布局,自动换行
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
ForEach(this.tags, (tag: string) => {
Text(tag)
.fontSize(14)
.padding({ top: 4, bottom: 4, left: 8, right: 8 })
.backgroundColor('#F0F0F0')
.borderRadius(4)
.margin(4)
})
}
.width('100%')
.padding(16)
// 垂直弹性布局
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start }) {
Text('标题')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('描述')
.fontSize(14)
.fontColor('#666666')
Button('按钮')
.margin({ top: 16 })
}
.width('100%')
.padding(16)
// 弹性布局 - 子元素占比
Flex({ direction: FlexDirection.Row }) {
Text('左侧')
.layoutWeight(1)
.height(50)
.backgroundColor('#FF6B6B')
.textAlign(TextAlign.Center)
Text('中间')
.layoutWeight(2)
.height(50)
.backgroundColor('#4ECDC4')
.textAlign(TextAlign.Center)
Text('右侧')
.layoutWeight(1)
.height(50)
.backgroundColor('#45B7D1')
.textAlign(TextAlign.Center)
}
.width('100%')网格布局 Grid / GridItem
Grid 用于创建二维网格布局,可以精确控制行列结构。
代码示例
// 等分网格
Grid() {
ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
GridItem() {
Text(`${item}`)
.width('100%')
.height(100)
.backgroundColor(item % 2 === 0 ? '#007DFF' : '#4ECDC4')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(24)
}
})
}
.width('100%')
.columnsTemplate('1fr 1fr 1fr') // 三列等分
.rowsTemplate('1fr 1fr') // 两行等分
.columnsGap(8)
.rowsGap(8)
.padding(8)
// 不等分网格
Grid() {
GridItem() {
Text('大图')
.width('100%')
.height('100%')
.backgroundColor('#FF6B6B')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
}
.rowStart(0)
.rowEnd(1)
.columnStart(0)
.columnEnd(1)
ForEach([2, 3, 4, 5], (item: number) => {
GridItem() {
Text(`${item}`)
.width('100%')
.height('100%')
.backgroundColor('#4ECDC4')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
}
})
}
.width('100%')
.height(300)
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.padding(8)
// 使用 LazyForEach 的网格(性能优化)
Grid() {
LazyForEach(this.dataSource, (item: ItemType) => {
GridItem() {
Image(item.image)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
Text(item.title)
.fontSize(14)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
})
}
.width('100%')
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.cachedCount(3) // 缓存数量列表布局 List / ListItem
List 用于展示大量数据的滚动列表,支持垂直和水平方向。
代码示例
// 基础列表
List({ space: 8 }) {
ForEach(this.items, (item: string, index: number) => {
ListItem() {
Row() {
Text(`${index + 1}. ${item}`)
.fontSize(16)
Button('删除')
.fontSize(12)
.onClick(() => {
this.items.splice(index, 1)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
}
}, (item: string) => item)
}
.width('100%')
.divider({ strokeWidth: 1, color: '#EEEEEE' })
// 横向列表
List({ space: 12 }) {
ForEach(this.categories, (item: string) => {
ListItem() {
Text(item)
.fontSize(14)
.padding({ top: 8, bottom: 8, left: 16, right: 16 })
.backgroundColor('#F0F0F0')
.borderRadius(16)
}
})
}
.width('100%')
.height(60)
.listDirection(Axis.Horizontal)
.scrollBar(BarState.Off)
// 带侧滑操作的列表
List({ space: 0 }) {
ForEach(this.messages, (item: MessageType, index: number) => {
ListItem() {
Row() {
Image(item.avatar)
.width(48)
.height(48)
.borderRadius(24)
Column({ space: 4 }) {
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(item.content)
.fontSize(14)
.fontColor('#666666')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
.padding(16)
}
.swipeAction({
end: this.DeleteBuilder(index)
})
})
}
.width('100%')
.divider({ strokeWidth: 1, color: '#EEEEEE', startMargin: 80 })
@Builder
DeleteBuilder(index: number) {
Button() {
Image($r('app.media.icon_delete'))
.width(24)
.height(24)
.fillColor(Color.White)
}
.width(60)
.height('100%')
.backgroundColor(Color.Red)
.onClick(() => {
this.messages.splice(index, 1)
})
}懒加载 LazyForEach
LazyForEach 用于长列表场景,只渲染可视区域内的列表项,大幅提升性能。
// 数据源需要实现 IDataSource 接口
class MyDataSource implements IDataSource {
private dataArray: string[] = []
private listeners: DataChangeListener[] = []
constructor(data: string[]) {
this.dataArray = data
}
totalCount(): number {
return this.dataArray.length
}
getData(index: number): string {
return this.dataArray[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener)
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const index = this.listeners.indexOf(listener)
if (index !== -1) {
this.listeners.splice(index, 1)
}
}
// 数据操作方法
pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
deleteData(index: number): void {
this.dataArray.splice(index, 1)
this.notifyDataDelete(index)
}
private notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
private notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
}
// 使用 LazyForEach
@State dataSource: MyDataSource = new MyDataSource(
Array.from({ length: 1000 }, (_, i) => `项目 ${i + 1}`)
)
List({ space: 8 }) {
LazyForEach(this.dataSource, (item: string, index: number) => {
ListItem() {
Text(item)
.width('100%')
.height(60)
.textAlign(TextAlign.Center)
.backgroundColor('#F5F5F5')
}
}, (item: string, index: number) => index.toString())
}
.width('100%')
.height('100%')
.cachedCount(5) // 缓存前后各 5 个列表项
// 添加数据
Button('添加项目')
.onClick(() => {
this.dataSource.pushData(`新项目 ${Date.now()}`)
})轮播布局 Swiper
Swiper 用于创建轮播图效果,支持自动播放、循环、指示器等。
代码示例
@State swiperIndex: number = 0
// 基础轮播
Swiper() {
ForEach([1, 2, 3, 4], (item: number) => {
Text(`轮播项 ${item}`)
.width('100%')
.height(200)
.backgroundColor(item % 2 === 0 ? '#007DFF' : '#4ECDC4')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(24)
})
}
.width('100%')
.height(200)
.autoPlay(true)
.interval(3000)
.loop(true)
.indicator(true)
// 纵向轮播
Swiper() {
ForEach(['通知1', '通知2', '通知3'], (item: string) => {
Text(item)
.width('100%')
.height(40)
.textAlign(TextAlign.Center)
.backgroundColor('#F0F0F0')
})
}
.width('100%')
.height(40)
.vertical(true)
.autoPlay(true)
.interval(2000)
.indicator(false)
// 自定义指示器样式
Swiper() {
ForEach([1, 2, 3], (item: number) => {
Image($r(`app.media.banner${item}`))
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
})
}
.width('100%')
.height(200)
.indicator(
new DotIndicator()
.itemWidth(8)
.itemHeight(8)
.selectedItemWidth(16)
.selectedItemHeight(8)
.color('#CCCCCC')
.selectedColor('#007DFF')
)
.onChange((index: number) => {
this.swiperIndex = index
})
// 卡片式轮播
Swiper() {
ForEach([1, 2, 3, 4, 5], (item: number) => {
Column() {
Text(`卡片 ${item}`)
.fontSize(20)
.fontColor(Color.White)
}
.width('80%')
.height(180)
.backgroundColor(item % 2 === 0 ? '#FF6B6B' : '#4ECDC4')
.borderRadius(12)
.justifyContent(FlexAlign.Center)
})
}
.width('100%')
.height(200)
.itemSpace(12)
.displayMode(SwiperDisplayMode.STRETCH)
.indicator(true)瀑布流 WaterFlow
WaterFlow 用于创建瀑布流布局,子组件可以有不同的尺寸。
代码示例
// 瀑布流布局
WaterFlow() {
LazyForEach(this.waterFlowData, (item: WaterFlowItem) => {
FlowItem() {
Column({ space: 8 }) {
Image(item.image)
.width('100%')
.height(item.imageHeight)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 })
Text(item.title)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.padding({ left: 8, right: 8 })
Text(item.price)
.fontSize(16)
.fontColor('#FF6B6B')
.fontWeight(FontWeight.Bold)
.padding({ left: 8, right: 8, bottom: 8 })
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#1F000000', offsetX: 0, offsetY: 2 })
}
})
}
.width('100%')
.height('100%')
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.padding(8)
// 瀑布流数据源
class WaterFlowDataSource implements IDataSource {
private data: WaterFlowItem[] = []
private listeners: DataChangeListener[] = []
constructor() {
// 初始化数据,每个项目有不同的高度
for (let i = 0; i < 100; i++) {
this.data.push({
id: i,
image: $r('app.media.goods'),
imageHeight: 100 + Math.random() * 100, // 随机高度
title: `商品标题 ${i + 1}`,
price: `¥${(Math.random() * 1000).toFixed(2)}`
})
}
}
totalCount(): number {
return this.data.length
}
getData(index: number): WaterFlowItem {
return this.data[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener)
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const index = this.listeners.indexOf(listener)
if (index !== -1) {
this.listeners.splice(index, 1)
}
}
}相对布局 RelativeContainer
RelativeContainer 用于创建相对布局,子组件通过锚点关系进行定位。
| 属性 | 类型 | 说明 |
|---|---|---|
alignRules | AlignRuleOption | 对齐规则 |
id | string | 组件标识 |
AlignRuleOption 属性
| 属性 | 类型 | 说明 |
|---|---|---|
left | { anchor: string, align: HorizontalAlign } | 左对齐规则 |
right | { anchor: string, align: HorizontalAlign } | 右对齐规则 |
top | { anchor: string, align: VerticalAlign } | 顶部对齐规则 |
bottom | { anchor: string, align: VerticalAlign } | 底部对齐规则 |
middle | { anchor: string, align: HorizontalAlign } | 水平居中对齐 |
center | { anchor: string, align: VerticalAlign } | 垂直居中对齐 |
代码示例
// 相对布局基础用法
RelativeContainer() {
// 左上角头像
Image($r('app.media.avatar'))
.id('avatar')
.width(60)
.height(60)
.borderRadius(30)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
.margin(16)
// 头像右侧的用户名
Text('用户名')
.id('username')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.alignRules({
top: { anchor: 'avatar', align: VerticalAlign.Top },
left: { anchor: 'avatar', align: HorizontalAlign.End }
})
.margin({ left: 12, top: 4 })
// 用户名下方的描述
Text('用户描述信息')
.id('description')
.fontSize(14)
.fontColor('#666666')
.alignRules({
top: { anchor: 'username', align: VerticalAlign.Bottom },
left: { anchor: 'username', align: HorizontalAlign.Start }
})
.margin({ top: 4 })
// 右侧的操作按钮
Button('关注')
.id('followBtn')
.fontSize(14)
.width(70)
.height(32)
.alignRules({
top: { anchor: 'avatar', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.margin({ right: 16, top: 14 })
// 底部的分隔线
Divider()
.id('divider')
.height(1)
.color('#EEEEEE')
.alignRules({
top: { anchor: 'avatar', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.margin({ top: 16 })
}
.width('100%')
.height(100)
.backgroundColor(Color.White)
// 复杂的相对布局
RelativeContainer() {
// 顶部导航栏
Row() {
Image($r('app.media.icon_back'))
.width(24)
.height(24)
Blank()
Text('页面标题')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Blank()
Image($r('app.media.icon_more'))
.width(24)
.height(24)
}
.id('header')
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
// 内容区域
Scroll() {
Column({ space: 16 }) {
ForEach([1, 2, 3, 4, 5], (item: number) => {
Text(`内容项 ${item}`)
.width('100%')
.height(80)
.backgroundColor('#F5F5F5')
.textAlign(TextAlign.Center)
})
}
.padding(16)
}
.id('content')
.alignRules({
top: { anchor: 'header', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)动画系统
属性动画 animation
属性动画通过 animation 方法实现,当组件的属性变化时会自动触发动画。
| 属性 | 类型 | 说明 |
|---|---|---|
duration | number | 动画时长(ms) |
tempo | number | 动画速率 |
curve | Curve / ICurve | 动画曲线 |
delay | number | 延迟时间(ms) |
iterations | number | 迭代次数(-1 表示无限) |
playMode | PlayMode | 播放模式 |
onFinish | () => void | 动画结束回调 |
Curve 枚举值
| 枚举值 | 说明 |
|---|---|
Curve.Linear | 线性 |
Curve.Ease | 缓动 |
Curve.EaseIn | 缓入 |
Curve.EaseOut | 缓出 |
Curve.EaseInOut | 缓入缓出 |
Curve.FastOutSlowIn | 快出慢入 |
Curve.LinearOutSlowIn | 线性出缓入 |
Curve.FastOutLinearIn | 快出线性入 |
Curve.Decelerate | 减速 |
Curve.Bounce | 弹跳 |
Curve.Spring | 弹性 |
PlayMode 枚举值
| 枚举值 | 说明 |
|---|---|
PlayMode.Normal | 正常播放 |
PlayMode.Alternate | 往返播放 |
PlayMode.Reverse | 反向播放 |
PlayMode.AlternateReverse | 反向往返 |
代码示例
@State rotateAngle: number = 0
@State scaleValue: number = 1
@State opacityValue: number = 1
@State translateX: number = 0
@State widthValue: number = 100
@State heightValue: number = 100
@State bgColor: ResourceColor = '#007DFF'
// 旋转动画
Column() {
Text('旋转')
.width(100)
.height(100)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.rotate({ angle: this.rotateAngle })
.animation({
duration: 1000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
}
.onClick(() => {
this.rotateAngle = 360
})
// 缩放动画
Column() {
Text('缩放')
.width(100)
.height(100)
.backgroundColor('#4ECDC4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.scale({ x: this.scaleValue, y: this.scaleValue })
.animation({
duration: 500,
curve: Curve.Spring,
iterations: -1,
playMode: PlayMode.Alternate
})
}
.onClick(() => {
this.scaleValue = this.scaleValue === 1 ? 1.5 : 1
})
// 位移动画
Column() {
Text('位移')
.width(100)
.height(100)
.backgroundColor('#FF6B6B')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.translate({ x: this.translateX })
.animation({
duration: 800,
curve: Curve.EaseInOut,
playMode: PlayMode.Normal
})
}
.onClick(() => {
this.translateX = this.translateX === 0 ? 200 : 0
})
// 组合动画
Column() {
Text('组合')
.width(this.widthValue)
.height(this.heightValue)
.backgroundColor(this.bgColor)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(this.widthValue / 2)
.animation({
duration: 1000,
curve: Curve.EaseInOut
})
}
.onClick(() => {
this.widthValue = this.widthValue === 100 ? 150 : 100
this.heightValue = this.heightValue === 100 ? 150 : 100
this.bgColor = this.bgColor === '#007DFF' ? '#FF6B6B' : '#007DFF'
})
// 透明度动画
Column() {
Text('淡入淡出')
.width(100)
.height(100)
.backgroundColor('#96CEB4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.opacity(this.opacityValue)
.animation({
duration: 1000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
}
.onAppear(() => {
this.opacityValue = 0.2
})显式动画 animateTo
显式动画通过 animateTo 函数实现,可以同时对多个组件的属性变化应用动画。
| 属性 | 类型 | 说明 |
|---|---|---|
duration | number | 动画时长 |
tempo | number | 动画速率 |
curve | Curve / ICurve | 动画曲线 |
delay | number | 延迟时间 |
iterations | number | 迭代次数 |
playMode | PlayMode | 播放模式 |
onFinish | () => void | 结束回调 |
代码示例
@State width1: number = 100
@State height1: number = 100
@State width2: number = 100
@State height2: number = 100
@State color1: ResourceColor = '#007DFF'
@State color2: ResourceColor = '#4ECDC4'
// 同时动画多个组件
Column({ space: 20 }) {
Text('组件1')
.width(this.width1)
.height(this.height1)
.backgroundColor(this.color1)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
Text('组件2')
.width(this.width2)
.height(this.height2)
.backgroundColor(this.color2)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
Button('触发动画')
.onClick(() => {
animateTo({
duration: 1000,
curve: Curve.EaseInOut,
onFinish: () => {
console.info('动画完成')
}
}, () => {
// 在闭包中修改状态,所有变化会同时动画
this.width1 = this.width1 === 100 ? 200 : 100
this.height1 = this.height1 === 100 ? 150 : 100
this.color1 = this.color1 === '#007DFF' ? '#FF6B6B' : '#007DFF'
this.width2 = this.width2 === 100 ? 150 : 100
this.height2 = this.height2 === 100 ? 200 : 100
this.color2 = this.color2 === '#4ECDC4' ? '#96CEB4' : '#4ECDC4'
})
})
}
// 链式动画
Button('链式动画')
.onClick(() => {
// 第一步动画
animateTo({ duration: 500 }, () => {
this.width1 = 200
})
// 第二步动画(延迟执行)
setTimeout(() => {
animateTo({ duration: 500 }, () => {
this.height1 = 200
})
}, 500)
})
// 弹性动画
Button('弹性动画')
.onClick(() => {
animateTo({
duration: 1000,
curve: curves.springCurve(100, 10, 80, 10) // 弹性曲线参数
}, () => {
this.width1 = this.width1 === 100 ? 250 : 100
})
})组件转场动画 transition
组件转场动画用于控制组件插入和移除时的动画效果。
| 属性 | 类型 | 说明 |
|---|---|---|
type | TransitionType | 转场类型 |
opacity | number | 透明度 |
translate | TranslateOptions | 位移 |
scale | ScaleOptions | 缩放 |
rotate | RotateOptions | 旋转 |
TransitionType 枚举值
| 枚举值 | 说明 |
|---|---|
TransitionType.All | 插入和移除都使用转场 |
TransitionType.Insert | 仅插入使用转场 |
TransitionType.Delete | 仅移除使用转场 |
代码示例
@State isShow: boolean = false
@State isShowList: boolean[] = [false, false, false]
// 基础转场动画
Column({ space: 20 }) {
Button(this.isShow ? '隐藏' : '显示')
.onClick(() => {
this.isShow = !this.isShow
})
if (this.isShow) {
Text('转场内容')
.width(200)
.height(100)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.transition(TransitionEffect.OPACITY) // 淡入淡出
}
}
// 位移动画
if (this.isShow) {
Text('从下方滑入')
.width(200)
.height(100)
.backgroundColor('#4ECDC4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.transition(TransitionEffect.translate({ y: 100 }))
}
// 缩放动画
if (this.isShow) {
Text('缩放进入')
.width(200)
.height(100)
.backgroundColor('#FF6B6B')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.transition(TransitionEffect.scale({ x: 0, y: 0 }))
}
// 组合转场
if (this.isShow) {
Text('组合效果')
.width(200)
.height(100)
.backgroundColor('#96CEB4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.transition(
TransitionEffect.asymmetric(
TransitionEffect.OPACITY.combine(TransitionEffect.translate({ x: 100 })),
TransitionEffect.OPACITY.combine(TransitionEffect.translate({ x: -100 }))
)
)
}
// 列表项转场
Column({ space: 8 }) {
Button('添加/移除')
.onClick(() => {
this.isShowList = [...this.isShowList, !this.isShowList[this.isShowList.length - 1]]
})
ForEach(this.isShowList, (isShow: boolean, index: number) => {
if (isShow) {
Text(`列表项 ${index + 1}`)
.width('100%')
.height(60)
.backgroundColor('#F0F0F0')
.textAlign(TextAlign.Center)
.transition(
TransitionEffect.asymmetric(
TransitionEffect.translate({ x: 100 }).combine(TransitionEffect.OPACITY),
TransitionEffect.translate({ x: -100 }).combine(TransitionEffect.OPACITY)
)
)
}
})
}页面转场动画 PageTransition
页面转场动画用于控制页面进入和退出时的动画效果。
| 属性 | 类型 | 说明 |
|---|---|---|
PageTransitionEnter | 页面进入转场 | 配置进入动画 |
PageTransitionExit | 页面退出转场 | 配置退出动画 |
代码示例
// PageOne.ets
@Entry
@Component
struct PageOne {
@State scale: number = 1
@State opacity: number = 1
// 页面进入转场
pageTransition() {
PageTransitionEnter({ type: RouteType.Push, duration: 300 })
.slide(SlideEffect.Right)
.opacity(0)
PageTransitionEnter({ type: RouteType.Pop, duration: 300 })
.slide(SlideEffect.Left)
.opacity(0)
// 页面退出转场
PageTransitionExit({ type: RouteType.Push, duration: 300 })
.slide(SlideEffect.Left)
.opacity(0)
PageTransitionExit({ type: RouteType.Pop, duration: 300 })
.slide(SlideEffect.Right)
.opacity(0)
}
build() {
Column() {
Text('页面一')
.fontSize(24)
Button('跳转到页面二')
.onClick(() => {
router.pushUrl({ url: 'pages/PageTwo' })
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
// PageTwo.ets
@Entry
@Component
struct PageTwo {
// 页面进入转场 - 从底部滑入
pageTransition() {
PageTransitionEnter({ type: RouteType.Push, duration: 300 })
.slide(SlideEffect.Bottom)
.opacity(0)
PageTransitionExit({ type: RouteType.Pop, duration: 300 })
.slide(SlideEffect.Bottom)
.opacity(0)
}
build() {
Column() {
Text('页面二')
.fontSize(24)
Button('返回')
.onClick(() => {
router.back()
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#F5F5F5')
}
}共享元素转场 sharedTransition
共享元素转场用于在两个页面之间对相同元素进行平滑过渡,实现"一镜到底"效果。
| 属性 | 类型 | 说明 |
|---|---|---|
id | string | 共享元素标识 |
options | sharedTransitionOptions | 转场配置 |
代码示例
// 页面一:列表页
@Entry
@Component
struct ListPage {
@State images: string[] = [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
'https://example.com/image3.jpg'
]
build() {
List() {
ForEach(this.images, (image: string, index: number) => {
ListItem() {
Image(image)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(8)
// 共享元素标识
.sharedTransition(`image_${index}`, {
duration: 500,
curve: Curve.EaseInOut,
delay: 0
})
.onClick(() => {
router.pushUrl({
url: 'pages/DetailPage',
params: { imageIndex: index, imageUrl: image }
})
})
}
})
}
.padding(16)
}
}
// 页面二:详情页
@Entry
@Component
struct DetailPage {
@State imageIndex: number = 0
@State imageUrl: string = ''
aboutToAppear() {
const params = router.getParams() as Record<string, string>
this.imageIndex = Number(params['imageIndex'])
this.imageUrl = params['imageUrl']
}
build() {
Column() {
Image(this.imageUrl)
.width('100%')
.height(300)
.objectFit(ImageFit.Cover)
// 相同的共享元素标识
.sharedTransition(`image_${this.imageIndex}`, {
duration: 500,
curve: Curve.EaseInOut,
delay: 0
})
.onClick(() => {
router.back()
})
Text('详情内容')
.fontSize(16)
.margin(16)
}
.width('100%')
.height('100%')
}
}路径动画 motionPath
路径动画用于让组件沿着指定路径运动。
| 属性 | 类型 | 说明 |
|---|---|---|
path | string | 运动路径(SVG path 格式) |
from | number | 起始比例(0-1) |
to | number | 结束比例(0-1) |
rotatable | boolean | 是否跟随路径旋转 |
代码示例
@State progress: number = 0
// 路径动画
Column() {
Button('开始路径动画')
.onClick(() => {
animateTo({
duration: 3000,
curve: Curve.Linear,
iterations: -1
}, () => {
this.progress = 1
})
})
// 沿着路径运动的组件
Text('运动')
.width(50)
.height(50)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(25)
.motionPath({
path: 'M100,200 C100,100 250,100 250,200 S400,300 400,200',
from: 0,
to: 1,
rotatable: true
})
.animation({ duration: 3000, curve: Curve.Linear, iterations: -1 })
}
.width('100%')
.height('100%')
// 自定义路径动画
Column() {
// 圆形路径
Text('圆')
.width(40)
.height(40)
.backgroundColor('#FF6B6B')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(20)
.motionPath({
path: 'M200,200 m-100,0 a100,100 0 1,0 200,0 a100,100 0 1,0 -200,0',
from: 0,
to: 1,
rotatable: false
})
}手势系统
点击手势 TapGesture
TapGesture 用于识别点击操作,支持单击、双击和多击。
| 属性 | 类型 | 说明 |
|---|---|---|
count | number | 点击次数(默认 1) |
fingers | number | 手指数量(默认 1) |
代码示例
@State tapCount: number = 0
@State doubleTapCount: number = 0
// 单击手势
Text('单击我')
.width(200)
.height(100)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
TapGesture({ count: 1 })
.onAction((event: GestureEvent) => {
this.tapCount++
console.info(`单击次数: ${this.tapCount}`)
console.info(`点击位置: x=${event.fingerList[0].localX}, y=${event.fingerList[0].localY}`)
})
)
// 双击手势
Text('双击我')
.width(200)
.height(100)
.backgroundColor('#4ECDC4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
TapGesture({ count: 2 })
.onAction(() => {
this.doubleTapCount++
console.info(`双击次数: ${this.doubleTapCount}`)
})
)
// 多指点击
Text('双指点击')
.width(200)
.height(100)
.backgroundColor('#FF6B6B')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
TapGesture({ count: 1, fingers: 2 })
.onAction(() => {
console.info('双指点击')
})
)长按手势 LongPressGesture
LongPressGesture 用于识别长按操作。
| 属性 | 类型 | 说明 |
|---|---|---|
fingers | number | 手指数量 |
repeat | boolean | 是否重复触发 |
duration | number | 长按触发时长(ms,默认 500) |
代码示例
@State isLongPress: boolean = false
@State pressDuration: number = 0
// 基础长按
Text('长按我')
.width(200)
.height(100)
.backgroundColor(this.isLongPress ? '#FF6B6B' : '#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
LongPressGesture({ duration: 500 })
.onAction((event: GestureEvent) => {
this.isLongPress = true
console.info('长按触发')
})
.onActionEnd(() => {
this.isLongPress = false
console.info('长按结束')
})
)
// 重复触发长按
Text('持续长按')
.width(200)
.height(100)
.backgroundColor('#96CEB4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
LongPressGesture({ repeat: true, duration: 500 })
.onAction((event: GestureEvent) => {
this.pressDuration += 500
console.info(`长按时长: ${this.pressDuration}ms`)
})
)拖动手势 PanGesture
PanGesture 用于识别拖拽操作。
| 属性 | 类型 | 说明 |
|---|---|---|
fingers | number | 手指数量 |
direction | PanDirection | 拖动方向 |
distance | number | 最小识别距离(vp) |
PanDirection 枚举值
| 枚举值 | 说明 |
|---|---|
PanDirection.Left | 向左 |
PanDirection.Right | 向右 |
PanDirection.Up | 向上 |
PanDirection.Down | 向下 |
PanDirection.Horizontal | 水平方向 |
PanDirection.Vertical | 垂直方向 |
PanDirection.All | 所有方向 |
代码示例
@State offsetX: number = 0
@State offsetY: number = 0
@State positionX: number = 100
@State positionY: number = 100
// 自由拖动
Text('拖动我')
.width(100)
.height(100)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.position({ x: this.positionX, y: this.positionY })
.gesture(
PanGesture({ fingers: 1, direction: PanDirection.All })
.onActionStart((event: GestureEvent) => {
console.info('拖动开始')
})
.onActionUpdate((event: GestureEvent) => {
this.offsetX = event.offsetX
this.offsetY = event.offsetY
this.positionX += event.offsetX
this.positionY += event.offsetY
})
.onActionEnd((event: GestureEvent) => {
console.info('拖动结束')
this.offsetX = 0
this.offsetY = 0
})
)
// 水平拖动
Text('水平拖动')
.width(200)
.height(60)
.backgroundColor('#4ECDC4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.translate({ x: this.offsetX })
.gesture(
PanGesture({ direction: PanDirection.Horizontal })
.onActionUpdate((event: GestureEvent) => {
this.offsetX = event.offsetX
})
.onActionEnd(() => {
// 回弹效果
animateTo({ duration: 300, curve: Curve.Spring }, () => {
this.offsetX = 0
})
})
)捏合手势 PinchGesture
PinchGesture 用于识别双指捏合操作,常用于缩放。
| 属性 | 类型 | 说明 |
|---|---|---|
fingers | number | 手指数量(默认 2) |
distance | number | 最小识别距离 |
代码示例
@State scale: number = 1
@State pinchValue: number = 1
// 图片缩放
Image($r('app.media.photo'))
.width(300)
.height(300)
.objectFit(ImageFit.Cover)
.scale({ x: this.scale, y: this.scale })
.gesture(
PinchGesture()
.onActionStart((event: GestureEvent) => {
console.info('捏合开始')
})
.onActionUpdate((event: GestureEvent) => {
this.scale = this.pinchValue * event.scale
})
.onActionEnd(() => {
this.pinchValue = this.scale
// 限制缩放范围
if (this.scale < 0.5) {
animateTo({ duration: 300 }, () => {
this.scale = 0.5
this.pinchValue = 0.5
})
} else if (this.scale > 3) {
animateTo({ duration: 300 }, () => {
this.scale = 3
this.pinchValue = 3
})
}
})
)旋转手势 RotationGesture
RotationGesture 用于识别双指旋转操作。
| 属性 | 类型 | 说明 |
|---|---|---|
fingers | number | 手指数量(默认 2) |
angle | number | 最小识别角度 |
代码示例
@State rotateAngle: number = 0
@State currentAngle: number = 0
// 图片旋转
Image($r('app.media.photo'))
.width(300)
.height(300)
.objectFit(ImageFit.Cover)
.rotate({ angle: this.rotateAngle })
.gesture(
RotationGesture()
.onActionStart((event: GestureEvent) => {
console.info('旋转开始')
})
.onActionUpdate((event: GestureEvent) => {
this.rotateAngle = this.currentAngle + event.angle
})
.onActionEnd(() => {
this.currentAngle = this.rotateAngle
})
)滑动手势 SwipeGesture
SwipeGesture 用于识别快速滑动操作。
| 属性 | 类型 | 说明 |
|---|---|---|
fingers | number | 手指数量 |
direction | SwipeDirection | 滑动方向 |
speed | number | 最小识别速度(vp/s) |
SwipeDirection 枚举值
| 枚举值 | 说明 |
|---|---|
SwipeDirection.Horizontal | 水平方向 |
SwipeDirection.Vertical | 垂直方向 |
SwipeDirection.All | 所有方向 |
SwipeDirection.Left | 向左 |
SwipeDirection.Right | 向右 |
SwipeDirection.Up | 向上 |
SwipeDirection.Down | 向下 |
代码示例
@State swipeResult: string = ''
// 滑动识别
Text(`滑动方向: ${this.swipeResult}`)
.width(300)
.height(200)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
SwipeGesture({ direction: SwipeDirection.All, speed: 100 })
.onAction((event: GestureEvent) => {
const angle = event.angle
if (angle >= -45 && angle < 45) {
this.swipeResult = '向右滑动'
} else if (angle >= 45 && angle < 135) {
this.swipeResult = '向下滑动'
} else if (angle >= -135 && angle < -45) {
this.swipeResult = '向上滑动'
} else {
this.swipeResult = '向左滑动'
}
console.info(`滑动速度: ${event.speed} vp/s`)
})
)
// 左滑删除示例
List() {
ForEach(this.items, (item: string, index: number) => {
ListItem() {
Text(item)
.width('100%')
.height(60)
.backgroundColor('#F5F5F5')
}
.gesture(
SwipeGesture({ direction: SwipeDirection.Left, speed: 100 })
.onAction(() => {
this.items.splice(index, 1)
})
)
})
}组合手势 GestureGroup
GestureGroup 用于将多个手势组合在一起,支持顺序识别、并行识别和互斥识别。
| 模式 | 说明 |
|---|---|
GestureMode.Sequence | 顺序识别,按顺序触发 |
GestureMode.Parallel | 并行识别,同时触发 |
GestureMode.Exclusive | 互斥识别,只触发一个 |
代码示例
@State scale: number = 1
@State rotateAngle: number = 0
@State offsetX: number = 0
@State offsetY: number = 0
// 顺序手势:长按后拖动
Text('长按后拖动')
.width(200)
.height(100)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.translate({ x: this.offsetX, y: this.offsetY })
.gesture(
GestureGroup(GestureMode.Sequence,
LongPressGesture({ duration: 500 }),
PanGesture()
.onActionUpdate((event: GestureEvent) => {
this.offsetX += event.offsetX
this.offsetY += event.offsetY
})
)
.onCancel(() => {
console.info('手势序列取消')
})
)
// 并出手势:缩放和旋转同时进行
Image($r('app.media.photo'))
.width(300)
.height(300)
.objectFit(ImageFit.Cover)
.scale({ x: this.scale, y: this.scale })
.rotate({ angle: this.rotateAngle })
.gesture(
GestureGroup(GestureMode.Parallel,
PinchGesture()
.onActionUpdate((event: GestureEvent) => {
this.scale = event.scale
}),
RotationGesture()
.onActionUpdate((event: GestureEvent) => {
this.rotateAngle = event.angle
})
)
)
// 互斥手势:单击或长按
Text('单击或长按')
.width(200)
.height(100)
.backgroundColor('#4ECDC4')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.gesture(
GestureGroup(GestureMode.Exclusive,
TapGesture()
.onAction(() => {
console.info('单击触发')
}),
LongPressGesture({ duration: 500 })
.onAction(() => {
console.info('长按触发')
})
)
)绘制与自定义
Canvas 绘制
Canvas 组件用于自定义绘制图形、文本、图片等。
| 属性 | 类型 | 说明 |
|---|---|---|
context | CanvasRenderingContext2D | 2D 绘制上下文 |
CanvasRenderingContext2D 常用方法
| 方法 | 说明 |
|---|---|
fillRect(x, y, w, h) | 填充矩形 |
strokeRect(x, y, w, h) | 描边矩形 |
clearRect(x, y, w, h) | 清除矩形区域 |
beginPath() | 开始路径 |
moveTo(x, y) | 移动到 |
lineTo(x, y) | 连线到 |
arc(x, y, r, startAngle, endAngle) | 绘制圆弧 |
fill() | 填充路径 |
stroke() | 描边路径 |
fillText(text, x, y) | 填充文本 |
drawImage(image, x, y, w, h) | 绘制图片 |
代码示例
// 基础 Canvas 绘制
@Entry
@Component
struct CanvasDemo {
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(
new RenderingContextSettings(true)
)
build() {
Column({ space: 20 }) {
// 绘制矩形
Canvas(this.context)
.width(300)
.height(200)
.backgroundColor('#F5F5F5')
.onReady(() => {
// 填充矩形
this.context.fillStyle = '#007DFF'
this.context.fillRect(20, 20, 100, 80)
// 描边矩形
this.context.strokeStyle = '#FF6B6B'
this.context.lineWidth = 3
this.context.strokeRect(140, 20, 100, 80)
// 清除区域
this.context.clearRect(50, 40, 60, 40)
})
// 绘制路径
Canvas(this.context)
.width(300)
.height(200)
.backgroundColor('#F5F5F5')
.onReady(() => {
// 绘制三角形
this.context.beginPath()
this.context.moveTo(150, 30)
this.context.lineTo(100, 150)
this.context.lineTo(200, 150)
this.context.closePath()
this.context.fillStyle = '#4ECDC4'
this.context.fill()
// 绘制圆形
this.context.beginPath()
this.context.arc(80, 100, 40, 0, 2 * Math.PI)
this.context.fillStyle = '#FF6B6B'
this.context.fill()
this.context.strokeStyle = '#333333'
this.context.lineWidth = 2
this.context.stroke()
})
// 绘制文本
Canvas(this.context)
.width(300)
.height(100)
.backgroundColor('#F5F5F5')
.onReady(() => {
this.context.font = '24px sans-serif'
this.context.fillStyle = '#333333'
this.context.fillText('Hello Canvas', 20, 40)
this.context.font = '16px sans-serif'
this.context.fillStyle = '#666666'
this.context.fillText('ArkUI 自定义绘制', 20, 70)
})
// 绘制渐变
Canvas(this.context)
.width(300)
.height(100)
.backgroundColor('#F5F5F5')
.onReady(() => {
const gradient = this.context.createLinearGradient(0, 0, 300, 0)
gradient.addColorStop(0, '#007DFF')
gradient.addColorStop(0.5, '#4ECDC4')
gradient.addColorStop(1, '#FF6B6B')
this.context.fillStyle = gradient
this.context.fillRect(0, 0, 300, 100)
})
}
.padding(16)
}
}Shape 形状绘制
Shape 组件提供了预定义的形状绘制能力。
| 形状 | 说明 |
|---|---|
Rect | 矩形 |
Circle | 圆形 |
Ellipse | 椭圆 |
Path | 路径 |
Line | 直线 |
Polyline | 折线 |
Polygon | 多边形 |
代码示例
// 矩形
Rect()
.width(100)
.height(80)
.fill('#007DFF')
.stroke('#333333')
.strokeWidth(2)
.radius(8) // 圆角
// 圆形
Circle()
.width(100)
.height(100)
.fill('#4ECDC4')
.stroke('#333333')
.strokeWidth(2)
// 椭圆
Ellipse()
.width(150)
.height(100)
.fill('#FF6B6B')
.stroke('#333333')
.strokeWidth(2)
// 路径
Path()
.width(100)
.height(100)
.commands('M0 0 L100 0 L50 100 Z')
.fill('#96CEB4')
.stroke('#333333')
.strokeWidth(2)
// 直线
Line()
.width(100)
.height(2)
.startPoint([0, 0])
.endPoint([100, 0])
.stroke('#333333')
.strokeWidth(2)
// 组合形状
Shape() {
Rect()
.width(100)
.height(80)
.fill('#007DFF')
.radius(8)
Circle()
.width(40)
.height(40)
.fill('#FFFFFF')
.offset({ x: 30, y: 20 })
}
.width(100)
.height(80)自定义组件生命周期
自定义组件拥有完整的生命周期,可以在不同阶段执行特定逻辑。
| 生命周期 | 说明 |
|---|---|
aboutToAppear() | 组件即将出现时调用 |
aboutToDisappear() | 组件即将销毁时调用 |
onPageShow() | 页面显示时调用 |
onPageHide() | 页面隐藏时调用 |
onBackPress() | 用户点击返回键时调用 |
代码示例
@Entry
@Component
struct LifecycleDemo {
@State message: string = ''
aboutToAppear() {
// 组件创建时初始化数据
this.message = '组件已创建'
console.info('aboutToAppear: 组件即将出现')
}
aboutToDisappear() {
// 组件销毁时清理资源
console.info('aboutToDisappear: 组件即将销毁')
}
onPageShow() {
// 页面显示时刷新数据
console.info('onPageShow: 页面显示')
}
onPageHide() {
// 页面隐藏时暂停操作
console.info('onPageHide: 页面隐藏')
}
onBackPress() {
// 返回键处理
console.info('onBackPress: 用户点击返回')
return false // 返回 false 允许默认返回行为
}
build() {
Column() {
Text(this.message)
.fontSize(20)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}@BuilderParam 构建参数
@BuilderParam 用于在自定义组件中接收外部传入的 UI 构建函数,实现组件的高度可定制。
代码示例
// 定义带构建参数的自定义组件
@Component
struct Card {
@BuilderParam header: () => void
@BuilderParam content: () => void
@BuilderParam footer: () => void = this.DefaultFooter
@Builder
DefaultFooter() {
Text('默认底部')
.fontSize(12)
.fontColor('#999999')
}
build() {
Column({ space: 12 }) {
// 头部区域
if (this.header) {
this.header()
}
// 内容区域
if (this.content) {
this.content()
}
// 底部区域
if (this.footer) {
this.footer()
}
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: '#1F000000', offsetX: 0, offsetY: 2 })
}
}
// 使用自定义组件
@Entry
@Component
struct BuilderParamDemo {
@Builder
CustomHeader() {
Row() {
Image($r('app.media.avatar'))
.width(40)
.height(40)
.borderRadius(20)
Column({ space: 4 }) {
Text('用户名')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text('2小时前')
.fontSize(12)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
}
.width('100%')
}
@Builder
CustomContent() {
Text('这是卡片的内容区域,可以包含任意内容。')
.fontSize(14)
.fontColor('#333333')
.lineHeight(20)
}
@Builder
CustomFooter() {
Row({ space: 16 }) {
Text('点赞')
.fontSize(14)
.fontColor('#666666')
Text('评论')
.fontSize(14)
.fontColor('#666666')
Text('分享')
.fontSize(14)
.fontColor('#666666')
}
}
build() {
Column({ space: 16 }) {
// 使用自定义头部和内容
Card({
header: this.CustomHeader,
content: this.CustomContent,
footer: this.CustomFooter
})
// 只使用内容,使用默认底部
Card({
content: () => {
Text('简化卡片')
.fontSize(16)
}
})
}
.padding(16)
.backgroundColor('#F5F5F5')
}
}@CustomDialog 自定义弹窗
@CustomDialog 用于创建自定义弹窗组件。
| 属性 | 类型 | 说明 |
|---|---|---|
controller | CustomDialogController | 弹窗控制器 |
autoCancel | boolean | 点击遮罩是否关闭 |
alignment | DialogAlignment | 弹窗对齐方式 |
offset | Offset | 偏移量 |
customStyle | boolean | 是否自定义样式 |
maskColor | ResourceColor | 遮罩颜色 |
代码示例
// 自定义弹窗组件
@CustomDialog
struct ConfirmDialog {
controller: CustomDialogController
title: string = '提示'
message: string = ''
confirmText: string = '确定'
cancelText: string = '取消'
onConfirm: () => void = () => {}
onCancel: () => void = () => {}
build() {
Column({ space: 20 }) {
Text(this.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(this.message)
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
Row({ space: 16 }) {
Button(this.cancelText)
.width(100)
.backgroundColor('#F0F0F0')
.fontColor('#666666')
.onClick(() => {
this.onCancel()
this.controller.close()
})
Button(this.confirmText)
.width(100)
.backgroundColor('#007DFF')
.onClick(() => {
this.onConfirm()
this.controller.close()
})
}
}
.width(280)
.padding(24)
.backgroundColor(Color.White)
.borderRadius(12)
}
}
// 使用自定义弹窗
@Entry
@Component
struct DialogDemo {
dialogController: CustomDialogController = new CustomDialogController({
builder: ConfirmDialog({
title: '确认删除',
message: '确定要删除这条记录吗?删除后无法恢复。',
confirmText: '删除',
cancelText: '取消',
onConfirm: () => {
console.info('用户确认删除')
},
onCancel: () => {
console.info('用户取消删除')
}
}),
autoCancel: true,
alignment: DialogAlignment.Center,
maskColor: '#80000000'
})
// 底部弹窗控制器
bottomDialogController: CustomDialogController = new CustomDialogController({
builder: ConfirmDialog({
title: '选择操作',
message: '请选择要执行的操作',
confirmText: '确认',
cancelText: '关闭'
}),
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -20 }
})
build() {
Column({ space: 20 }) {
Button('显示确认弹窗')
.onClick(() => {
this.dialogController.open()
})
Button('显示底部弹窗')
.onClick(() => {
this.bottomDialogController.open()
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}样式系统
通用属性
尺寸与位置
| 属性 | 类型 | 说明 |
|---|---|---|
width | Length | 宽度 |
height | Length | 高度 |
size | { width: Length, height: Length } | 同时设置宽高 |
padding | Length / Padding | 内边距 |
margin | Length / Margin | 外边距 |
layoutWeight | number | 布局权重 |
constraintSize | ConstraintSizeOptions | 约束尺寸 |
aspectRatio | number | 宽高比 |
位置属性
| 属性 | 类型 | 说明 |
|---|---|---|
position | Position | 绝对定位 |
offset | Position | 相对偏移 |
markAnchor | Position | 锚点标记 |
align | Alignment | 对齐方式 |
alignRules | AlignRuleOption | 相对布局规则 |
变换属性
| 属性 | 类型 | 说明 |
|---|---|---|
translate | TranslateOptions | 平移变换 |
scale | ScaleOptions | 缩放变换 |
rotate | RotateOptions | 旋转变换 |
transform | Matrix4 | 矩阵变换 |
背景属性
| 属性 | 类型 | 说明 |
|---|---|---|
backgroundColor | ResourceColor | 背景颜色 |
backgroundImage | string / Resource | 背景图片 |
backgroundImageSize | ImageSize / SizeOptions | 背景图片尺寸 |
backgroundImagePosition | Alignment / Position | 背景图片位置 |
linearGradient | LinearGradientOptions | 线性渐变 |
radialGradient | RadialGradientOptions | 径向渐变 |
sweepGradient | SweepGradientOptions | 扫描渐变 |
边框属性
| 属性 | 类型 | 说明 |
|---|---|---|
border | BorderOptions | 边框 |
borderRadius | Length / BorderRadiuses | 圆角 |
borderWidth | Length / EdgeWidths | 边框宽度 |
borderColor | ResourceColor / EdgeColors | 边框颜色 |
borderStyle | BorderStyle / EdgeStyles | 边框样式 |
阴影与效果
| 属性 | 类型 | 说明 |
|---|---|---|
shadow | ShadowOptions | 阴影 |
blur | number | 模糊度 |
backdropBlur | number | 背景模糊 |
clip | boolean / Shape | 裁剪 |
mask | ProgressMask | 遮罩 |
代码示例
// 尺寸与位置
Text('尺寸示例')
.width(200)
.height(60)
.layoutWeight(1)
.constraintSize({ minWidth: 100, maxWidth: 300 })
// 位置示例
Text('绝对定位')
.width(100)
.height(40)
.position({ x: 50, y: 100 })
Text('相对偏移')
.width(100)
.height(40)
.offset({ x: 20, y: 10 })
// 变换示例
Text('缩放')
.scale({ x: 1.5, y: 1.5 })
Text('旋转')
.rotate({ angle: 45, centerX: '50%', centerY: '50%' })
Text('位移')
.translate({ x: 50, y: 20 })
// 背景示例
Column()
.width(200)
.height(100)
.backgroundColor('#007DFF')
Column()
.width(200)
.height(100)
.linearGradient({
angle: 90,
colors: [['#007DFF', 0], ['#4ECDC4', 1]]
})
Column()
.width(200)
.height(100)
.radialGradient({
center: ['50%', '50%'],
radius: '50%',
colors: [['#FF6B6B', 0], ['#4ECDC4', 1]]
})
// 边框示例
Text('圆角边框')
.width(150)
.height(50)
.border({ width: 2, color: '#007DFF', radius: 25 })
Text('单边边框')
.width(150)
.height(50)
.borderWidth({ bottom: 2 })
.borderColor({ bottom: '#007DFF' })
// 阴影示例
Column()
.width(200)
.height(100)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({
radius: 8,
color: '#40000000',
offsetX: 0,
offsetY: 4
})
// 裁剪示例
Image($r('app.media.photo'))
.width(100)
.height(100)
.clip(true) // 裁剪为矩形
.borderRadius(50) // 圆形裁剪
Image($r('app.media.photo'))
.width(100)
.height(100)
.clip(new Circle({ width: 100, height: 100 })) // 圆形裁剪响应式布局
媒体查询
媒体查询用于根据设备特征应用不同的样式。
| 特征 | 说明 |
|---|---|
width / height | 视口宽高 |
min-width / min-height | 最小宽高 |
max-width / max-height | 最大宽高 |
orientation | 屏幕方向 |
device-type | 设备类型 |
round-screen | 是否圆形屏幕 |
dark-mode | 是否深色模式 |
断点系统
断点系统将屏幕宽度划分为不同范围,根据断点调整布局。
| 断点 | 范围 | 设备类型 |
|---|---|---|
xs | < 320vp | 超小屏 |
sm | 320vp - 520vp | 小屏(手机) |
md | 520vp - 840vp | 中屏(折叠屏展开) |
lg | 840vp - 1280vp | 大屏(平板) |
xl | >= 1280vp | 超大屏(2in1) |
代码示例
// 媒体查询
@Entry
@Component
struct MediaQueryDemo {
@State isWide: boolean = false
listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(min-width: 600vp)')
aboutToAppear() {
this.listener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => {
this.isWide = mediaQueryResult.matches
})
}
aboutToDisappear() {
this.listener.off('change')
}
build() {
if (this.isWide) {
// 宽屏布局
Row({ space: 16 }) {
Column() {
Text('侧边栏')
.width(200)
.height('100%')
.backgroundColor('#F0F0F0')
}
Column() {
Text('主内容区')
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
} else {
// 窄屏布局
Column() {
Text('主内容区')
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
.width('100%')
.height('100%')
}
}
}
// 断点系统
@Entry
@Component
struct BreakpointDemo {
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
build() {
GridRow({
columns: { xs: 1, sm: 2, md: 4, lg: 6 },
gutter: { x: 12, y: 12 }
}) {
ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
GridCol({
span: { xs: 1, sm: 1, md: 2, lg: 2 }
}) {
Text(`项目 ${item}`)
.width('100%')
.height(100)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
})
}
.padding(16)
}
}
// 响应式尺寸
@Entry
@Component
struct ResponsiveSizeDemo {
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
getFontSize(): number {
switch (this.currentBreakpoint) {
case 'xs':
case 'sm':
return 14
case 'md':
return 16
case 'lg':
case 'xl':
return 18
default:
return 14
}
}
getPadding(): number {
switch (this.currentBreakpoint) {
case 'xs':
case 'sm':
return 12
case 'md':
return 16
case 'lg':
case 'xl':
return 24
default:
return 12
}
}
build() {
Column() {
Text('响应式文本')
.fontSize(this.getFontSize())
.padding(this.getPadding())
}
.width('100%')
.height('100%')
}
}主题和暗色模式
ArkUI 支持主题系统和暗色模式适配。
| 属性 | 说明 |
|---|---|
@ohos.arkui.theme | 主题管理 |
Theme | 主题对象 |
WithTheme | 作用域主题 |
代码示例
// 暗色模式适配
@Entry
@Component
struct DarkModeDemo {
@StorageProp('currentColorMode') currentColorMode: number = 0
getBgColor(): ResourceColor {
return this.currentColorMode === 0 ? '#FFFFFF' : '#1A1A1A'
}
getTextColor(): ResourceColor {
return this.currentColorMode === 0 ? '#333333' : '#FFFFFF'
}
getCardBgColor(): ResourceColor {
return this.currentColorMode === 0 ? '#F5F5F5' : '#2A2A2A'
}
build() {
Column({ space: 16 }) {
Text('暗色模式适配')
.fontSize(20)
.fontColor(this.getTextColor())
Column() {
Text('卡片内容')
.fontSize(14)
.fontColor(this.getTextColor())
}
.width('100%')
.height(100)
.backgroundColor(this.getCardBgColor())
.borderRadius(8)
.padding(16)
Text(`当前模式: ${this.currentColorMode === 0 ? '浅色' : '深色'}`)
.fontSize(14)
.fontColor(this.getTextColor())
}
.width('100%')
.height('100%')
.backgroundColor(this.getBgColor())
.padding(16)
}
}
// 使用系统颜色资源
@Entry
@Component
struct SystemColorDemo {
build() {
Column({ space: 16 }) {
// 使用系统颜色,自动适配深浅色模式
Text('主要文本')
.fontColor($r('sys.color.font_primary'))
Text('次要文本')
.fontColor($r('sys.color.font_secondary'))
Column() {
Text('强调色背景')
.fontColor($r('sys.color.font_on_primary'))
}
.width(200)
.height(50)
.backgroundColor($r('sys.color.background_primary'))
.borderRadius(8)
.justifyContent(FlexAlign.Center)
Column() {
Text('卡片背景')
}
.width(200)
.height(50)
.backgroundColor($r('sys.color.background_secondary'))
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.padding(16)
}
}
// 自定义主题
@Entry
@Component
struct CustomThemeDemo {
@State primaryColor: ResourceColor = '#007DFF'
@State secondaryColor: ResourceColor = '#4ECDC4'
build() {
WithTheme({ theme: {
colors: {
brand: this.primaryColor,
warning: '#FF6B6B',
success: '#4ECDC4'
}
}}) {
Column({ space: 16 }) {
Button('主要按钮')
.backgroundColor($r('sys.color.brand'))
Button('成功按钮')
.backgroundColor($r('sys.color.success'))
Button('警告按钮')
.backgroundColor($r('sys.color.warning'))
}
.padding(16)
}
}
}条件渲染
@State isLogin: boolean = false
@State isLoading: boolean = true
build() {
Column() {
if (this.isLoading) {
LoadingProgress()
.width(50)
.height(50)
} else if (this.isLogin) {
Text('欢迎回来')
.fontSize(20)
} else {
Button('登录')
.onClick(() => {
this.isLogin = true
})
}
}
}列表渲染
@State items: string[] = ['苹果', '香蕉', '橙子', '葡萄']
build() {
List({ space: 8 }) {
ForEach(this.items, (item: string, index: number) => {
ListItem() {
Row() {
Text(`${index + 1}. ${item}`)
.fontSize(16)
Button('删除')
.fontSize(12)
.onClick(() => {
this.items.splice(index, 1)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
}
}, (item: string) => item)
}
.width('100%')
.divider({ strokeWidth: 1, color: '#EEEEEE' })
}样式复用
@Styles 通用样式
@Styles
function cardStyle() {
.padding(16)
.borderRadius(8)
.backgroundColor(Color.White)
.shadow({ radius: 4, color: '#1F000000', offsetX: 0, offsetY: 2 })
}
// 使用
Column() {
Text('卡片内容')
}
.cardStyle()
.width('90%')@Extend 组件扩展
@Extend(Text)
function title(size: number, color: ResourceColor) {
.fontSize(size)
.fontColor(color)
.fontWeight(FontWeight.Bold)
}
// 使用
Text('标题').title(24, '#333333')
Text('副标题').title(18, '#666666')@Builder UI 复用
@Builder
function InfoCard(title: string, content: string) {
Column() {
Text(title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(content)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8 })
}
.width('100%')
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
// 使用
build() {
Column() {
InfoCard('标题1', '内容1')
InfoCard('标题2', '内容2')
}
}单位系统
| 单位 | 说明 | 示例 |
|---|---|---|
| vp | 虚拟像素(默认) | width(100) |
| fp | 字体像素 | fontSize('16fp') |
| px | 物理像素 | width('100px') |
| lpx | 逻辑像素 | width('100lpx') |
| % | 百分比 | width('100%') |
数字默认使用 vp,百分比用字符串表示。