HarmonyOS 实现一个自定义分类列表

应用在多个地方使用了自定义分类,之前是用的同一个组件,通过参数控制在不同场景下的样式。

1. 希望提供一个类似的demo,自定义分类组件,可以通过参数控制样式。

2. 分类标签的切换,希望能有IOS的平滑过渡效果。

HarmonyOS
2024-10-21 11:37:14
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
FengTianYa

自定义分类组件demo如下所示:

// 把tab和视图分开构造布局  
export class TabBarDataType {  
  id: number;  
  value: string;  
  
  constructor(id: number, value: string) {  
    this.id = id;  
    this.value = value;  
  }  
}  
  
export const TABINFO: TabBarDataType[] = [  
  new TabBarDataType(0, '待就诊'),  
  new TabBarDataType(1, '已就诊'),  
  new TabBarDataType(2, '已取消'),  
  new TabBarDataType(3, '已违约'),  
  
];  
@Entry  
@Component  
struct TabsExample {  
  @State currentIndex: number = 0  
  @State animationDuration: number = 300  
  @State tabData:Array<string> = ['待就诊','已就诊','已取消','已违约']  
  private controller: TabsController = new TabsController(); // 初始化Tab控制器  
  
  build() {  
    Column() {  
      // 自定义TabBar组件  
      CustomTabBar({ currentIndex: $currentIndex })  
      Tabs({ index: this.currentIndex, barPosition: BarPosition.End, controller: this.controller }) {  
        ForEach(this.tabData, (item:string, index) => {  
          TabContent() {  
            Column(){  
              Text(item)  
            }.width('100%').height('100%').backgroundColor(Color.Pink)  
          }  
        }, (item: string): string => item)  
      }  
  
      .barWidth('100%')  
      .barHeight(56)  
      .width('100%')  
      .height(296)  
      .backgroundColor('#3b98ff')  
      .animationDuration(this.animationDuration)  
      .onChange((index: number) => {  
        this.currentIndex = index  // 监听索引index的变化,实现页签内容的切换。  
      })  
    }.width('100%')  
  }  
}  
  
@Component  
struct CustomTabBar {  
  @Link currentIndex: number; // 初始化被选定的tabBar下标  
  
  build() {  
    Row() {  
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {  
        ForEach(TABINFO, (item: TabBarDataType, tabIndex: number) => {  
          // 单独一个TabBar组件  
          TabItem({  
            tabBarIndex: tabIndex,  
            currentIndex: $currentIndex,  
          })  
        })  
      }  
      .height(60)  
      .width('100%')  
      .backgroundColor('#3796ff')  
    }  
    .height(60)  
    .width('100%')  
  }  
}  
  
@Component  
struct TabItem {  
  @Prop tabBarIndex: number; // tabBar下标  
  @Link currentIndex: number; // 初始化被选定的tabBar下标  
  build() {  
    Column() {  
      Column() {  
        // 通过被选中的tabBar下标值和tabBar的默认下标值来改变图片显示  
        Text(TABINFO[this.tabBarIndex].value)  
          .size({ width: 60, height: 60 })  
          .textAlign(TextAlign.Center)  
          .fontSize(this.currentIndex == this.tabBarIndex?20:15)  
          .fontColor(Color.White)  
      }  
      .width(60)  
      .height(60)  
      .justifyContent(FlexAlign.Center)  
  
    }  
    .width(60)  
    .onClick(() => {  
      // 更新被选中的tabBar下标  
      this.currentIndex = this.tabBarIndex;  
    })  
  }  
}

平滑过渡效果demo:

export class TabBarDataType {  
  id: number;  
  value: string;  
  
  constructor(id: number, value: string) {  
    this.id = id;  
    this.value = value;  
  }  
}  
  
export const TABINFO: TabBarDataType[] = [  
  new TabBarDataType(0, '待就诊'),  
  new TabBarDataType(1, '已就诊'),  
  new TabBarDataType(2, '已取消'),  
  new TabBarDataType(3, '已违约'),  
];  
  
  
@Entry  
@Component  
struct Index {  
  build() {  
    Column() {  
      TabsExample({  
        tabData: TABINFO,  
        fontColor: '#182431',  
        selectFontColor: Color.White,  
        normalBg: Color.White,  
        selectedBg: Color.Blue  
      })  
  
      TabsExample({  
        tabData: TABINFO,  
        fontColor: '#182431',  
        selectFontColor:"#007DFF",  
        isUnderline: true  
      })  
    }  
  }  
}  
  
@Component  
export struct TabsExample {  
  tabData: TabBarDataType[] = []  
  normalBg?: ResourceColor  
  selectedBg?: ResourceColor  
  fontColor?: ResourceColor  
  selectFontColor?: ResourceColor  
  isUnderline: boolean = false  
  @State currentIndex: number = 0  
  @State animationDuration: number = 300  
  @State indicatorLeftMargin: number = 0  
  @State indicatorWidth: number = 0  
  private tabsWidth: number = 0  
  private controller: TabsController = new TabsController(); // 初始化Tab控制器  
  
