鸿蒙5:自定义构建函数_鸿蒙自定义函数
注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下
如果大家觉得博主文章写的好的话,可以点下关注,博主会一直更新鸿蒙next相关知识
专栏地址: https://blog.csdn.net/qq_56760790/category_12794123.html
文章所属类目(HarmonyOS语言-ArkTS)
目录
1. 自定义构建函数
1.1 构建函数-@Builder
1.2 构建函数-传参传递 (值传递和引用传递)
1.3 构建函数-传递参数练习-Tabs组件使用
1.4 构建函数-@BuilderParam 传递UI
1.5 @BuilderParam传值
1.6 尾随闭包
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@ComponentV2struct 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%\') }}@ComponentV2struct 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
📎图片.zip
- 封装一个列表的组件,可以渲染传入的数组,结构也是用户自己定义
@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@ComponentV2struct 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且不需要传参的时候,可以使用尾随闭包
注意:尾随闭包用空大括号就代表传递空内容,会替代默认内容