HarmonyOS:如何实现自定义的Tabs,TabContent内部实现如何动态配置

前言 :最近做开发任务的时候,想把Tabs自定义了,并且动态配置TabContent里面的内容,不是写死一样的,这个问题困扰了很长时间,试过**@BuilderParam**(类似于vue的插槽)传组件方式的,但是**@BuilderParam只能传一个,我想要传递的是一个数组,扒了很多Api最后找到了WrappedBuilder\[\]**这种方式。

废话不多说,直接上代码,因为大部分的学习者都是先看代码,作为最直接的切入点,先做分解解释,最后附完整代码,以及示例用法。

javascript 复制代码
import { Log4a,InitializeAllLoggers } from 'winloglib';
import {BlockController} from '../model/BlockController'

Log4a自定义的日志打印,主要是为了日志输出到本地的功能

BlockController:回调类,主要是一些闭包函数,用作父子组件之间的回调事件处理,以及传值功能

正文开始
less 复制代码
@Builder
function MyBuilder() {}

自定义全局builder,WrappedBuilder使用的时候需要初始化,所以初始化为空

ini 复制代码
  tabController: TabsController = new TabsController()
  titleSelectColor: ResourceColor = '#00EB01';//选中高亮颜色
  titleNormalColor: ResourceColor = '#182431';//字体默认颜色
  dividerColor: ResourceColor = '#00EB01';//下划线颜色
  dividerStrokeWidth: number | string = 2;//下划线高度
  titleFontSize: number | string | Resource = 16; //字体默认大小
  titleFontWeight: number | string | FontWeight = '400';//字体默认Weight
  titleSelectFontWeight: number | string | FontWeight = '500';//字体默认Weight
  titleLineHeight: number | string | Resource = '22';//默认高度
  tabBarHeight: Length = 60;//默认高度
  tabBarWidth: Length = '100%';//默认宽度
  durationTime: number = 400;//默认动画时间
  @State currentIndex: number = 0;
  @Require  titleNameList: string[] = []; //标题列表
  @Require componentList: WrappedBuilder<[BlockController]>[] = [wrapBuilder(MyBuilder)];
  @Builder FunABuilder0() {}
  // 接受外部传入的AttributeModifier类实例
  @Prop modifier: AttributeModifier<TabsAttribute> | null = null;
  private block: BlockController = new BlockController();
  private log4a: Log4a = new Log4a();

自定义组件需要的参数,这中自定义方法是鸿蒙不提倡的,因为需要太多的参数传递,这是我之前的自定义方案,还没来得及改动。

鸿蒙 系统希望用AttributeModifier 的方式自定义组件,我后面用这种方案实现自定义。本期的重点是实现TabContent的动态配置。

less 复制代码
@Require  titleNameList: string[] = []; //标题列表

这是tabs的标题列表数组,这个没什么解释的。

ini 复制代码
@Require componentList: WrappedBuilder<[BlockController]>[] = [wrapBuilder(MyBuilder)];

这个是关键,这个就是自定义的传递组件列表,接收组件列表的参数,需要WrappedBuilder包裹,<BlockController>这个是参数类型,是需要向你自定义组件传值,或者传递回调的时候用的,我这里面只需要传递回调方法,所以只有这一个类型,多参数凡是<BlockController,string,number,object>,当然你可以用自定义的数据类型,model类型。 这个解释很详细了,你可以看下官方文档对WrappedBuilder的解释以及示例。

developer.huawei.com/consumer/cn...

typescript 复制代码
declare function wrapBuilder< Args extends Object[]>(builder: (...args: Args) => void): WrappedBuilder;
typescript 复制代码
// 组件生命周期
  aboutToAppear() {
    console.info('WinTabBarComponents aboutToAppear');
    // InitializeAllLoggers();
    this.log4a.info('WinTabBarComponents aboutToAppear');
    this.block.toggleTabBlock = this.clickBlock;
  }
  //切换路线
  changeRouteBlock: (param: string) => void = (param: string) => {
    this.block.jumpNextVCBlock(param);
  };
  clickBlock: () => void = () => {
    console.info('切换路线完成action:');
    this.tabController.changeIndex(1);
  }
  changeTabIndex(index: number) {
    this.block.changeTabIndexBlock(index);
  }

这是我项目中需要对父组件回调事件做的处理,你可以自定义自己的回调事件,

kotlin 复制代码
//构造函数
  @Builder tabBuilder(index: number,name?: string){
    Column() {
      Text(this.titleNameList[index])
        .fontColor(this.currentIndex === index ? this.titleSelectColor : this.titleNormalColor)
        .fontSize(this.titleFontSize)
        .fontWeight(this.currentIndex === index ? this.titleFontWeight : this.titleSelectFontWeight)
        .lineHeight(this.titleLineHeight)
        .margin({ top: 7, bottom: 7 })
      Divider()
        .strokeWidth(this.dividerStrokeWidth)
        .color(this.dividerColor)
        .opacity(this.currentIndex === index ? 1 : 0)
    }.width('100%')
  }

这是Tabs的标题栏的构造函数,这个很简单,没什么解释的。

kotlin 复制代码
build() {
    Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.tabController }) {
      ForEach(this.componentList, (item: WrappedBuilder<[BlockController]>,index: number) => {
        TabContent() {
          item.builder(this.block)
        }.tabBar(this.tabBuilder(index))
      }, (item: WrappedBuilder<[BlockController]>) => JSON.stringify(item))
    }
    .vertical(false)
    .barMode(BarMode.Fixed)
    .barWidth(this.tabBarWidth)
    .barHeight(this.tabBarHeight)
    .animationDuration(this.durationTime)
    .onChange((index: number) => {
        this.currentIndex = index;
        this.changeTabIndex(index)
    })
  }

