开天辟地 HarmonyOS(鸿蒙) - 组件(导航类): Tabs(页签导航)

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 组件(导航类): Tabs(页签导航)

示例如下:

pages\component\navigation\TabsDemo.ets

/*
 * Tabs - 页签导航
 * 包括页签栏和内容区
 *
 * 注:Tabs 的子组件通过 TabContent 定义某个页签及其对应的内容,详见 TabContentDemo.ets 中的说明
 */

import { TitleBar, RadioBar, MyLog } from '../../TitleBar';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct TabsDemo {

  build() {
    Column() {
      TitleBar()
      Tabs() {
        TabContent() { MySample1() }.tabBar('基础1').align(Alignment.Top)
        TabContent() { MySample2() }.tabBar('基础2').align(Alignment.Top)
        TabContent() { MySample3() }.tabBar('基础3').align(Alignment.Top)
        TabContent() { MySample4() }.tabBar('切换动画').align(Alignment.Top)
        TabContent() { MySample5() }.tabBar('预加载').align(Alignment.Top)
      }
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .layoutWeight(1)
    }
  }
}

@Component
struct MySample1 {

  @State currentIndex: number = 0
  @State vertical: boolean = false
  @State message: string = ""

  /*
   * TabsController - 用于和绑定的 Tabs 之间的交互
   *   changeIndex() - 切换到指定索引位置的页签
   */
  private controller: TabsController = new TabsController()

  @Builder tabBarBuilder(index: number, name: string) {
    Column() {
      Text(name).fontSize(24)
        .fontColor(this.currentIndex === index ? Color.Black : Color.White)

      Divider().strokeWidth(2)
        .color(this.currentIndex === index ? Color.Black : Color.White)
        .opacity(this.currentIndex === index ? 1 : 0)
    }
    .backgroundColor(Color.Orange)
  }

  /*
   * Tabs - 页签导航
   *   barPosition - 页签栏的位置
   *     BarPosition.Start - 在顶部或左侧
   *     BarPosition.End - 在底部或右侧
   *   index - 当前选中的页签的索引位置
   *   controller - 绑定的 TabsController 对象
   *   vertical() - 页签栏中的页签是垂直排列还是水平排列
   *   scrollable() - 是否允许在内容区通过滑动手势切换页签
   *   barMode() - 页签栏的布局模式(BarMode 枚举)
   *     Fixed - 页签栏不可滚动,每个页签的宽度会被平均分配
   *     Scrollable - 每个页签的宽度由其内容决定,如果显示不下则页签栏可滚动
   *   barWidth(), barHeight(), barBackgroundColor(), barBackgroundBlurStyle() - 页签栏的宽度,高度,背景色,模糊效果
   *   divider() - 页签栏与内容区之间的分隔线
   *     strokeWidth, color, startMargin, endMargin
   *     注:如果需要隐藏分隔线的话,就 divider(null) 即可
   *   animationDuration() - 页签切换动画的时长
   *   onChange() - 页签切换后的回调
   *   onTabBarClick() - 页签点击后的回调
   *   onContentWillChange() - 切换页签前的回调,可用于页签的切换拦截
   *     currentIndex - 切换前的页签的索引位置
   *     comingIndex - 切换后的页签的索引位置
   *     return true - 允许切换到新页签
   *     return false - 阻止切换到新页签
   *   onGestureSwipe() - 在内容区通过滑动手势切换时的回调
   *     index - 切换前的页签的索引位置
   *     event - 事件参数(一个 TabsAnimationEvent 对象)
   *       currentOffset - 切换前的内容区的内容的当前的偏移距离
   */
  build() {
    Column({space:10}) {

      Text(this.message)

      Button(`vertical:${this.vertical}`).onClick(() => {
        this.vertical = !this.vertical
      })

      Button(`controller.changeIndex(1)`).onClick(() => {
        // 通过 TabsController 切换到指定索引位置的页签
        this.controller.changeIndex(1)
      })

      Tabs({
        barPosition: BarPosition.Start,
        index: this.currentIndex,
        controller: this.controller
      }) {
        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Red)
        }.tabBar(this.tabBarBuilder(0, 'red'))

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Green)
        }.tabBar(this.tabBarBuilder(1, 'green'))

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Blue)
        }.tabBar(this.tabBarBuilder(2, 'blue'))
      }
      .vertical(this.vertical)
      .scrollable(true)
      .barMode(BarMode.Fixed)
      .barWidth(this.vertical ? "64vp" : '100%')
      .barHeight(this.vertical ? "100%" : '64vp')
      .barBackgroundColor(Color.Yellow)
      .barBackgroundBlurStyle(BlurStyle.Thin)
      // .divider(null)
      .divider({
        strokeWidth: 5,
        color: Color.Pink,
        startMargin: 0,
        endMargin: 0
      })
      .animationDuration(1000)
      .onChange((index: number) => {
        this.message = `onChange:${index}`
        this.currentIndex = index
      })
      .onTabBarClick((index: number) => {
        this.message = `onTabBarClick:${index}`
      })
      // 页签的切换拦截
      .onContentWillChange((currentIndex: number, comingIndex: number) => {
        this.message = `onContentWillChange currentIndex:${currentIndex}, comingIndex:${comingIndex}`
        return true
      })
      .onGestureSwipe((index: number, event: TabsAnimationEvent) => {
        this.message = `onGestureSwipe index:${index}, event:${JSON.stringify(event)}`
      })
      .layoutWeight(1)
    }
  }
}

