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

35 阅读1分钟

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

🌍 案例集合Tabs

🏷️ 效果图

📖 参考

🧩 拆解

  • 自定义tabs(V1-V2组件都可用)使用fadingEdge Api 14
@Component
export struct tabsMoreButAndFadingEdge {
  private mockData: string[] = ['购物', '体育', '财经', '服装', '军事', '政治', '居家', '国际', '科技', '城市', '景点']
  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.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 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.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%')
  }
}

🌸🌼🌺