目录
1. 自定义构建函数
1.1 构建函数-@Builder
如果你不想在直接抽象组件,ArkUI还提供了一种更轻量的UI元素复用机制 @Builder
,可以将重复使用的UI元素抽象成一个方法,在 build
方法里调用。称之为自定义构建函数
只要使用Builder修饰符修饰的内容,都可以做成对应的UI描述
@Entry
@ComponentV2
struct BuilderCase {
@Local
list: string[] = ["A", "B","C", "D", "E", "F"]
@Builder
getItemBuilder (itemName: string) {
Row() {
Text(`${itemName}. 选项`)
}
.height(60)
.backgroundColor("#ffe0dede")
.borderRadius(8)
.width("100%")
.padding({
left: 20,
right: 20
})
}
build() {
Column({ space: 10 }) {
ForEach(this.list, (item: string) => {
this.getItemBuilder(item)
})
}
.padding(20)
}
}
用法- 使用@Builder修饰符修饰
@Entry
@ComponentV2
struct BuilderCase02 {
build() {
Column() {
Row() {
Row() {
Text("异常时间")
Text("2023-12-12")
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 15,
right: 15
})
.borderRadius(8)
.height(40)
.backgroundColor(Color.White)
}.padding({
left: 10,
right: 10
})
}
.width('100%')
.height('100%')
.backgroundColor('#ccc')
.padding({ top: 30 })
}
}
- 全局定义- @Builder function name () {}
@Entry
@ComponentV2
struct BuilderCase02 {
build() {
Column() {
Column({ space: 10 }) {
getCellContent("异常时间", "2023-12-12")
getCellContent("异常位置", "回龙观")
getCellContent("异常类型", "漏油")
}
}
.width('100%')
.height('100%')
.backgroundColor('#ccc')
.padding({ top: 30 })
}
}
@Builder
function getCellContent(leftTitle: string, rightValue: string) {
Row() {
Row() {
Text(leftTitle)
Text(rightValue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 15,
right: 15
})
.borderRadius(8)
.height(40)
.backgroundColor(Color.White)
}.padding({
left: 10,
right: 10
})
}
1.2 构建函数-传参传递 (值传递和引用传递)
调用@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。
按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。
@Builder
export function getCellContent(leftTitle: string, rightValue: string) {
Row() {
Row() {
Text(leftTitle)
Text(rightValue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 15,
right: 15
})
.borderRadius(8)
.height(40)
.backgroundColor(Color.White)
}.padding({
left: 10,
right: 10
})
}
interface ParamObj {
leftTitle: string
rightValue: string
}
@Builder
export function getCellContentObj(obj: ParamObj) {
Row() {
Row() {
Text(obj.leftTitle)
Text(obj.rightValue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 15,
right: 15
})
.borderRadius(8)
.height(40)
.backgroundColor(Color.White)
}.padding({
left: 10,
right: 10
})
}
@Entry
@ComponentV2
struct BuilderCase03 {
@Local area: string = '望京'
build() {
Column({ space: 20 }) {
getCellContent("异常时间", "2023-12-12")
getCellContent("异常位置", this.area)
getCellContent("异常类型", "漏油")
Divider()
getCellContentObj({
leftTitle: '异常位置',
rightValue: this.area
})
Button('修改数据').onClick(() => {
this.area = '黑龙江'
})
}
.width('100%')
.height('100%')
.backgroundColor('#ccc')
.padding({ top: 30 })
}
}
- 使用
@Builder
复用逻辑的时候,支持传参可以更灵活的渲染UI - 参数可以使用
状态数据
,不过需要建议通过对象的方式传入@Builder
注意问题:
当参数存在两个或者两个以上的时候,就算通过对象字面量的形式传递,值的改变也不会引起UI刷新。
1.3 构建函数-传递参数练习-Tabs组件使用
上图中,是tabs组件中的tabbar属性,支持自定义builder,意味着我们可以定制它的样式
- 准备八个图标放到资源目录下
- 新建一个页面, 声明一个interface并建立四个数据的状态
interface TabInterface {
name: string
icon: ResourceStr
selectIcon: ResourceStr
title: string
}
- 循环生成对应的TabContent
interface TabInterface {
name: string
icon: ResourceStr
selectIcon: ResourceStr
title: string
}
@Entry
@ComponentV2
struct TabBarDemo {
@Local list: TabInterface[] = [{
icon: $r("app.media.ic_public_message"),
selectIcon: $r('app.media.ic_public_message_filled'),
name: 'wechat',
title: '微信',
}, {
icon: $r('app.media.ic_public_contacts_group'),
selectIcon: $r('app.media.ic_public_contacts_group_filled'),
name: 'connect',
title: '联系人',
}, {
icon: $r('app.media.ic_gallery_discover'),
selectIcon: $r('app.media.ic_gallery_discover_filled'),
name: 'discover',
title: '发现',
}, {
icon: $r('app.media.ic_public_contacts'),
selectIcon: $r('app.media.ic_public_contacts_filled'),
name: 'my',
title: '我的',
}]
build() {
Tabs() {
ForEach(this.list, (item: TabInterface) => {
TabContent() {
Text(item.title)
}
.tabBar(item.title)
})
}
.barPosition(BarPosition.End)
}
}
此时,如果我们想实现图中对应的效果,就需要使用自定义Builder来做,因为TabContent的tabBar属性支持CustomBuilder类型,CustomBuilder类型就是builder修饰的函数
- 在当前组件中声明一个builder函数
@Builder
CommonTabBar (item: TabInterface) {
Column () {
Image(item.icon)
.width(20)
.height(20)
Text(item.title)
.fontSize(12)
.fontColor("#1AAD19")
.margin({
top: 5
})
}
}
interface TabInterface {
name: string
icon: ResourceStr
selectIcon: ResourceStr
title: string
}
@Entry
@ComponentV2
struct TabBarDemo {
@Local list: TabInterface[] = [{
icon: $r("app.media.ic_public_message"),
selectIcon: $r('app.media.ic_public_message_filled'),
name: 'wechat',
title: '微信',
}, {
icon: $r('app.media.ic_public_contacts_group'),
selectIcon: $r('app.media.ic_public_contacts_group_filled'),
name: 'connect',
title: '联系人',
}, {
icon: $r('app.media.ic_gallery_discover'),
selectIcon: $r('app.media.ic_gallery_discover_filled'),
name: 'discover',
title: '发现',
}, {
icon: $r('app.media.ic_public_contacts'),
selectIcon: $r('app.media.ic_public_contacts_filled'),
name: 'my',
title: '我的',
}]
@Builder
CommonTabBar (item: TabInterface) {
Column () {
Image(item.icon)
.width(20)
.height(20)
Text(item.title)
.fontSize(12)
.fontColor("#1AAD19")
.margin({
top: 5
})
}
}
build() {
Tabs() {
ForEach(this.list, (item: TabInterface) => {
TabContent() {
Text(item.title)
}
.tabBar(this.CommonTabBar(item))
})
}
.barPosition(BarPosition.End)
}
}
- 定义一个数据来绑定当前tabs的激活索引
@Local currentIndex: number = 0
- 根据当前激活索引设置不同的颜色的图标
@Builder CommonTabBar (item: TabInterface) {
Column() {
Image(item.name === this.list[this.currentIndex].name ? item.selectIcon : item.icon)
.width(20)
.height(20)
Text(item.title)
.fontSize(12)
.fontColor(item.name === this.list[this.currentIndex].name ? "#1AAD19" : "#2A2929")
.margin({
top: 5
})
}
}
1.4 构建函数-@BuilderParam 传递UI
插槽-Vue-Slot React-RenderProps
- 把UI结构体的函数(Builder修饰的函数)当成参数传入到组件中,让组件放入固定的位置去渲染
- 子组件接收传入的函数的修饰符/装饰器叫做BuilderParam
- BuilderParam的基本使用 - 如何实现定制化Header?
使用BuilderParam的步骤
- 前提:需要出现父子组件的关系
- 前提:BuilderParam应出现在子组件中
- 1. 子组件声明 @BuilderParam getConent: () => void
- 2. BuilderParam的参数可以不给初始值,如果给了初始值, 就是默认内容
- 3. 父组件传入的时候,它需要用builder修饰的函数又或者是 一个箭头函数中包裹着
- 4. 调用builder函数的逻辑
- 封装子组件
@Entry
@ComponentV2
struct BuildParamCase {
build() {
Column() {
// Header容器
ChildHeader()
}
.width('100%')
}
}
@ComponentV2
struct ChildHeader {
build() {
Row() {
// 左
Text('返回')
// 中
Text('首页')
.layoutWeight(1)
.textAlign(TextAlign.Center)
// 右
Text('确定')
}
.width('100%')
.backgroundColor(Color.Pink)
.padding(20)
}
}
- 子组件中定义 @BuilderParam 接受结构参数
@Entry
@ComponentV2
struct BuildParamCase {
@Builder
LeftBuilder() {
Image($r('sys.media.ohos_ic_compnent_titlebar_back'))
.width(20)
}
@Builder
CenterBuilder() {
Row(){
Text('最新推荐')
Text('🔥')
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
@Builder
RightBuilder(){
Image($r('sys.media.ohos_ic_public_scan'))
.width(20)
}
build() {
Column() {
// Header容器
ChildHeader({
leftContent: this.LeftBuilder,
centerContent: this.CenterBuilder,
rightContent: this.RightBuilder
})
}
.width('100%')
}
}
@ComponentV2
struct ChildHeader {
@BuilderParam leftContent: ()=>void
@BuilderParam centerContent: ()=>void
@BuilderParam rightContent: ()=>void
build() {
Row() {
// 左
this.leftContent()
// 中
this.centerContent()
// 右
this.rightContent()
}
.width('100%')
.backgroundColor(Color.Pink)
.padding(20)
}
}
- 子组件中定义默认值
@Entry
@ComponentV2
struct BuildParamCase {
@Builder
LeftBuilder() {
Image($r('sys.media.ohos_ic_compnent_titlebar_back'))
.width(20)
}
@Builder
CenterBuilder() {
Row(){
Text('最新推荐')
Text('🔥')
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
@Builder
RightBuilder(){
Image($r('sys.media.ohos_ic_public_scan'))
.width(20)
}
build() {
Column() {
// Header容器
ChildHeader({
centerContent: this.CenterBuilder,
rightContent: this.RightBuilder
})
}
.width('100%')
}
}
@ComponentV2
struct ChildHeader {
@Builder leftDefault () {
Text('返回')
}
@BuilderParam leftContent: ()=>void = this.leftDefault
@BuilderParam centerContent: ()=>void
@BuilderParam rightContent: ()=>void
build() {
Row() {
// 左
this.leftContent()
// 中
this.centerContent()
// 右
this.rightContent()
}
.width('100%')
.backgroundColor(Color.Pink)
.padding(20)
}
}
1.5 @BuilderParam传值
场景:二次封装组件,可以用列表组件渲染数据 - 但是每一个选项的UI具体结构由调用者决定
- 拷贝图片到assets
- 封装一个列表的组件,可以渲染传入的数组,结构也是用户自己定义
@ComponentV2
struct GoodList {
@Param list: object[] = [] // 不知道传过来什么类型, 统一用object
@Builder
defaultRender() {
Text('默认结构')
}
@BuilderParam renderItem: (item: object) => void = this.defaultRender
build() {
List({ space: 10 }) {
ForEach(this.list, (item: object) => {
ListItem() {
this.renderItem(item)
}
})
}
.padding(20)
}
}
- 父组件调用
import GoodList from '../components/GoodList'
interface GoodItem {
goods_name: string
goods_price: number
goods_img: string
goods_count: number
id: number
}
@Entry
@ComponentV2
struct ListBuilder {
@Local list: GoodItem[] = [
{
"id": 1,
"goods_name": "班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣",
"goods_img": "assets/1.webp",
"goods_price": 108,
"goods_count": 1,
},
{
"id": 2,
"goods_name": "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
"goods_img": "assets/2.webp",
"goods_price": 129,
"goods_count": 1,
},
{
"id": 3,
"goods_name": "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
"goods_img": "assets/3.webp",
"goods_price": 198,
"goods_count": 1,
},
{
"id": 4,
"goods_name": "思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套",
"goods_img": "assets/4.webp",
"goods_price": 99,
"goods_count": 1,
},
{
"id": 5,
"goods_name": "幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮",
"goods_img": "assets/5.webp",
"goods_price": 156,
"goods_count": 1,
},
{
"id": 6,
"goods_name": "ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女",
"goods_img": "assets/6.webp",
"goods_price": 142.8,
"goods_count": 1,
},
{
"id": 7,
"goods_name": "幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套",
"goods_img": "assets/7.webp",
"goods_price": 219,
"goods_count": 2,
},
{
"id": 8,
"goods_name": "依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套",
"goods_img": "assets/8.webp",
"goods_price": 178,
"goods_count": 1,
},
{
"id": 9,
"goods_name": "芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬",
"goods_img": "assets/9.webp",
"goods_price": 128,
"goods_count": 1,
},
{
"id": 10,
"goods_name": "Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫",
"goods_img": "assets/10.webp",
"goods_price": 153,
"goods_count": 1,
}
]
@Builder
renderItem() {
Row({ space: 10 }) {
Image('assets/1.webp')
.borderRadius(8)
.width(120)
.height(200)
Column() {
Text('芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬')
.fontWeight(FontWeight.Bold)
Text("¥ 178")
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
}
.padding({
top: 5,
bottom: 5
})
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.height(200)
.layoutWeight(1)
}
.width('100%')
}
build() {
Column() {
GoodList({
list: this.list,
renderItem: () => {
this.renderItem()
}
})
}
.width('100%')
.height('100%')
}
}
- 父组件接受传值,渲染
import GoodList from '../components/GoodList'
interface GoodItem {
goods_name: string
goods_price: number
goods_img: string
goods_count: number
id: number
}
@Entry
@ComponentV2
struct ListBuilder {
@Local list: GoodItem[] = [
{
"id": 1,
"goods_name": "班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣",
"goods_img": "assets/1.webp",
"goods_price": 108,
"goods_count": 1,
},
{
"id": 2,
"goods_name": "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
"goods_img": "assets/2.webp",
"goods_price": 129,
"goods_count": 1,
},
{
"id": 3,
"goods_name": "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
"goods_img": "assets/3.webp",
"goods_price": 198,
"goods_count": 1,
},
{
"id": 4,
"goods_name": "思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套",
"goods_img": "assets/4.webp",
"goods_price": 99,
"goods_count": 1,
},
{
"id": 5,
"goods_name": "幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮",
"goods_img": "assets/5.webp",
"goods_price": 156,
"goods_count": 1,
},
{
"id": 6,
"goods_name": "ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女",
"goods_img": "assets/6.webp",
"goods_price": 142.8,
"goods_count": 1,
},
{
"id": 7,
"goods_name": "幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套",
"goods_img": "assets/7.webp",
"goods_price": 219,
"goods_count": 2,
},
{
"id": 8,
"goods_name": "依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套",
"goods_img": "assets/8.webp",
"goods_price": 178,
"goods_count": 1,
},
{
"id": 9,
"goods_name": "芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬",
"goods_img": "assets/9.webp",
"goods_price": 128,
"goods_count": 1,
},
{
"id": 10,
"goods_name": "Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫",
"goods_img": "assets/10.webp",
"goods_price": 153,
"goods_count": 1,
}
]
@Builder
renderItem (item: GoodItem) {
Row({ space: 10 }) {
Image(item.goods_img)
.borderRadius(8)
.width(120)
.height(200)
Column() {
Text(item.goods_name)
.fontWeight(FontWeight.Bold)
Text("¥ " + item.goods_price)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
}
.padding({
top: 5,
bottom: 5
})
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.height(200)
.layoutWeight(1)
}
.width('100%')
}
build() {
Column() {
GoodList({
list: this.list,
renderItem: (item: object) => {
this.renderItem(item as GoodItem)
}
})
}
.width('100%')
.height('100%')
}
}
1.6 尾随闭包
Column () { } 中大括号就是尾随闭包的写法
当我们的组件只有一个BuilderParam的时候,此时可以使用尾随闭包的语法
也就是像我们原来使用Column或者Row组件时一样,直接在大括号中传入
我们用尾随闭包来封装这样的组件,理解一下BuildParam的使用
首先封装一个Panel组件
@ComponentV2
struct PanelComp {
@Param leftText:string = '左侧标题'
@BuilderParam
rightContent:()=>void = this.defaultContent
@Builder
defaultContent(){
Row({space:16}){
Checkbox().select(true).shape(CheckBoxShape.CIRCLE)
Text('是')
}
}
build() {
Row(){
Text(this.leftText)
this.rightContent()
}
.width('100%')
.padding(20)
.backgroundColor('#ccc')
.borderRadius(8)
.justifyContent(FlexAlign.SpaceBetween)
}
}
export { PanelComp }
- 接下来父组件使用,并分别传递左侧文字和右侧的结构
import { PanelComp } from '../components/PanelComp'
@Entry
@ComponentV2
struct PanelPage {
@Local isOn: boolean = false
// @Builder rightContent() {
// Toggle({ type: ToggleType.Switch })
// }
build() {
Column() {
PanelComp({
leftText: '低电量模式',
// rightContent: this.rightContent
}) {
Toggle({
type: ToggleType.Switch,
isOn: $$this.isOn
})
}
Text(this.isOn.toString()).fontSize(20)
PanelComp({
leftText: '设置'
}) {
Text('查看详情 >')
}
}
.padding(20)
.width('100%')
.height('100%')
.backgroundColor(Color.Pink)
}
}
只有一个BuilderParam且不需要传参的时候,可以使用尾随闭包
注意:尾随闭包用空大括号就代表传递空内容,会替代默认内容