@Component
struct MySample2 {

  @State barOverlap: boolean = false

  build() {
    Column({space:10}) {

      Button(`barOverlap:${this.barOverlap}`).onClick(() => {
        this.barOverlap = !this.barOverlap
      })

      /*
       * Tabs - 页签导航
       *   barMode() - 页签栏的布局模式
       *     value - BarMode 枚举
       *       Fixed - 页签栏不可滚动,每个页签的宽度会被平均分配
       *       Scrollable - 每个页签的宽度由其内容决定,如果显示不下则页签栏可滚动
       *     options - 选项(一个 ScrollableBarModeOptions 对象)
       *       margin - 左右的外边距(仅 BarMode.Scrollable 时有效)
       *       nonScrollableLayoutStyle - 当页签栏不需要滚动时,每个页签的排列方式(仅 BarMode.Scrollable 时有效)
       *         LayoutStyle.ALWAYS_CENTER - 紧凑居中
       *         LayoutStyle.ALWAYS_AVERAGE_SPLIT - 平均分配
       *         LayoutStyle.SPACE_BETWEEN_OR_CENTER
       *           所有页签的内容的宽度大于页签栏的宽度的一半时,则紧凑居中
       *           所有页签的内容的宽度小于页签栏的宽度的一半时,则在居中的页签栏的一半的宽度内排列且每个页签的间距相同
       *   barOverlap() - 页签栏是否要覆盖在内容区之上
       *   fadingEdge() - 页签栏可滚动时,两端是否启用渐隐效果
       *   edgeEffect() - 页签栏可滚动时,边缘的回弹效果(EdgeEffect 枚举)
       *     Spring - 弹性效果
       *     Fade - 圆弧阴影效果
       *     None - 无效果
       */
      Tabs() {
        TabContent() {
          Column() {
            Text("111")
            Text("222")
            Text("333")
            Text("444")
            Text("555")
            Text("666")
            Text("777")
            Text("888")
            Text("999")
          }.width('100%').height('100%').backgroundColor(Color.Red)
        }.tabBar("aaaaaaaaaaaaaaaaaaaa")

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Green)
        }.tabBar("bbbbbbbbbbbbbbbbbbbb")

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Blue)
        }.tabBar("cccccccccccccccccccc")
      }
      .backgroundColor(Color.Yellow)
      .barMode(BarMode.Scrollable, {
        margin: 10,
        nonScrollableLayoutStyle: LayoutStyle.ALWAYS_AVERAGE_SPLIT
      })
      .edgeEffect(EdgeEffect.Spring)
      .barOverlap(this.barOverlap)
      .fadingEdge(true)
      .barWidth('100%')
      .barHeight(64)
    }
  }
}

@Component
struct MySample3 {

  @State barGridAlign_margin: number = 0
  @State barGridAlign_gutter: number = 0
  @State barGridAlign_columns: number = 2

  build() {
    Column({space:10}) {

      Button(`barGridAlign_margin:${this.barGridAlign_margin}`).onClick(() => {
        this.barGridAlign_margin += 10
      })

      Button(`barGridAlign_gutter:${this.barGridAlign_gutter}`).onClick(() => {
        this.barGridAlign_gutter += 10
      })

      Button(`barGridAlign_columns:${this.barGridAlign_columns}`).onClick(() => {
        this.barGridAlign_columns += 2
      })

      /*
       * Tabs - 页签导航
       *   barGridAlign() - 栅格化页签栏(一个 BarGridColumnOptions 对象)
       *     margin - 栅格化后 column 的边距
       *     gutter - 栅格化后 column 的间距
       *     sm, md, lg - 栅格化后,小中大屏的 column 的数量
       */
      Tabs() {
        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Red)
        }.tabBar({
          icon: $r("app.media.app_icon"),
          text: "aaa"
        })

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Green)
        }.tabBar({
          icon: $r("app.media.app_icon"),
          text: "bbb"
        })

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Blue)
        }.tabBar({
          icon: $r("app.media.app_icon"),
          text: "ccc"
        })
      }
      .barWidth('100%')
      .barHeight(64)
      .barGridAlign({
        margin: this.barGridAlign_margin,
        gutter: this.barGridAlign_gutter,
        sm: this.barGridAlign_columns,
        md: this.barGridAlign_columns,
        lg: this.barGridAlign_columns,
      })
    }
  }
}

@Component
struct MySample4 {

  @State message: string = ""

  @State currentIndex: number = 0
  @State opacityList: number[] = [1.0, 1.0, 1.0]
  @State scaleList: number[] = [1.0, 1.0, 1.0]