  @Builder  
  tabBuilder($$: TabBarDataType, index: number) {  
    Column() {  
      Text($$.value)// .size({ width: 60, height: 60 })  
        .textAlign(TextAlign.Center)  
        .fontSize(this.currentIndex == index ? 16 : 12)  
        .fontWeight(this.currentIndex === index ? 500 : 400)  
        .fontColor(this.currentIndex === index ? this.selectFontColor : this.fontColor)  
        .id(index.toString())  
    }  
    .backgroundColor(this.currentIndex === index ? this.selectedBg : this.normalBg)  
    .borderRadius(20)  
    .padding(10)  
    .margin({ bottom: 10 })  
    .onAreaChange((oldValue: Area, newValue: Area) => {  
      if (this.currentIndex === index && (this.indicatorLeftMargin === 0 || this.indicatorWidth === 0)) {  
        if (newValue.position.x != undefined) {  
          let positionX = Number.parseFloat(newValue.position.x.toString())  
          this.indicatorLeftMargin = Number.isNaN(positionX) ? 0 : positionX  
        }  
        let width = Number.parseFloat(newValue.width.toString())  
        this.indicatorWidth = Number.isNaN(width) ? 0 : width  
      }  
    })  
  }  
  
  build() {  
    Stack({ alignContent: Alignment.TopStart }) {  
      Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {  
        ForEach(this.tabData, (item: TabBarDataType, index: number) => {  
          TabContent() {  
            Column() {  
              Text(item.value)  
            }.width('100%').height('100%').backgroundColor(Color.Pink)  
          }  
          .tabBar(this.tabBuilder(item, index))  
        }, (item: string): string => item)  
      }  
      .onAreaChange((oldValue: Area, newValue: Area) => {  
        let width = Number.parseFloat(newValue.width.toString())  
        this.tabsWidth = Number.isNaN(width) ? 0 : width  
      })  
      .barWidth('100%')  
      .barHeight(56)  
      .width('100%')  
      .height(296)  
      .backgroundColor('#F1F3F5')  
      .animationDuration(this.animationDuration)  
      .onChange((index: number) => {  
        this.currentIndex = index // 监听索引index的变化,实现页签内容的切换。  
      })  
  
        .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {  
          // 切换动画开始时触发该回调。下划线跟着页面一起滑动,同时宽度渐变。  
          this.currentIndex = targetIndex  
          let targetIndexInfo = this.getTextInfo(targetIndex)  
          this.startAnimateTo(this.animationDuration, targetIndexInfo.left, targetIndexInfo.width)  
        })  
        .onAnimationEnd((index: number, event: TabsAnimationEvent) => {  
          // 切换动画结束时触发该回调。下划线动画停止。  
          let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event)  
          this.startAnimateTo(0, currentIndicatorInfo.left, currentIndicatorInfo.width)  
        })  
        .onGestureSwipe((index: number, event: TabsAnimationEvent) => {  
          // 在页面跟手滑动过程中,逐帧触发该回调。  
          let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event)  
          this.currentIndex = currentIndicatorInfo.index  
          this.indicatorLeftMargin = currentIndicatorInfo.left  
          this.indicatorWidth = currentIndicatorInfo.width  
        })  
  
      if(this.isUnderline){  
        Column()  
          .height(2)  
          .width(this.indicatorWidth)  
          .margin({ left: this.indicatorLeftMargin, top: 48 })  
          .backgroundColor('#007DFF')  
      }  
    }.width('100%')  
  }  
  
  private getTextInfo(index: number): Record<string, number> {  
    let strJson = getInspectorByKey(index.toString())  
    try {  
      let obj: Record<string, string> = JSON.parse(strJson)  
      let rectInfo: number[][] = JSON.parse('[' + obj.$rect + ']')  
      return { 'left': px2vp(rectInfo[0][0]), 'width': px2vp(rectInfo[1][0] - rectInfo[0][0]) }  
    } catch (error) {  
      return { 'left': 0, 'width': 0 }  
    }  
  }  
  
  private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {  
    let nextIndex = index  
    if (index > 0 && event.currentOffset > 0) {  
      nextIndex--  
    } else if (index < 3 && event.currentOffset < 0) {  
      nextIndex++  
    }  
    let indexInfo = this.getTextInfo(index)  
    let nextIndexInfo = this.getTextInfo(nextIndex)  
    let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth)  
    let currentIndex = swipeRatio > 0.5 ? nextIndex : index // 页面滑动超过一半,tabBar切换到下一页。  
    let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * swipeRatio  
    let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * swipeRatio  
    return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }  
  }  
  
  private startAnimateTo(duration: number, leftMargin: number, width: number) {  
    animateTo({  
      duration: duration, // 动画时长  
      curve: Curve.Linear, // 动画曲线  
      iterations: 1, // 播放次数  
      playMode: PlayMode.Normal, // 动画模式  
      onFinish: () => {  
        console.info('play end')  
      }  
    }, () => {  
      this.indicatorLeftMargin = leftMargin  
      this.indicatorWidth = width  
    })  
  }  
}
分享
微博
QQ
微信
回复
2024-10-21 15:09:49
相关问题
如何实现一个自定义询问框
426浏览 • 1回复 待解决
怎样实现一个自定义播放器?
368浏览 • 1回复 待解决
实现一个自定义动画,出现丢帧问题
396浏览 • 1回复 待解决
如何在全局实现一个自定义dialog弹窗
2835浏览 • 1回复 待解决
如何实现一个自定义样式的toast提示
1957浏览 • 1回复 待解决
如何快速开发出一个自定义弹窗?
376浏览 • 1回复 待解决
使用自定义函数创建一个UI组
369浏览 • 1回复 待解决
如何封装一个自定义Dialog对话框
2234浏览 • 1回复 待解决
HarmonyOS 怎么实现下拉分类列表
250浏览 • 1回复 待解决
如何在自定义函数中创建一个UI组件
1832浏览 • 1回复 待解决
如何添加一个自定义的代码文件夹
441浏览 • 1回复 待解决