一、自定义构建函数
1.构建函数 @Builder
1.1 介绍
文档地址:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-builder-V5?catalogVersion=V5
概念:ArkUI提供了一种轻量的UI元素复用机制@Builder,可以将重复使用的UI元素抽象成一个方法。在
builder
里调用。
1.2 定义的位置
组件内定义
@Builder MyBuilderFcuntion(){
}
//调用
this.MyBuilderFcuntion()
全局定义
@Builder function MyBuilderFcuntion(){
}
//调用
MyBuilderFcuntion()
案例
鸿蒙页面布局,此处我们会用到栅格布局:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-layout-development-grid-layout-V5
待实现的页面结构
@Entry
@Component
struct BuilderDemo1Page {
build() {
Column(){ //垂直方向布局
//columns:分为几列,columns默认值为12
//gutter:设置子元素在水平和垂直方向的间距。
GridRow({columns:3,gutter:15}){
GridCol(){
Column(){
Row(){
Text('评价(200+)')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
}
Row(){
}
.height(100)
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
GridCol(){
Column(){
Row(){
Text('推荐')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
}
Row(){
}
.height(100)
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
GridCol(){
Column(){
Row(){
Text('体验')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
}
Row(){
}
.height(100)
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
}
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
.padding(15)
}
}
使用@Builder提取UI结构
@Entry
@Component
struct BuilderDemo1Page {
@Builder
MyBuilder(){
Row(){
Text('查看更多')
Image($r('app.media.chevron_right'))
.width(16)
.aspectRatio(1)
}
}
build() {
Column(){ //垂直方向布局
//columns:分为几列,columns默认值为12
//gutter:设置子元素在水平和垂直方向的间距。
GridRow({columns:2,gutter:15}){
GridCol({span:2}){ //span:将几个合并成一个
Column(){
Row(){
Text('评价(200+)')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
// 查看更多
this.MyBuilder()
}
Row(){
}
.height(100)
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
GridCol(){
Column(){
Row(){
Text('推荐')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
// 查看更多
this.MyBuilder()
}
Row(){
}
.height(100)
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
GridCol(){
Column(){
Row(){
Text('体验')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
// 查看更多
this.MyBuilder()
}
Row(){
}
.height(100)
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
}
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
.padding(15)
}
}
小结
遇到非遍历情况下,一个组件分散着相同的ui结构,可以使用@Builder加上更轻量级的GridRow和GirdCol栅格布局
1.3 参数传递
场景:构建不同的UI
@Builder MyBuilderFcuntion(title:string){
}
//调用
this.MyBuilderFcuntion('Title')
//两个位置变动
@Builder
MyBuilder(title:string){
Row(){
Text(title)
Image($r('app.media.chevron_right'))
.width(16)
.aspectRatio(1)
}
}
// 查看更多
this.MyBuilder('好频率98%')
this.MyBuilder('查看全部')
this.MyBuilder('4条测评')
1.4 引用传递
场景:当传递的数据更新,需要更新ui
需求:
点击按钮后,模拟加载好评率数据
import promptAction from '@ohos.promptAction';
class Params{
title:string = ''
}
@Entry
@Component
struct BuilderDemo1Page {
@State rate:number = 0;
@Builder
MyBuilder(params:Params){
Row(){
Text(params.title)
Image($r('app.media.chevron_right'))
.width(16)
.aspectRatio(1)
}
}
build() {
Column(){ //垂直方向布局
Button('获取数据')
.onClick( () => {
this.rate = 98;
promptAction.showToast({message:this.rate.toString()})
})
//columns:分为几列,columns默认值为12
//gutter:设置子元素在水平和垂直方向的间距。
GridRow({columns:2,gutter:15}){
GridCol({span:2}){ //span:将几个合并成一个
Column(){
Row(){
Text('评价(200+)')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
// 查看更多
this.MyBuilder({title:`好频率${this.rate}%`})
}
Row(){
}
.height(100)
}
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
GridCol(){
Column(){
Row(){
Text('推荐')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
// 查看更多
this.MyBuilder({title:'查看全部'})
}
Row(){
}
.height(100)
}
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
GridCol(){
Column(){
Row(){
Text('体验')
.layoutWeight(1)
//.fontWeight(FontWeight.Bold)
.fontWeight(600)
// 查看更多
this.MyBuilder({title:'4条测评'})
}
Row(){
}
.height(100)
}
}
.backgroundColor('#fff')
.borderRadius(12)
.padding(10)
}
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
.padding(15)
}
}
使用Builder复用逻辑的时候,支持参数可以更灵活渲染UI
参数可以使用
状态数据
,不过建议通过对象的方式传递@Builder
2.构建函数 @BuilderParam
@BuilderParam
该装饰器用于声明任意UI描述一个元素,类似于slot
占位符
前置知识:
组件属性初始化
1.定义组件声明属性: title:string
2.使用组件的初始化属性:Comp({title:string})
- 尾随闭包初始化组件
- 组件内有且仅有一个@BuilderParam装饰器的属性
- 参数初始化组件
- 组件内有多个@BuilderParam装饰器属性
2.1 尾随闭包初始化组件
需求:
- 标题文件和更多文件通过属性传入
- 内容结构需要传入
@Entry
@Component
struct BuilderParamPage {
build() {
Column(){
GridRow({columns:2,gutter:15}){
GridCol({span:2}){
PanelComp({title:'评价(200+)',more:'好评率98%'});
}
GridCol(){
PanelComp({title:'推荐',more:'查看全部'}){
Text('推荐内容')
};
}
GridCol(){
PanelComp({title:'体验',more:'4 条测评'}){
Text('体验内容')
};
}
}
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
.padding(15)
}
}
@Component
struct PanelComp {
title:string = ''
more:string = ''
@Builder
DefaultPanelContent(){
Text('默认内容')
}
@BuilderParam
panelContent: () => void = this.DefaultPanelContent
build() {
Column(){
Row(){
Text(this.title)
.layoutWeight(1)
.fontWeight(600)
Row(){
Text(this.more)
.fontSize(14)
.fontColor('#666')
Image($r('app.media.chevron_right'))
.width(16)
.aspectRatio(1)
.fillColor('#666')
}
}
Row(){
this.panelContent();
}
.height(100)
}
.backgroundColor('#fff')
.padding(10)
.borderRadius(12)
}
}
2.2 参数初始化组件 (具名插槽)
需求:
需要传入内容结构和底部结构
@Entry
@Component
struct BuilderParam2Page {
@Builder
ContentBuilderA(){
Text('评论内容1111')
}
@Builder
ContentBuilderB(){
Text('评论底部')
}
build() {
Column(){
GridRow({columns:2,gutter:15}){
GridCol({span:2}){
PanelComp2({
title:'评价(2000+)',
more:'好评率98%',
panelContent: this.ContentBuilderA,
panelFooter:this.ContentBuilderB
})
}
}
}
.width('100%')
.height('100%')
.padding(15)
.backgroundColor('#f5f5f5')
}
}
@Component
struct PanelComp2 {
title:string = '';
more:string = '';
@Builder
DefaultPanelContent(){
Text('默认内容')
}
@BuilderParam
panelContent: () => void = this.DefaultPanelContent
@BuilderParam
panelFooter: () => void = this.DefaultPanelContent
build() {
Column(){
Row(){
Text(this.title)
.layoutWeight(1)
.fontWeight(600)
.fontSize(14)
.fontColor('#666')
Row(){
Text(this.more)
Image($r('app.media.chevron_right'))
.width(16)
.aspectRatio(1)
.fillColor('#666')
}
}
Row(){
this.panelContent()
}
.height(100)
Row(){
this.panelFooter()
}
.height(100)
}
.backgroundColor('#fff')
.padding(10)
.borderRadius(16)
}
}
3.总结
- 当子组件使用一个@BuilderParam的时候,使用组件的时候尾随 {},插入UI结构
- 当子组件有多个@BuilderParam的时候,使用组件的时候Comp({xxx:xxxxx})传入
- 子组件本身提供一个默认的@Builder函数作为@BuilderParam备用函数,当作准用内容使用(默认内容)
4.系统组件自定义UI
在一些系统组件中,根据配置无法达到预期的UI,就可以使用@Builder构建函数自定义UI,前提是该组件要支持自定义UI
Tabs:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-navigation-tabs-V5
4.1 基本Tab结构
@Entry
@Component
struct TabsPage {
build() {
Column(){
//barPosition:控制显示的位置
Tabs({barPosition:BarPosition.End}){
TabContent(){
Text('首页')
}.tabBar('首页')
TabContent(){
Text('项目')
}
.tabBar('项目')
TabContent(){
Text('面试')
}.tabBar('面试')
TabContent() {
Text('我的')
}.tabBar('我的')
}
}
.width('100%')
.height('100%')
}
}
4.2 自定义Tab结构
import promptAction from '@ohos.promptAction'
@Entry
@Component
struct TabsPage {
@State activeIndex:number = 0; //默认激活的是第一个索引
//定义个对象数组,用来存储图标相关信息
toolBarItems:ToolBarItem[] = [
{defaultIcon:$r('app.media.home'),activeIcon:$r('app.media.home_select'),label:'首页'},
{defaultIcon:$r('app.media.project'),activeIcon:$r('app.media.project_select'),label:'项目'},
{defaultIcon:$r('app.media.interview'),activeIcon:$r('app.media.interview_select'),label:'面试'},
{defaultIcon:$r('app.media.mine'),activeIcon:$r('app.media.mine_select'),label:'我的'},
]
@Builder
TabBarBuilder(item:ToolBarItem,index:number){
Column(){
Image(this.activeIndex === index?item.activeIcon:item.defaultIcon)
.width(24)
.aspectRatio(1)
Text(item.label)
.lineHeight(12)
.margin({top:4})
.fontColor(this.activeIndex === index?'#000':'#aaa')
}
}
build() {
Column(){
//barPosition:控制显示的位置
Tabs({barPosition:BarPosition.End}){
//循环遍历
ForEach(this.toolBarItems,(item:ToolBarItem,index:number)=>{
TabContent(){
Text(item.label)
}.tabBar(this.TabBarBuilder(item,index))
})
}
.onChange( (index:number) => {
//获取到点击的索引
// promptAction.showToast({message:index.toString()})
this.activeIndex = index;//获取到当前激活的索引
})
}
.width('100%')
.height('100%')
}
}
//类:描述ToolBar
class ToolBarItem{
defaultIcon:string | Resource = '' //默认图标
activeIcon:string | Resource = '' //激活图标
label: string = '' //文字
}
二、组件状态共享
1.父子单向
@Prop 装饰的变量可以和父组件建立单向的同步关系
@Prop装饰的变量是可变的,但是变化不同同步回到父组件
@Entry
@Component
struct PropPage {
@State money:number = 0;
build() {
Column({space:20}){
Text('父组件:' + this.money)
.fontSize(20)
.onClick(() => {
this.money++
})
//引入子组件
Child1({money: this.money});
}
}
}
@Component
struct Child1 {
//@Prop是父子单向
@Prop money:number;
// @Prop money:number = 10;
build() {
Text('子组件:' + this.money)
.fontSize(20)
.onClick(() => {
this.money++
})
}
}
支持的类型:
- string
- number
- boolean
- enum
子组件可以修改Prop的数据值,父组件更新后覆盖子组件Prop的数据
子组件可以有初始化的默认值,注意:目前的编译器是已经修复bug,不会报错
2.父子双向
子组件中用@Link装饰的变量与父组件中对应的数据建立双向数据绑定
2.1 简单类型
string、number、boolean、enum
@Entry
@Component
struct Link1Page {
@State money:number = 0;
build() {
Column({space:20}){
Text('父组件:' + this.money)
.fontSize(20)
.onClick(() => {
this.money++
})
//引入子组件
Child2({money: this.money});
}
}
}
@Component
struct Child2 {
//@Link父子双向
@Link money:number;
build() {
Text('子组件:' + this.money)
.fontSize(20)
.onClick(() => {
this.money++
})
}
}
2.2 复杂类型
object、class
@Entry
@Component
struct Link2Page {
@State person:Person = {name:'kunkun',age:26};
build() {
Column({space:20}){
Text(`父组件: + ${this.person.name},今年${this.person.age}岁了`)
.fontSize(20)
.onClick(() => {
this.person.age++
})
//引入子组件
Child3({person:$person});
// Child3({person:this.person});
}
}
}
@Component
struct Child3 {
//@Link父子双向
@Link person:Person;
build() {
Text(`子组件: + ${this.person.name},今年${this.person.age}岁了`)
.fontSize(20)
.onClick(() => {
this.person.age++
})
}
}
//自定义类型
class Person{
name:string = ''
age:number = 0
}
父组件传值的时候,需要
this.
改写成$
子组件
@Link
装饰数据
3.后代组件
@Provide
和consume
,应用于与后代组件的双向数据同步,应用于状态的数据的多个层级之间传递场景
3.1 通过相同的变量名绑定
必须保证变量名是相同
@Entry
@Component
struct ProvidePage {
@Provide money:number = 0;
build() {
Column({space:25}){
Text('父组件:' + this.money)
.onClick(() => {
this.money++
})
Child4()
}
}
}
@Component
struct Child4 {
//不可以给初始值
@Consume money:number;
build() {
Text('子组件:' + this.money)
.onClick(() => {
this.money++
})
}
}
Tip ⏲
- Object、class、string、number、boolean、enum类型都支持
- 通过相同的变量名绑定
@Provide
和@Consume
的变量名一致
3.2 状态监听
如果开发者需要关注某个变量的值是否发生变化,可以使用
@watch
为状态变量设置回调函数@State 、@Prop 和@Link等装饰器在@Watch装饰之前
import promptAction from '@ohos.promptAction'
@Entry
@Component
struct WatchPage {
@State activeIndex:number = 0;
build() {
Column(){
Button('按钮1')
.onClick(()=>{
this.activeIndex = 1;
promptAction.showToast({message:this.activeIndex.toString()})
})
Button('按钮2')
.onClick(()=>{
this.activeIndex = 2;
promptAction.showToast({message:this.activeIndex.toString()})
})
Child5({activeIndex:this.activeIndex});
}
}
}
@Component
struct Child5 {
@Prop @Watch('onActionIndex') activeIndex:number = 0
onActionIndex(){
promptAction.showToast({message:'监听到了变化 -->' + this.activeIndex})
}
build() {
Text('Child5---->' + this.activeIndex)
}
}
TIP:
在第一次初始化的时候,
@Watch
装饰器的方法是不会被调用的
3.2 @Observed 与@ObjectLink
之前我们通过 赋值的方式 修改嵌套对象或对象数组这类复杂数据来更新UI的
使用步骤:
- 类class数据模拟需要定义通过构造函数,使用@Observed修改这个类
- 初始化数据:需要通过初始化构造方式添加
- 通过@ObjectLink关联对象,可以直接修改被关联对象来更新UI
//创建接口
interface ReplyItemInterface{
id?:number
avatar?:string | Resource
author?:string
content?:string
time?:string
area?:string
linkNum?:number
likeFlag?:boolean
}
@Observed
export class ReplyItem{
id?:number
avatar?:string | Resource
author?:string
content?:string
time?:string
area?:string
linkNum?:number
likeFlag?:boolean
//构造函数中要求使用接口类型
constructor(item:ReplyItemInterface) {
this.id = item.id
this.avatar = item.avatar
this.author = item.author
this.content = item.content
this.time = item.time
this.area = item.area
this.linkNum = item.linkNum
this.likeFlag = item.likeFlag
}
}
//创建对象数组
export const replyList:ReplyItem[] = [
new ReplyItem({id:1,avatar:$r('app.media.kun'),author:'金庸',content:'倚天屠龙记',time:'16:54',area:'武汉',linkNum:30,likeFlag:true}),
new ReplyItem({id:1,avatar:$r('app.media.kun'),author:'金庸',content:'倚天屠龙记',time:'16:54',area:'武汉',linkNum:30,likeFlag:true}),
new ReplyItem({id:1,avatar:$r('app.media.kun'),author:'金庸',content:'倚天屠龙记',time:'16:54',area:'武汉',linkNum:30,likeFlag:true})
]
import {ReplyItem,replyList} from '../model/common'
import promptAction from '@ohos.promptAction'
@Entry
@Component
struct ZhihuPage {
@State content:string = ''
//评论对象数组
@State replyList:ReplyItem[] = replyList
//回复消息
onReply(){
const reply:ReplyItem = new ReplyItem({id:1,avatar:$r('app.media.kun'),author:'蔡徐坤',content:this.content,time:'16:54',area:'武汉',linkNum:30,likeFlag:true})
//添加到对象数组中
// this.replyList.push(reply) //添加到数组的最后一个
this.replyList.unshift(reply) //添加到对象数组中的第一个
this.content = ''
promptAction.showToast({message:'回复成功'})
}
build() {
Column(){
TextInput({placeholder:'回复'})
.layoutWeight(1)
.onChange((value:string) => {
this.content = value; //获取到输入框中输入的值
})
ForEach(this.replyList,(item:ReplyItem) => {
ReplyComp({item:item})
})
Button('发布')
.onClick(()=> {
this.onReply();//发布消息
})
}
}
}
@Component
struct ReplyComp {
@ObjectLink item:ReplyItem
build() {
Column(){
Image(this.item.avatar)
.width(32)
.aspectRatio(1)
.borderRadius(16)
Column(){
Text(this.item.author)
.fontSize(15)
.fontWeight(100)
.margin({top:5})
Text(this.item.content)
.margin({top:5})
.fontColor('blue')
.lineHeight(20)
}
}
}
}