  private customContentTransition: (from: number, to: number) => TabContentAnimatedTransition = (from: number, to: number) => {
    return {
      timeout: 1000,
      transition: (proxy: TabContentTransitionProxy) => {
        this.scaleList[from] = 1.0
        this.scaleList[to] = 0.5
        this.opacityList[from] = 1.0
        this.opacityList[to] = 0.5
        animateTo({
          duration: 1000,
          onFinish: () => {
            proxy.finishTransition()
          }
        }, () => {
          this.scaleList[from] = 0.5
          this.scaleList[to] = 1.0
          this.opacityList[from] = 0.5
          this.opacityList[to] = 1.0
        })
      }
    } as TabContentAnimatedTransition
  }

  build() {
    Column({space:10}) {

      Text(this.message)

      /*
       * Tabs - 页签导航
       *   customContentTransition() - 自定义内容区的切换动画
       *     from - 切换前的页签的索引位置
       *     to - 切换后的页签的索引位置
       *     返回值是一个 TabContentAnimatedTransition 对象
       *       timeout - 自定义切换动画的超时时间
       *       transition - 自定义切换动画启动时的回调(回调参数是一个 TabContentTransitionProxy 对象)
       *         from - 切换前的页签的索引位置
       *         to - 切换后的页签的索引位置
       *         finishTransition - 告知 Tabs 组件,自定义切换动画已结束
       *   onAnimationStart() - 动画启动时的回调
       *     index - 切换前的页签的索引位置
       *     targetIndex - 切换后的页签的索引位置
       *     event - 事件参数(一个 TabsAnimationEvent 对象)
       *       currentOffset - 切换前的内容区的内容的当前的偏移距离
       *   onAnimationEnd() - 动画结束时的回调
       *     index - 切换后的页签的索引位置
       *     event - 事件参数(一个 TabsAnimationEvent 对象)
       *       currentOffset - 切换前的内容区的内容的当前的偏移距离
       */
      Tabs({
        index: this.currentIndex
      }) {
        TabContent() {
          Column().width('100%').height('100%')
            .backgroundColor(Color.Red)
            .opacity(this.opacityList[0])
            .scale({ x: this.scaleList[0], y: this.scaleList[0] })
        }.tabBar("red")

        TabContent() {
          Column().width('100%').height('100%')
            .backgroundColor(Color.Green)
            .opacity(this.opacityList[1])
            .scale({ x: this.scaleList[1], y: this.scaleList[1] })
        }.tabBar("green")

        TabContent() {
          Column().width('100%').height('100%')
            .backgroundColor(Color.Blue)
            .opacity(this.opacityList[2])
            .scale({ x: this.scaleList[2], y: this.scaleList[2] })
        }.tabBar("blue")
      }
      .customContentTransition(this.customContentTransition)
      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
        this.message = `onAnimationStart index:${index}, targetIndex:${targetIndex}, event:${JSON.stringify(event)}`
        this.currentIndex = targetIndex
      })
      .onAnimationEnd((index: number, event: TabsAnimationEvent) => {
        this.message = `onAnimationEnd index:${index}, event:${JSON.stringify(event)}`
      })
    }
  }
}

@Component
struct MySample5 {

  @State currentIndex: number = 0

  /*
   * TabsController - 用于和绑定的 Tabs 之间的交互
   *   changeIndex() - 切换到指定索引位置的页签
   *   preloadItems() - 预加载指定索引位置的页签
   */
  private controller: TabsController = new TabsController()

  build() {
    Column({space:10}) {

      Button('preloadItems([2, 3])').onClick(() => {
        this.controller.preloadItems([2, 3])
          .then(() => {
            MyLog.d('preloadItems 成功')
          })
          .catch((error: BusinessError) => {
            MyLog.d(`preloadItems 失败 code:${error.code}, message:${error.message}`)
          })
      })

      /*
       * Tabs - 页签导航
       *   controller - 绑定的 TabsController 对象
       */
      Tabs({
        index: this.currentIndex,
        controller: this.controller
      }) {
        TabContent() {
          MyComponent({ text: "red" }).backgroundColor(Color.Red)
        }.tabBar("red")

        TabContent() {
          MyComponent({ text: "green" }).backgroundColor(Color.Green)
        }.tabBar("green")

        TabContent() {
          MyComponent({ text: "blue" }).backgroundColor(Color.Blue)
        }.tabBar("blue")

        TabContent() {
          MyComponent({ text: "orange" }).backgroundColor(Color.Orange)
        }.tabBar("orange")
      }
      .onChange((index: number) => {
        this.currentIndex = index
      })
    }
  }
}
@Component
struct MyComponent {
  private text: string = ""

  aboutToAppear(): void {
    console.info('aboutToAppear:' + this.text)
  }

  aboutToDisappear(): void {
    console.info('aboutToDisappear:' + this.text)
  }

  build() {
    Column() {
      Text(this.text).fontColor(Color.White).fontSize(64)
    }.width('100%').height('100%')
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

posted @ 2025-02-06 08:12  webabcd  阅读(129)  评论(0)    收藏  举报