🎯 案例集合Tabs:自定义tabs左右边缘渐隐,切换动画衔接 + 更多按钮

🌍 案例集合Tabs

🏷️ 效果图

📖 参考

🧩 拆解

  • 自定义tabs(V1-V2组件都可用)使用fadingEdge Api 14

    @Component
    export struct tabsMoreButAndFadingEdge {
    private mockData: string[] = ['购物', '体育', '财经', '服装', '军事', '政治', '居家', '国际', '科技', '城市', '景点']
    private controller: TabsController = new TabsController()
    private listScroller: ListScroller = new ListScroller()
    @State selectIdx: number = 0
    @State animationStartIdx: number = 0
    
    @Builder
    tabBuilder(label: string, idx: number) {
      Text(label)
        .width(60)
        .height(40)
        .borderRadius('50%')
        .fontSize(16)
        .fontColor(this.animationStartIdx === idx ? Color.White : Color.Black)
        .backgroundColor(this.animationStartIdx === idx ? Color.Orange : '#80e0dede')
        .textAlign(TextAlign.Center)
        .onClick(() => {
          this.controller.changeIndex(idx)
          this.listScroller.scrollToIndex(idx, true, ScrollAlign.CENTER)
          this.selectIdx = this.animationStartIdx = idx
        })
    }
    
    build() {
      Column() {
        Stack({ alignContent: Alignment.TopStart }) {
          Tabs({ index: this.selectIdx }) {
            ForEach(this.mockData, (item: string) => {
              TabContent() {
                Column() {
                  Text(item)
                }
                .width('100%')
                .height('100%')
                .backgroundColor('#66e2dcdc')
                .justifyContent(FlexAlign.Start)
              }
            })
          }
          .barHeight(0)
          .padding({ top: 50 }) // 关键:这里解决自定义tabs上面缺失的barHeight高度
          .onChange((idx: number) => {
            this.selectIdx = idx
          })
          // 关键:切换动画开始时触发该回调:解决切换tabs延迟的问题
          .onAnimationStart((idx: number, targetIndex: number) => {
            if (idx === targetIndex) {
              return
            }
    
            this.listScroller.scrollToIndex(targetIndex, true, ScrollAlign.CENTER)
            this.animationStartIdx = targetIndex
          })
    
          Row({ space: 10 }) {
            Row() {
              List({ scroller: this.listScroller, space: 10 }) {
                ForEach(this.mockData, (item: string, idx: number) => {
                  this.tabBuilder(item, idx)
                })
              }
              .width('100%')
              .height(40)
              .listDirection(Axis.Horizontal)
              .scrollBar(BarState.Off)
              .fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(40) })
            }
            .height(40)
            .layoutWeight(1)
    
            Row() {
              Image($r('app.media.startIcon'))
                .width(20)
                .aspectRatio(1)
            }
            .width(40)
            .aspectRatio(1)
            .backgroundColor('#80e0dede')
            .borderRadius('50%')
            .justifyContent(FlexAlign.Center)
            .onClick(() => this.getUIContext().getPromptAction().showToast({ message: '周二周二浑浑噩噩' }))
          }
          .width('100%')
          .height(40)
        }
      }
      .width('100%')
      .height('100%')
    }
    }
    
  • 自定义tabs(V1-V2组件都可用)不使用fadingEdge 适合更低版本

    @Component
    export struct tabsMoreButAndFadingEdge {
    private mockData: string[] = ['购物', '体育', '财经', '服装', '军事', '政治', '居家', '国际', '科技', '城市', '景点']
    private controller: TabsController = new TabsController()
    private listScroller: ListScroller = new ListScroller()
    @State selectIdx: number = 0
    @State animationStartIdx: number = 0
    @State startFadingEdge: boolean = false
    @State endFadingEdge: boolean = false
    
    @Builder
    tabBuilder(label: string, idx: number) {
      Text(label)
        .width(60)
        .height(40)
        .borderRadius('50%')
        .fontSize(16)
        .fontColor(this.animationStartIdx === idx ? Color.White : Color.Black)
        .backgroundColor(this.animationStartIdx === idx ? Color.Orange : '#80e0dede')
        .textAlign(TextAlign.Center)
        .onClick(() => {
          this.controller.changeIndex(idx)
          this.listScroller.scrollToIndex(idx, true, ScrollAlign.CENTER)
          this.selectIdx = this.animationStartIdx = idx
        })
    }
    
    build() {
      Column() {
        Stack({ alignContent: Alignment.TopStart }) {
          Tabs({ index: this.selectIdx }) {
            ForEach(this.mockData, (item: string) => {
              TabContent() {
                Column() {
                  Text(item)
                }
                .width('100%')
                .height('100%')
                .backgroundColor('#66e2dcdc')
                .justifyContent(FlexAlign.Start)
              }
            })
          }
          .barHeight(0)
          .padding({ top: 50 }) // 关键:这里解决自定义tabs上面缺失的barHeight高度
          .onChange((idx: number) => {
            this.selectIdx = idx
          })
          // 关键:切换动画开始时触发该回调:解决切换tabs延迟的问题
          .onAnimationStart((idx: number, targetIndex: number) => {
            if (idx === targetIndex) {
              return
            }
    
            this.listScroller.scrollToIndex(targetIndex, true, ScrollAlign.CENTER)
            this.animationStartIdx = targetIndex
          })
    
          Row({ space: 10 }) {
            Stack() {
              Row() {
                List({ scroller: this.listScroller, space: 10 }) {
                  ForEach(this.mockData, (item: string, idx: number) => {
                    this.tabBuilder(item, idx)
                  })
                }
                .width('100%')
                .height(40)
                .listDirection(Axis.Horizontal)
                .scrollBar(BarState.Off)
                .onScrollIndex(() => {
                  this.startFadingEdge = true
                  this.endFadingEdge = true
                })
                .onReachStart(() => {
                  this.startFadingEdge = false
                  this.endFadingEdge = true
                })
                .onReachEnd(() => {
                  this.startFadingEdge = true
                  this.endFadingEdge = false
                })
              }
    
              Row() {
                Text()
                  .width(60)
                  .height(40)
                  .linearGradient({
                    direction: GradientDirection.Left,
                    colors: [['#00ffffff', 0.0], ['#ffffffff', 1.0]]
                  })
                  .visibility(this.startFadingEdge ? Visibility.Visible : Visibility.Hidden)
    
                Blank()
    
                Text()
                  .width(60)
                  .height(40)
                  .linearGradient({
                    direction: GradientDirection.Right,
                    colors: [['#00ffffff', 0.0], ['#ffffffff', 1.0]]
                  })
                  .visibility(this.endFadingEdge ? Visibility.Visible : Visibility.Hidden)
    
              }
              .width('100%')
              .height(40)
              .hitTestBehavior(HitTestMode.Transparent) // 关键:自身和子节点均响应触摸测试,不会阻塞兄弟节点和祖先节点的触摸测试
            }
            .height(40)
            .layoutWeight(1)
    
            Row() {
              Image($r('app.media.startIcon'))
                .width(20)
                .aspectRatio(1)
            }
            .width(40)
            .aspectRatio(1)
            .backgroundColor('#80e0dede')
            .borderRadius('50%')
            .justifyContent(FlexAlign.Center)
            .onClick(() => this.getUIContext().getPromptAction().showToast({ message: '周二周二浑浑噩噩' }))
          }
          .width('100%')
          .height(40)
        }
      }
      .width('100%')
      .height('100%')
    }
    }
    

🌸🌼🌺

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