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

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

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

import { Log4a,InitializeAllLoggers } from 'winloglib';
import {BlockController} from '../model/BlockController'

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

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

正文开始
@Builder
function MyBuilder() {}

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

  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的动态配置

@Require  titleNameList: string[] = []; //标题列表

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

@Require componentList: WrappedBuilder<[BlockController]>[] = [wrapBuilder(MyBuilder)];

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

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-wrapbuilder-V5

declare function wrapBuilder< Args extends Object[]>(builder: (...args: Args) => void): WrappedBuilder;
// 组件生命周期
  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%')
  }

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

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)就是你传参数给自定义组件的参数。

请看父组件的调用方法以及示例:
@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('test')
}

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

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

示例:
@Entry
@Component
export   struct StoreListPage{
  private blockRef = new BlockController();
    @State tabNameList: string[] = [
        'test',
        'test(0)',
        'test(90)',
        'test'
      ];
build() {
    Column(){ 
        WinTabBarComponents({
          titleNameList: this.tabNameList,
          componentList: builderArr,
          block: this.blockRef,
        });
      }
    }
}

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

完整自定义的代码如下
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)
    })
  }
}

我们的Tabs如图所示


image.png

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容