这里就是实现TabContent的动态配置内部实现方法,ForEach循环的item的类型是WrappedBuilder<BlockController>,这个参数是自己定义的,你可以传递多参数的**,item.builder(this.block)**就是你传参数给自定义组件的参数。

请看父组件的调用方法以及示例:
less 复制代码
@Builder
function  componentMineRoutePageBuilder(block: BlockController){
  TodayVisitRoutePage({blockController:block}).padding({bottom:CommonConstants.TAB_PAD_BOTTOM});
}
@Builder
function componentTodayVisitStorePage(block: BlockController){
  TodayVisitStorePage().padding({bottom:CommonConstants.TAB_PAD_BOTTOM});
}
@Builder
function componentMapPageBuilder(block: BlockController) {
  Text('售点地图')
}

const builderArr: WrappedBuilder<[BlockController]>[] = [                      wrapBuilder(componentMineRoutePageBuilder),                      wrapBuilder(componentTodayVisitStorePage),                      wrapBuilder(componentMapPageBuilder),                      wrapBuilder(componentMapPageBuilder)                      ];

这个是需要全局定义的@Builder方法,不能定义在struct里面的,需要全局定义。 TodayVisitRoutePage,TodayVisitStorePage都是我自定义的组件,这样就动态实现TabContent的配置,不再是一样的写法,也可以传递参数,以及回调方法。

示例:
less 复制代码
@Entry
@Component
export   struct StoreListPage{
  private blockRef = new BlockController();
    @State tabNameList: string[] = [        '我的路线',        '今日路线(0)',        '门店列表(90)',        '售点地图'      ];
build() {
    Column(){ 
        WinTabBarComponents({
          titleNameList: this.tabNameList,
          componentList: builderArr,
          block: this.blockRef,
        });
      }
    }
}

使用方法也很简单,传递你自定义的WrappedBuilder数组,自定义的Tabs会根据你传递的数组数量创建相同数量的TabContent了。

完整自定义的代码如下
kotlin 复制代码
import { Log4a,InitializeAllLoggers } from 'winloglib';
import {BlockController} from '../model/BlockController'

@Builder
function MyBuilder() {}
@Component
export struct  WinTabBarComponents{
  tabController: TabsController = new TabsController()
  titleSelectColor: ResourceColor = '#00EB01';//选中高亮颜色
  titleNormalColor: ResourceColor = '#182431';//字体默认颜色
  dividerColor: ResourceColor = '#00EB01';//下划线颜色
  dividerStrokeWidth: number | string = 2;//下划线高度
  titleFontSize: number | string | Resource = 16; //字体默认大小
  titleFontWeight: number | string | FontWeight = '400';//字体默认Weight
  titleSelectFontWeight: number | string | FontWeight = '500';//字体默认Weight
  titleLineHeight: number | string | Resource = '22';//默认高度
  tabBarHeight: Length = 60;//默认高度
  tabBarWidth: Length = '100%';//默认宽度
  durationTime: number = 400;//默认动画时间
  @State currentIndex: number = 0;
  @Require  titleNameList: string[] = []; //标题列表
  @Require componentList: WrappedBuilder<[BlockController]>[] = [wrapBuilder(MyBuilder)];
  @Builder FunABuilder0() {}
  // 接受外部传入的AttributeModifier类实例
  @Prop modifier: AttributeModifier<TabsAttribute> | null = null;
  private block: BlockController = new BlockController();
  private log4a: Log4a = new Log4a();
  // 组件生命周期
  aboutToAppear() {
    console.info('WinTabBarComponents aboutToAppear');
    // InitializeAllLoggers();
    this.log4a.info('WinTabBarComponents aboutToAppear');
    this.block.toggleTabBlock = this.clickBlock;
  }
  //切换路线
  changeRouteBlock: (param: string) => void = (param: string) => {
    this.block.jumpNextVCBlock(param);
  };
  clickBlock: () => void = () => {
    console.info('切换路线完成action:');
    this.tabController.changeIndex(1);
  }
  changeTabIndex(index: number) {
    this.block.changeTabIndexBlock(index);
  }
  //构造函数
  @Builder tabBuilder(index: number,name?: string){
    Column() {
      Text(this.titleNameList[index])
        .fontColor(this.currentIndex === index ? this.titleSelectColor : this.titleNormalColor)
        .fontSize(this.titleFontSize)
        .fontWeight(this.currentIndex === index ? this.titleFontWeight : this.titleSelectFontWeight)
        .lineHeight(this.titleLineHeight)
        .margin({ top: 7, bottom: 7 })
      Divider()
        .strokeWidth(this.dividerStrokeWidth)
        .color(this.dividerColor)
        .opacity(this.currentIndex === index ? 1 : 0)
    }.width('100%')
  }
  build() {
    Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.tabController }) {
      ForEach(this.componentList, (item: WrappedBuilder<[BlockController]>,index: number) => {
        TabContent() {
          item.builder(this.block)
        }.tabBar(this.tabBuilder(index))
      }, (item: WrappedBuilder<[BlockController]>) => JSON.stringify(item))
    }
    .vertical(false)
    .barMode(BarMode.Fixed)
    .barWidth(this.tabBarWidth)
    .barHeight(this.tabBarHeight)
    .animationDuration(this.durationTime)
    .onChange((index: number) => {
        this.currentIndex = index;
        this.changeTabIndex(index)
    })
  }
}

我们的UI如图所示

是不是很简单啊?如果可以的话给个赞和关注。如果还有什么疑问可以发私信问我。

相关推荐
卸任7 小时前
Vibe coding工具使用心得
前端·ai编程
brien97 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance8 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M8 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写8 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李8 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易8 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马8 小时前
前沿视界
前端
kyriewen8 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易8 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员