# Tabs 通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。 > **说明:** > > 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 > > 该组件从API Version 11开始默认支持安全区避让特性(默认值为:expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])),开发者可以重写该属性覆盖默认行为,API Version 11之前的版本需配合[expandSafeArea](ts-universal-attributes-expand-safe-area.md)属性实现安全区避让。 ## 子组件 不支持自定义组件作为子组件, 仅可包含子组件[TabContent](ts-container-tabcontent.md), 以及渲染控制类型[if/else](../../../quick-start/arkts-rendering-control-ifelse.md)和[ForEach](../../../quick-start/arkts-rendering-control-foreach.md), 并且if/else和ForEach下也仅支持TabContent, 不支持自定义组件。 > **说明:** > > Tabs子组件的visibility属性设置为None,或者visibility属性设置为Hidden时,对应子组件不显示,但依然会在视窗内占位。 > > Tabs子组件TabContent显示之后不会销毁,若需要页面懒加载和释放,可以参考[示例12](#示例12页面懒加载和释放)。 ## 接口 Tabs(value?: {barPosition?: BarPosition, index?: number, controller?: TabsController}) **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----------- | --------------------------------- | ---- | ---------------------------------------- | | barPosition | [BarPosition](#barposition枚举说明)| 否 | 设置Tabs的页签位置。
默认值:BarPosition.Start | | index | number | 否 | 设置当前显示页签的索引。
默认值:0
**说明:**
设置为小于0的值时按默认值显示。
可选值为[0, TabContent子节点数量-1]。
直接修改index跳页时,切换动效不生效。 使用TabController的changeIndex时,默认生效切换动效,可以设置animationDuration为0关闭动画。
从API version 10开始,该参数支持[$$](../../../quick-start/arkts-two-way-sync.md)双向绑定变量。
Tabs重建、系统资源切换(如系统字体切换、系统深浅色切换)或者组件属性变化时,会跳转到index对应的页面。若需要在上述情况下不跳转,建议使用双向绑定。 | | controller | [TabsController](#tabscontroller) | 否 | 设置Tabs控制器。 | ## BarPosition枚举说明 Tabs页签位置枚举。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 说明 | | ----- | ------------------------------------------------------------ | | Start | vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。 | | End | vertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。 | ## 属性 除支持[通用属性](ts-universal-attributes-size.md)外,还支持以下属性: ### vertical vertical(value: boolean) 设置是否为纵向Tab。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------- | ---- | ------------------------------------------------------------ | | value | boolean | 是 | 是否为纵向Tab。
默认值:false,横向Tabs,为true时纵向Tabs。
当横向Tabs设置height为auto时,Tabs组件高度自适应子组件高度,即为tabBar高度+divider线宽+TabContent高度+上下padding值+上下border宽度。
当纵向Tabs设置width为auto时,Tabs组件宽度自适应子组件宽度,即为tabBar宽度+divider线宽+TabContent宽度+左右padding值+左右border宽度。
尽量保持每一个页面中的子组件尺寸大小一致,避免滑动页面时出现页面切换动画跳动现象。 | ### scrollable scrollable(value: boolean) 设置是否可以通过滑动页面进行页面切换。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------- | ---- | ------------------------------------------------------------ | | value | boolean | 是 | 是否可以通过滑动页面进行页面切换。
默认值:true,可以通过滑动页面进行页面切换。为false时不可滑动切换页面。 | ### barMode barMode(value: BarMode, options?: ScrollableBarModeOptions) 设置TabBar布局模式。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | --------------------- | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | | value | [BarMode](#barmode枚举说明) | 是 | 布局模式。
默认值:BarMode.Fixed | | options10+ | [ScrollableBarModeOptions](#scrollablebarmodeoptions10对象说明) | 否 | Scrollable模式下的TabBar的布局样式。
**说明:**
仅Scrollable且水平模式下有效。 | ### barMode10+ barMode(value: BarMode.Fixed) 设置TabBar布局模式为BarMode.Fixed。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | -------- | -------------------------------- | ---- | ------------------------------------ | | value | [BarMode.Fixed](#barmode枚举说明) | 是 | 所有TabBar会平均分配barWidth宽度(纵向时平均分配barHeight高度)。 | ### barMode10+ barMode(value: BarMode.Scrollable, options: ScrollableBarModeOptions) 设置TabBar布局模式为BarMode.Scrollable。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | -------- | --------------------------------- | ---- | ------------------------------------- | | value | [BarMode.Scrollable](#barmode枚举说明) | 是 | 所有TabBar都使用实际布局宽度,超过总宽度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。 | | options | [ScrollableBarModeOptions](#scrollablebarmodeoptions10对象说明) | 是 | Scrollable模式下的TabBar的布局样式。
**说明:**
仅水平模式下有效。 | ### barWidth barWidth(value: Length) 设置TabBar的宽度值。设置为小于0或大于Tabs宽度值时,按默认值显示。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ----------------------------------------- | ---- | ------------------------------------------------------------ | | value | [Length](ts-types.md#length)8+ | 是 | TabBar的宽度值。
默认值:
未设置[SubTabBarStyle](ts-container-tabcontent.md#subtabbarstyle9)和[BottomTabBarStyle](ts-container-tabcontent.md#bottomtabbarstyle9)的TabBar且vertical属性为false时,默认值为Tabs的宽度。
未设置SubTabBarStyle和BottomTabBarStyle的TabBar且vertical属性为true时,默认值为56vp。
设置SubTabBarStyle样式且vertical属性为false时,默认值为Tabs的宽度。
设置SubTabBarStyle样式且vertical属性为true时,默认值为56vp。
设置BottomTabBarStyle样式且vertical属性为true时,默认值为96vp。
设置BottomTabBarStyle样式且vertical属性为false时,默认值为Tabs的宽度。 | ### barHeight barHeight(value: Length) 设置TabBar的高度值。设置为'auto'时,TabBar自适应子组件高度,仅在水平模式下有效。设置为小于0或大于Tabs高度值时,按默认值显示。 API Version 14之前的版本,若设置barHeight为固定值后,TabBar无法扩展底部安全区。从API Version 14开始支持配合[safeAreaPadding](./ts-universal-attributes-size.md#safeareapadding14)属性,当safeAreaPadding不设置bottom或者bottom设置为0时,可以实现扩展安全区。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ----------------------------------------- | ---- | ------------------------------------------------------------ | | value | [Length](ts-types.md#length)8+ | 是 | TabBar的高度值。
默认值:
未设置带样式的TabBar且vertical属性为false时,默认值为56vp。
未设置带样式的TabBar且vertical属性为true时,默认值为Tabs的高度。
设置[SubTabBarStyle](ts-container-tabcontent.md#subtabbarstyle9)样式且vertical属性为false时,默认值为56vp。
设置SubTabBarStyle样式且vertical属性为true时,默认值为Tabs的高度。
设置[BottomTabBarStyle](ts-container-tabcontent.md#bottomtabbarstyle9)样式且vertical属性为true时,默认值为Tabs的高度。
设置BottomTabBarStyle样式且vertical属性为false时,默认值为56vp, 从API Version 12开始,默认值变更为48vp。 | ### animationDuration animationDuration(value: number) 设置点击TabBar页签和调用TabsController的changeIndex接口切换TabContent的动画时长。该参数不支持百分比设置。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ------------------------------------------------------------ | | value | number | 是 | 点击TabBar页签和调用TabsController的changeIndex接口切换TabContent的动画时长。
默认值:
API version 10及以前,不设置该属性或设置为null时,默认值为0,即点击TabBar页签和调用TabsController的changeIndex接口切换TabContent无动画。设置为小于0或undefined时,默认值为300。
API version 11及以后,不设置该属性或设置为异常值,且设置TabBar为BottomTabBarStyle样式时,默认值为0。设置TabBar为其他样式时,默认值为300。
单位:ms | ### animationMode12+ animationMode(mode: Optional\) 设置点击TabBar页签时切换TabContent的动画形式。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ------------------------------------------------------------ | | mode | Optional\<[AnimationMode](#animationmode12枚举说明)\> | 是 | 点击TabBar页签时切换TabContent的动画形式。
默认值:
默认值是AnimationMode.CONTENT_FIRST,表示在点击TabBar切换TabContent时,先加载目标页内容,再开始切换动画。| ### barPosition9+ barPosition(value: BarPosition) 设置Tabs的页签位置。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----- | ---------------------------------- | ---- | -------------------- | | value | [BarPosition](#barposition枚举说明)| 是 | 设置Tabs的页签位置。
默认值:BarPosition.Start | ### divider10+ divider(value: DividerStyle | null) 设置区分TabBar和TabContent的分割线样式。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | --------------------------------------------------------- | ---- | ------------------------------------------------------------ | | value | [DividerStyle](#dividerstyle10对象说明) \| null | 是 | 分割线样式,默认不显示分割线。
DividerStyle: 分割线的样式;
null: 不显示分割线。 | ### fadingEdge10+ fadingEdge(value: boolean) 设置页签超过容器宽度时是否渐隐消失。建议配合barBackgroundColor属性一起使用,如果barBackgroundColor属性没有定义,会默认显示页签末端为白色的渐隐效果。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------- | ---- | -------------------------------------------------- | | value | boolean | 是 | 页签超过容器宽度时是否渐隐消失。
默认值:true | ### barOverlap10+ barOverlap(value: boolean) 设置TabBar是否背后变模糊并叠加在TabContent之上。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------- | ---- | ------------------------------------------------------------ | | value | boolean | 是 | TabBar是否背后变模糊并叠加在TabContent之上。当barOverlap设置为true时,TabBar默认模糊材质的BlurStyle值修改为'BlurStyle.COMPONENT_THICK'。
默认值:false | ### barBackgroundColor10+ barBackgroundColor(value: ResourceColor) 设置TabBar的背景颜色。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------------------------------------------ | ---- | ------------------------------------ | | value | [ResourceColor](ts-types.md#resourcecolor) | 是 | TabBar的背景颜色。
默认值:Color.Transparent,透明 | ### barBackgroundBlurStyle11+ barBackgroundBlurStyle(value: BlurStyle) 设置TabBar的背景模糊材质。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | -------------------------------------------- | ---- | ---------------------------------------- | | value | [BlurStyle](ts-universal-attributes-background.md#blurstyle9) | 是 | TabBar的背景模糊材质。
默认值:BlurStyle.NONE | ### barGridAlign10+ barGridAlign(value: BarGridColumnOptions) 以栅格化方式设置TabBar的可见区域。具体参见BarGridColumnOptions对象。仅水平模式下有效,[不适用于XS、XL和XXL设备](../../../ui/arkts-layout-development-grid-layout.md#栅格系统断点)。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------------------------------------------------------- | ---- | ---------------------------------- | | value | [BarGridColumnOptions](#bargridcolumnoptions10对象说明) | 是 | 以栅格化方式设置TabBar的可见区域。 | ### edgeEffect12+ edgeEffect(edgeEffect: Optional<EdgeEffect>) 设置边缘回弹效果。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | --------------------------------------------- | ---- | -------------------------------------------- | | edgeEffect | Optional<[EdgeEffect](ts-appendix-enums.md#edgeeffect)> | 是 | 边缘滑动效果。
默认值:EdgeEffect.Spring | ## DividerStyle10+对象说明 分割线样式对象。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | ----------- | ---------------------------------------- | ---- | ---------------------------------------- | | strokeWidth | [Length](ts-types.md#length) | 是 | 分割线的线宽(不支持百分比设置)。
默认值:0.0
单位:vp | | color | [ResourceColor](ts-types.md#resourcecolor) | 否 | 分割线的颜色。
默认值:#33182431 | | startMargin | [Length](ts-types.md#length) | 否 | 分割线与侧边栏顶端的距离(不支持百分比设置)。
默认值:0.0
单位:vp | | endMargin | [Length](ts-types.md#length) | 否 | 分割线与侧边栏底端的距离(不支持百分比设置)。
默认值:0.0
单位:vp | ## BarGridColumnOptions10+对象说明 TabBar栅格化方式设置的对象,包括栅格模式下的column边距和间隔,以及小、中、大屏下,页签占用的columns数量。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | ----------- | ---------------------------------------- | ---- | ---------------------------------------- | | margin | [Dimension](ts-types.md#dimension10) | 否 | 栅格模式下的column边距(不支持百分比设置)。
默认值:24.0
单位:vp | | gutter | [Dimension](ts-types.md#dimension10) | 否 | 栅格模式下的column间隔(不支持百分比设置)。
默认值:24.0
单位:vp | | sm | number | 否 | 小屏下,页签占用的columns数量,必须是非负偶数。小屏为大于等于320vp但小于600vp。
默认值为-1,代表页签占用TabBar全部宽度。 | | md | number | 否 | 中屏下,页签占用的columns数量,必须是非负偶数。中屏为大于等于600vp但小于800vp。
默认值为-1,代表页签占用TabBar全部宽度。 | | lg | number | 否 | 大屏下,页签占用的columns数量,必须是非负偶数。大屏为大于等于840vp但小于1024vp。
默认值为-1,代表页签占用TabBar全部宽度。 | ## ScrollableBarModeOptions10+对象说明 Scrollable模式下的TabBar的布局样式对象。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | ----------- | ---------------------------------------- | ---- | ---------------------------------------- | | margin | [Dimension](ts-types.md#dimension10) | 否 | Scrollable模式下的TabBar的左右边距(不支持百分比设置)。
默认值:0.0
单位:vp | | nonScrollableLayoutStyle | [LayoutStyle](#layoutstyle10枚举说明) | 否 | Scrollable模式下不滚动时的页签排布方式。
默认值:LayoutStyle.ALWAYS_CENTER | ## BarMode枚举说明 TabBar布局模式枚举。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 值 | 说明 | | ---------- | -- | ---------------------------------------- | | Scrollable | 0 | 每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。 | | Fixed | 1 | 所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度)。 | ## AnimationMode12+枚举说明 点击TabBar页签时切换TabContent的动画形式枚举。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 值 | 说明 | | ------------- | ---- | ------------------------------------------------------------ | | CONTENT_FIRST | 0 | 先加载目标页内容,再开始切换动画 | | ACTION_FIRST | 1 | 先开始切换动画,再加载目标页内容;生效需要同时需要满足:Tabs的height、width没有设置成auto | | NO_ANIMATION | 2 | 关闭默认动画 | ## LayoutStyle10+枚举说明 Scrollable模式下不滚动时的页签排布方式枚举。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 值 | 说明 | | ---------- | -- | ---------------------------------------- | | ALWAYS_CENTER | 0 | 当页签内容超过TabBar宽度时,TabBar可滚动。
当页签内容不超过TabBar宽度时,TabBar不可滚动,页签紧凑居中。| | ALWAYS_AVERAGE_SPLIT | 1 | 当页签内容超过TabBar宽度时,TabBar可滚动。
当页签内容不超过TabBar宽度时,TabBar不可滚动,且所有页签平均分配TabBar宽度。| | SPACE_BETWEEN_OR_CENTER | 2 | 当页签内容超过TabBar宽度时,TabBar可滚动。
当页签内容不超过TabBar宽度但超过TabBar宽度一半时,TabBar不可滚动,页签紧凑居中。
当页签内容不超过TabBar宽度一半时,TabBar不可滚动,保证页签居中排列在TabBar宽度一半,且间距相同。| ## 事件 除支持[通用事件](ts-universal-events-click.md)外,还支持以下事件: ### onChange onChange(event: (index: number) => void) Tab页签切换后触发的事件。 触发该事件的条件: 1、滑动页面进行页面切换时,组件滑动动画结束后触发。 2、通过[控制器](#tabscontroller)API接口调用。 3、通过[状态变量](../../../quick-start/arkts-state.md)构造的属性值进行修改。 4、通过页签处点击触发。 > **说明:** > > 使用自定义页签时,在onChange事件中联动可能会导致滑动页面切换后才执行页签联动,引起自定义页签切换效果延迟。建议在[onAnimationStart](#onanimationstart11)中监听并刷新当前索引,以确保动效能够及时触发。具体实现可参考[示例1](#示例1自定义页签切换联动)。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | -------------------------------------- | | index | number | 是 | 当前显示的index索引,索引从0开始计算。 | ### onTabBarClick10+ onTabBarClick(event: (index: number) => void) Tab页签点击后触发的事件。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ------------------------------------ | | index | number | 是 | 被点击的index索引,索引从0开始计算。 | ### onAnimationStart11+ onAnimationStart(handler: (index: number, targetIndex: number, event: TabsAnimationEvent) => void) 切换动画开始时触发该回调。参数为动画开始前的index值(不是最终结束动画的index值)。当animationDuration为0时动画关闭,不触发该回调。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----------- | ------------------------------------------------------ | ---- | ------------------------------------------------------------ | | index | number | 是 | 当前显示元素的索引。 | | targetIndex | number | 是 | 切换动画目标元素的索引。 | | event | [TabsAnimationEvent](#tabsanimationevent11对象说明) | 是 | 动画相关信息,包括主轴方向上当前显示元素和目标元素相对Tabs起始位置的位移,以及离手速度。 | ### onAnimationEnd11+ onAnimationEnd(handler: (index: number, event: TabsAnimationEvent) => void) 切换动画结束时触发该回调。当Tabs切换动效结束时触发,包括动画过程中手势中断。参数为动画结束后的index值。当animationDuration为0时动画关闭,不触发该回调。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------------------------------------------------------ | ---- | ------------------------------------------------------------ | | index | number | 是 | 当前显示元素的索引。 | | event | [TabsAnimationEvent](#tabsanimationevent11对象说明) | 是 | 动画相关信息,只返回主轴方向上当前显示元素相对于Tabs起始位置的位移。 | ### onGestureSwipe11+ onGestureSwipe(handler: (index: number, event: TabsAnimationEvent) => void) 在页面跟手滑动过程中,逐帧触发该回调。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------------------------------------------------------ | ---- | ------------------------------------------------------------ | | index | number | 是 | 当前显示元素的索引。 | | event | [TabsAnimationEvent](#tabsanimationevent11对象说明) | 是 | 动画相关信息,只返回主轴方向上当前显示元素相对于Tabs起始位置的位移。 | ### customContentTransition11+ customContentTransition(delegate: (from: number, to: number) => TabContentAnimatedTransition \| undefined) 自定义Tabs页面切换动画。 使用说明: 1、当使用自定义切换动画时,Tabs组件自带的默认切换动画会被禁用,同时,页面也无法跟手滑动。
2、当设置为undefined时,表示不使用自定义切换动画,仍然使用组件自带的默认切换动画。
3、当前自定义切换动画不支持打断。
4、目前自定义切换动画只支持两种场景触发:点击页签和调用TabsController.changeIndex()接口。
5、当使用自定义切换动画时,Tabs组件支持的事件中,除了onGestureSwipe,其他事件均支持。
6、onChange和onAnimationEnd事件的触发时机需要特殊说明:如果在第一次自定义动画执行过程中,触发了第二次自定义动画,那么在开始第二次自定义动画时,就会触发第一次自定义动画的onChange和onAnimationEnd事件。
7、当使用自定义动画时,参与动画的页面布局方式会改为Stack布局。如果开发者未主动设置相关页面的zIndex属性,那么所有页面的zIndex值是一样的,页面的渲染层级会按照在组件树上的顺序(即页面的index值顺序)确定。因此,开发者需要主动修改页面的zIndex属性,来控制页面的渲染层级。 **卡片能力:** 从API version 11开始,该接口支持在ArkTS卡片中使用。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------ | ------ | ---- | ------------------------------- | | from | number | 是 | 动画开始时,当前页面的index值。 | | to | number | 是 | 动画开始时,目标页面的index值。 | **返回值:** | 类型 | 说明 | | ------------------------------------------------------------ | ------------------------ | | [TabContentAnimatedTransition](#tabcontentanimatedtransition11) \| undefined | 自定义切换动画相关信息。 | ### onContentWillChange12+ onContentWillChange(handler: (currentIndex: number, comingIndex: number) => boolean) 自定义Tabs页面切换拦截事件能力,新页面即将显示时触发该回调。 触发该回调的条件: 1、TabContent支持滑动时,滑动组件切换新页面时触发。 2、通过TabsController.changeIndex接口切换新页面时触发。 3、通过动态修改index属性值切换新页面时触发。 4、通过点击TabBar页签切换新页面时触发。 5、TabBar页签获焦后,通过键盘左右方向键等切换新页面时触发。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ------------ | ------ | ---- | ------------------------------------------ | | currentIndex | number | 是 | 当前显示页面的index索引,索引从0开始计算。 | | comingIndex | number | 是 | 将要显示的新页面的index索引。 | **返回值:** | 类型 | 说明 | | ------- | ------------------------------------------------------------ | | boolean | 当回调函数handler的返回值为true时,Tabs可以切换到新页面。
当回调函数handler的返回值为false时,Tabs无法切换到新页面,仍然显示原来页面内容。 | ## TabsAnimationEvent11+对象说明 Tabs组件动画相关信息集合。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 只读 | 可选 | 说明 | | ------------- | ---------- | ---- | ---- | ------------------------ | | currentOffset | number | 否 | 否 | Tabs当前显示元素在主轴方向上,相对于Tabs起始位置的位移。单位VP,默认值为0。| | targetOffset | number | 否 | 否 | Tabs动画目标元素在主轴方向上,相对于Tabs起始位置的位移。单位VP,默认值为0。| | velocity | number | 否 | 否 | Tabs离手动画开始时的离手速度。单位VP/S,默认值为0。| ## TabContentAnimatedTransition11+ Tabs自定义切换动画相关信息。 **卡片能力:** 从API version 11开始,该接口支持在ArkTS卡片中使用。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | ------------- | ---------------------- | ---- |---------------------- | | timeout | number | 否 | Tabs自定义切换动画超时时间。从自定义动画开始切换计时,如果到达该时间后,开发者仍未调用[TabContentTransitionProxy](#tabcontenttransitionproxy11)的finishTransition接口通知Tabs组件自定义动画结束,那么组件就会认为此次自定义动画已结束,直接执行后续操作。单位ms,默认值为1000.| | transition | [Callback](./ts-types.md#callback12)\<[TabContentTransitionProxy](#tabcontenttransitionproxy11)> | 是 | 自定义切换动画具体内容。| ## TabContentTransitionProxy11+ Tabs自定义切换动画执行过程中,返回给开发者的proxy对象。开发者可通过该对象获取自定义动画的起始和目标页面信息,同时,也可以通过调用该对象的finishTransition接口通知Tabs组件自定义动画已结束。 **卡片能力:** 从API version 11开始,该接口支持在ArkTS卡片中使用。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full ### 属性 | 名称 | 类型 | 只读 | 可选 | 说明 | | ----- | ------- | ---- | ---- | --------------------------- | | from | number | 否 | 否 | 自定义动画起始页面对应的index值。| | to | number | 否 | 否 | 自定义动画目标页面对应的index值。| ### finishTransition finishTransition(): void 通知Tabs组件,此页面的自定义动画已结束。 **卡片能力:** 从API version 11开始,该接口支持在ArkTS卡片中使用。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full ## TabsController Tabs组件的控制器,用于控制Tabs组件进行页签切换。不支持一个TabsController控制多个Tabs组件。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full ### 导入对象 ```ts let controller: TabsController = new TabsController() ``` ### constructor constructor() TabsController的构造函数。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full ### changeIndex changeIndex(value: number): void 控制Tabs切换到指定页签。 **原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------------------------------- | | value | number | 是 | 页签在Tabs里的索引值,索引值从0开始。
**说明:**
设置小于0或大于最大数量的值时,取默认值0。 | ### preloadItems12+ preloadItems(indices: Optional\>): Promise\ 控制Tabs预加载指定子节点。调用该接口后会一次性加载所有指定的子节点,因此为了性能考虑,建议分批加载子节点。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------------------------------- | | indices | Optional\> | 是 | 需预加载的子节点的下标数组。
默认值:空数组。 | **返回值:** | 类型 | 说明 | | ------------------------------------------------------------ | ------------------------ | | Promise\ | 预加载完成后触发的回调。 | **错误码:** 以下错误码的详细介绍请参见[通用错误码](../../errorcode-universal.md)错误码。 | 错误码ID | 错误信息 | | -------- | -------------------------------------------- | | 401 | Parameter invalid. Possible causes: 1. The parameter type is not Array\; 2. The parameter is an empty array; 3. The parameter contains an invalid index. | ### setTabBarTranslate13+ setTabBarTranslate(translate: TranslateOptions): void 设置TabBar的平移距离。 > **说明:** > > 当使用bindTabsToScrollable或bindTabsToNestedScrollable等接口绑定了Tabs组件和可滚动容器组件后,在滑动可滚动容器组件时,会触发所有与其绑定的Tabs组件的TabBar的显示和隐藏动效,调用setTabBarTranslate接口设置的TabBar平移距离会失效。因此不建议同时使用bindTabsToScrollable、bindTabsToNestedScrollable和setTabBarTranslate接口。 > **原子化服务API:** 从API version 13开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------------------------------- | | translate | [TranslateOptions](ts-universal-attributes-transformation.md#translateoptions对象说明) | 是 | 设置TabBar的平移距离。 | ### setTabBarOpacity13+ setTabBarOpacity(opacity: number): void 设置TabBar的不透明度。 > **说明:** > > 当使用bindTabsToScrollable或bindTabsToNestedScrollable等接口绑定了Tabs组件和可滚动容器组件后,在滑动可滚动容器组件时,会触发所有与其绑定的Tabs组件的TabBar的显示和隐藏动效,调用setTabBarOpacity接口设置的TabBar不透明度会失效。因此不建议同时使用bindTabsToScrollable、bindTabsToNestedScrollable和setTabBarOpacity接口。 > **原子化服务API:** 从API version 13开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | ----- | ------ | ---- | ---------------------------------------- | | opacity | number | 是 | 设置TabBar的不透明度,取值范围为[0.0, 1.0]。 | ## 示例 ### 示例1(自定义页签切换联动) 本示例通过onAnimationStart、onChange实现切换时自定义tabBar和TabContent的联动。 ```ts // xxx.ets @Entry @Component struct TabsExample { @State fontColor: string = '#182431' @State selectedFontColor: string = '#007DFF' @State currentIndex: number = 0 @State selectedIndex: number = 0 private controller: TabsController = new TabsController() @Builder tabBuilder(index: number, name: string) { Column() { Text(name) .fontColor(this.selectedIndex === index ? this.selectedFontColor : this.fontColor) .fontSize(16) .fontWeight(this.selectedIndex === index ? 500 : 400) .lineHeight(22) .margin({ top: 17, bottom: 7 }) Divider() .strokeWidth(2) .color('#007DFF') .opacity(this.selectedIndex === index ? 1 : 0) }.width('100%') } build() { Column() { Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) { TabContent() { Column().width('100%').height('100%').backgroundColor('#00CB87') }.tabBar(this.tabBuilder(0, 'green')) TabContent() { Column().width('100%').height('100%').backgroundColor('#007DFF') }.tabBar(this.tabBuilder(1, 'blue')) TabContent() { Column().width('100%').height('100%').backgroundColor('#FFBF00') }.tabBar(this.tabBuilder(2, 'yellow')) TabContent() { Column().width('100%').height('100%').backgroundColor('#E67C92') }.tabBar(this.tabBuilder(3, 'pink')) } .vertical(false) .barMode(BarMode.Fixed) .barWidth(360) .barHeight(56) .animationDuration(400) .onChange((index: number) => { // currentIndex控制TabContent显示页签 this.currentIndex = index this.selectedIndex = index }) .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => { if (index === targetIndex) { return } // selectedIndex控制自定义TabBar内Image和Text颜色切换 this.selectedIndex = targetIndex }) .width(360) .height(296) .margin({ top: 52 }) .backgroundColor('#F1F3F5') }.width('100%') } } ``` ![tabs2](figures/tabs2.gif) ### 示例2(分割线基本属性) 本示例通过divider实现了分割线各种属性的展示。 ```ts // xxx.ets @Entry @Component struct TabsDivider1 { private controller1: TabsController = new TabsController() @State dividerColor: string = 'red' @State strokeWidth: number = 2 @State startMargin: number = 0 @State endMargin: number = 0 @State nullFlag: boolean = false build() { Column() { Tabs({ controller: this.controller1 }) { TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Pink) }.tabBar('pink') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Yellow) }.tabBar('yellow') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Blue) }.tabBar('blue') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Red) }.tabBar('red') } .vertical(true) .scrollable(true) .barMode(BarMode.Fixed) .barWidth(70) .barHeight(200) .animationDuration(400) .onChange((index: number) => { console.info(index.toString()) }) .height('200vp') .margin({ bottom: '12vp' }) .divider(this.nullFlag ? null : { strokeWidth: this.strokeWidth, color: this.dividerColor, startMargin: this.startMargin, endMargin: this.endMargin }) Button('常规Divider').width('100%').margin({ bottom: '12vp' }) .onClick(() => { this.nullFlag = false; this.strokeWidth = 2; this.dividerColor = 'red'; this.startMargin = 0; this.endMargin = 0; }) Button('空Divider').width('100%').margin({ bottom: '12vp' }) .onClick(() => { this.nullFlag = true }) Button('颜色变为蓝色').width('100%').margin({ bottom: '12vp' }) .onClick(() => { this.dividerColor = 'blue' }) Button('宽度增加').width('100%').margin({ bottom: '12vp' }) .onClick(() => { this.strokeWidth += 2 }) Button('宽度减小').width('100%').margin({ bottom: '12vp' }) .onClick(() => { if (this.strokeWidth > 2) { this.strokeWidth -= 2 } }) Button('上边距增加').width('100%').margin({ bottom: '12vp' }) .onClick(() => { this.startMargin += 2 }) Button('上边距减少').width('100%').margin({ bottom: '12vp' }) .onClick(() => { if (this.startMargin > 2) { this.startMargin -= 2 } }) Button('下边距增加').width('100%').margin({ bottom: '12vp' }) .onClick(() => { this.endMargin += 2 }) Button('下边距减少').width('100%').margin({ bottom: '12vp' }) .onClick(() => { if (this.endMargin > 2) { this.endMargin -= 2 } }) }.padding({ top: '24vp', left: '24vp', right: '24vp' }) } } ``` ![tabs3](figures/tabs3.gif) ### 示例3(设置TabBar渐隐) 本示例通过fadingEdge实现了切换子页签渐隐和不渐隐。 ```ts // xxx.ets @Entry @Component struct TabsOpaque { @State message: string = 'Hello World' private controller: TabsController = new TabsController() private controller1: TabsController = new TabsController() @State selfFadingFade: boolean = true; build() { Column() { Button('子页签设置渐隐').width('100%').margin({ bottom: '12vp' }) .onClick((event?: ClickEvent) => { this.selfFadingFade = true; }) Button('子页签设置不渐隐').width('100%').margin({ bottom: '12vp' }) .onClick((event?: ClickEvent) => { this.selfFadingFade = false; }) Tabs({ barPosition: BarPosition.End, controller: this.controller }) { TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Pink) }.tabBar('pink') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Yellow) }.tabBar('yellow') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Blue) }.tabBar('blue') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') } .vertical(false) .scrollable(true) .barMode(BarMode.Scrollable) .barHeight(80) .animationDuration(400) .onChange((index: number) => { console.info(index.toString()) }) .fadingEdge(this.selfFadingFade) .height('30%') .width('100%') Tabs({ barPosition: BarPosition.Start, controller: this.controller1 }) { TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Pink) }.tabBar('pink') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Yellow) }.tabBar('yellow') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Blue) }.tabBar('blue') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar('green') } .vertical(true) .scrollable(true) .barMode(BarMode.Scrollable) .barHeight(200) .barWidth(80) .animationDuration(400) .onChange((index: number) => { console.info(index.toString()) }) .fadingEdge(this.selfFadingFade) .height('30%') .width('100%') } .padding({ top: '24vp', left: '24vp', right: '24vp' }) } } ``` ![tabs4](figures/tabs4.gif) ### 示例4(设置TabBar叠加在TabContent内容上) 本示例通过barOverlap实现了TabBar是否背后变模糊并叠加在TabContent之上。 ```ts // xxx.ets @Entry @Component struct barHeightTest { @State arr: number[] = [0, 1, 2, 3] @State barOverlap: boolean = true; build() { Column() { Text(`barOverlap ${this.barOverlap}`).fontSize(16) Button("barOverlap变化").width('100%').margin({ bottom: '12vp' }) .onClick((event?: ClickEvent) => { if (this.barOverlap) { this.barOverlap = false; } else { this.barOverlap = true; } }) Tabs({ barPosition: BarPosition.End }) { TabContent() { Column() { List({ space: 10 }) { ForEach(this.arr, (item: number) => { ListItem() { Text("item" + item).width('80%').height(200).fontSize(16).textAlign(TextAlign.Center).backgroundColor('#fff8b81e') } }, (item: string) => item) }.width('100%').height('100%') .lanes(2).alignListItem(ListItemAlign.Center) }.width('100%').height('100%') .backgroundColor(Color.Pink) } .tabBar(new BottomTabBarStyle($r('sys.media.ohos_icon_mask_svg'), "测试0")) } .scrollable(false) .height('60%') .barOverlap(this.barOverlap) } .height(500) .padding({ top: '24vp', left: '24vp', right: '24vp' }) } } ``` ![tabs5](figures/tabs5.gif) ### 示例5(设置TabBar栅格化可见区域) 本示例通过barGridAlign实现了以栅格化方式设置TabBar的可见区域。 ```ts // xxx.ets @Entry @Component struct TabsExample5 { private controller: TabsController = new TabsController() @State gridMargin: number = 10 @State gridGutter: number = 10 @State sm: number = -2 @State clickedContent: string = ""; build() { Column() { Row() { Button("gridMargin+10 " + this.gridMargin) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.gridMargin += 10 }) .margin({ right: '6%', bottom: '12vp' }) Button("gridMargin-10 " + this.gridMargin) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.gridMargin -= 10 }) .margin({ bottom: '12vp' }) } Row() { Button("gridGutter+10 " + this.gridGutter) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.gridGutter += 10 }) .margin({ right: '6%', bottom: '12vp' }) Button("gridGutter-10 " + this.gridGutter) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.gridGutter -= 10 }) .margin({ bottom: '12vp' }) } Row() { Button("sm+2 " + this.sm) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.sm += 2 }) .margin({ right: '6%' }) Button("sm-2 " + this.sm).width('47%').height(50).margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.sm -= 2 }) } Text("点击内容:" + this.clickedContent).width('100%').height(200).margin({ top: 5 }) Tabs({ barPosition: BarPosition.End, controller: this.controller }) { TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Pink) }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "1")) TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "2")) TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Blue) }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "3")) } .width('350vp') .animationDuration(300) .height('60%') .barGridAlign({ sm: this.sm, margin: this.gridMargin, gutter: this.gridGutter }) .backgroundColor(0xf1f3f5) .onTabBarClick((index: number) => { this.clickedContent += "now index " + index + " is clicked\n"; }) } .width('100%') .height(500) .margin({ top: 5 }) .padding('10vp') } } ``` ![tabs5](figures/tabs6.gif) ### 示例6(设置Scrollable模式下的TabBar的布局样式) 本示例实现了barMode的ScrollableBarModeOptions参数,该参数仅在Scrollable模式下有效。 ```ts // xxx.ets @Entry @Component struct TabsExample6 { private controller: TabsController = new TabsController() @State scrollMargin: number = 0 @State layoutStyle: LayoutStyle = LayoutStyle.ALWAYS_CENTER @State text: string = "文本" build() { Column() { Row() { Button("scrollMargin+10 " + this.scrollMargin) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.scrollMargin += 10 }) .margin({ right: '6%', bottom: '12vp' }) Button("scrollMargin-10 " + this.scrollMargin) .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.scrollMargin -= 10 }) .margin({ bottom: '12vp' }) } Row() { Button("文本增加 ") .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.text += '文本增加' }) .margin({ right: '6%', bottom: '12vp' }) Button("文本重置") .width('47%') .height(50) .margin({ top: 5 }) .onClick((event?: ClickEvent) => { this.text = "文本" }) .margin({ bottom: '12vp' }) } Row() { Button("layoutStyle.ALWAYS_CENTER") .width('100%') .height(50) .margin({ top: 5 }) .fontSize(15) .onClick((event?: ClickEvent) => { this.layoutStyle = LayoutStyle.ALWAYS_CENTER; }) .margin({ bottom: '12vp' }) } Row() { Button("layoutStyle.ALWAYS_AVERAGE_SPLIT") .width('100%') .height(50) .margin({ top: 5 }) .fontSize(15) .onClick((event?: ClickEvent) => { this.layoutStyle = LayoutStyle.ALWAYS_AVERAGE_SPLIT; }) .margin({ bottom: '12vp' }) } Row() { Button("layoutStyle.SPACE_BETWEEN_OR_CENTER") .width('100%') .height(50) .margin({ top: 5 }) .fontSize(15) .onClick((event?: ClickEvent) => { this.layoutStyle = LayoutStyle.SPACE_BETWEEN_OR_CENTER; }) .margin({ bottom: '12vp' }) } Tabs({ barPosition: BarPosition.End, controller: this.controller }) { TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Pink) }.tabBar(SubTabBarStyle.of(this.text)) TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) }.tabBar(SubTabBarStyle.of(this.text)) TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Blue) }.tabBar(SubTabBarStyle.of(this.text)) } .animationDuration(300) .height('60%') .backgroundColor(0xf1f3f5) .barMode(BarMode.Scrollable, { margin: this.scrollMargin, nonScrollableLayoutStyle: this.layoutStyle }) } .width('100%') .height(500) .margin({ top: 5 }) .padding('24vp') } } ``` ![tabs5](figures/tabs7.gif) ### 示例7(自定义Tabs页面切换动画) 本示例通过customContentTransition实现了自定义Tabs页面的切换动画。 ```ts // xxx.ets interface itemType { text: string, backgroundColor: Color } @Entry @Component struct TabsCustomAnimationExample { @State data: itemType[] = [ { text: 'Red', backgroundColor: Color.Red }, { text: 'Yellow', backgroundColor: Color.Yellow }, { text: 'Blue', backgroundColor: Color.Blue }] @State opacityList: number[] = [] @State scaleList: number[] = [] private durationList: number[] = [] private timeoutList: number[] = [] private customContentTransition: (from: number, to: number) => TabContentAnimatedTransition = (from: number, to: number) => { let tabContentAnimatedTransition = { timeout: this.timeoutList[from], 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: this.durationList[from], 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 return tabContentAnimatedTransition } aboutToAppear(): void { let duration = 1000 let timeout = 1000 for (let i = 1; i <= this.data.length; i++) { this.opacityList.push(1.0) this.scaleList.push(1.0) this.durationList.push(duration * i) this.timeoutList.push(timeout * i) } } build() { Column() { Tabs() { ForEach(this.data, (item: itemType, index: number) => { TabContent() {} .tabBar(item.text) .backgroundColor(item.backgroundColor) // 自定义动画变化透明度、缩放页面等 .opacity(this.opacityList[index]) .scale({ x: this.scaleList[index], y: this.scaleList[index] }) }) } .backgroundColor(0xf1f3f5) .width('100%') .height(500) .customContentTransition(this.customContentTransition) } } } ``` ![tabs5](figures/tabs8.gif) ### 示例8(页面切换拦截) 本示例通过onContentWillChange实现了自定义页面手势滑动切换拦截。 ```ts //xxx.ets @Entry @Component struct TabsExample { @State selectedIndex: number = 2 @State currentIndex: number = 2 private controller: TabsController = new TabsController() @Builder tabBuilder(title: string,targetIndex: number) { Column(){ Text(title).fontColor(this.selectedIndex === targetIndex ? '#1698CE' : '#6B6B6B') }.width('100%') .height(50) .justifyContent(FlexAlign.Center) } build() { Column() { Tabs({ barPosition: BarPosition.End, index: this.currentIndex, controller: this.controller }) { TabContent() { Column(){ Text('首页的内容') }.width('100%').height('100%').backgroundColor('#00CB87').justifyContent(FlexAlign.Center) }.tabBar(this.tabBuilder('首页',0)) TabContent() { Column(){ Text('发现的内容') }.width('100%').height('100%').backgroundColor('#007DFF').justifyContent(FlexAlign.Center) }.tabBar(this.tabBuilder('发现',1)) TabContent() { Column(){ Text('推荐的内容') }.width('100%').height('100%').backgroundColor('#FFBF00').justifyContent(FlexAlign.Center) }.tabBar(this.tabBuilder('推荐',2)) TabContent() { Column(){ Text('我的内容') }.width('100%').height('100%').backgroundColor('#E67C92').justifyContent(FlexAlign.Center) }.tabBar(this.tabBuilder('我的',3)) } .vertical(false) .barMode(BarMode.Fixed) .barWidth(360) .barHeight(60) .animationDuration(0) .onChange((index: number) => { this.currentIndex = index this.selectedIndex = index }) .width(360) .height(600) .backgroundColor('#F1F3F5') .scrollable(true) .onContentWillChange((currentIndex, comingIndex) => { if (comingIndex == 2) { return false } return true }) Button('动态修改index').width('50%').margin({ top: 20 }) .onClick(()=>{ this.currentIndex = (this.currentIndex + 1) % 4 }) Button('changeIndex').width('50%').margin({ top: 20 }) .onClick(()=>{ this.currentIndex = (this.currentIndex + 1) % 4 this.controller.changeIndex(this.currentIndex) }) }.width('100%') } } ``` ![tabs9](figures/tabs9.gif) ### 示例9(自定义TabBar切换动画) 本示例通过onChange、onAnimationStart、onAnimationEnd、onGestureSwipe等接口实现了自定义TabBar的切换动画。 ```ts // EntryAbility.ets import { Configuration, UIAbility } from '@kit.AbilityKit' import { i18n } from '@kit.LocalizationKit' import { CommonUtil } from '../common/CommonUtil' export default class EntryAbility extends UIAbility { onConfigurationUpdate(newConfig: Configuration): void { // 监听系统配置变化 if (newConfig.language) { CommonUtil.setIsRTL(i18n.isRTL(newConfig.language)) } } } ``` ```ts // CommonUtil.ets import { i18n, intl } from '@kit.LocalizationKit' export class CommonUtil { private static isRTL: boolean = i18n.isRTL((new intl.Locale()).language) public static setIsRTL(isRTL: boolean): void { CommonUtil.isRTL = isRTL } public static getIsRTL(): boolean { return CommonUtil.isRTL } } ``` ```ts // xxx.ets import { LengthMetrics } from '@kit.ArkUI' import { CommonUtil } from '../common/CommonUtil' @Entry @Component struct TabsExample { @State colorArray: [string, string][] = [['green', '#00CB87'], ['blue', '#007DFF'], ['yellow', '#FFBF00'], ['pink', '#E67C92']] @State currentIndex: number = 0 @State animationDuration: number = 300 @State indicatorLeftMargin: number = 0 @State indicatorWidth: number = 0 private tabsWidth: number = 0 private textInfos: [number, number][] = [] private isStartAnimateTo: boolean = false aboutToAppear():void { for (let i = 0; i < this.colorArray.length; i++) { this.textInfos.push([0, 0]); } } @Builder tabBuilder(index: number, name: string) { Column() { Text(name) .fontSize(16) .fontColor(this.currentIndex === index ? '#007DFF' : '#182431') .fontWeight(this.currentIndex === index ? 500 : 400) .id(index.toString()) .onAreaChange((oldValue: Area, newValue: Area) => { this.textInfos[index] = [newValue.globalPosition.x as number, newValue.width as number] if (!this.isStartAnimateTo && this.currentIndex === index && this.tabsWidth > 0) { this.setIndicatorAttr(this.textInfos[this.currentIndex][0], this.textInfos[this.currentIndex][1]) } }) }.width('100%') } build() { Stack({ alignContent: Alignment.TopStart }) { Tabs({ barPosition: BarPosition.Start }) { ForEach(this.colorArray, (item: [string, string], index:number) => { TabContent() { Column().width('100%').height('100%').backgroundColor(item[1]) }.tabBar(this.tabBuilder(index, item[0])) }) } .onAreaChange((oldValue: Area, newValue: Area)=> { this.tabsWidth = newValue.width as number if (!this.isStartAnimateTo) { this.setIndicatorAttr(this.textInfos[this.currentIndex][0], this.textInfos[this.currentIndex][1]) } }) .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 this.startAnimateTo(this.animationDuration, this.textInfos[targetIndex][0], this.textInfos[targetIndex][1]) }) .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.setIndicatorAttr(currentIndicatorInfo.left, currentIndicatorInfo.width) }) Column() .height(2) .width(this.indicatorWidth) .margin({ start: LengthMetrics.vp(this.indicatorLeftMargin), top: LengthMetrics.vp(48) }) .backgroundColor('#007DFF') }.width('100%') } private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record { let nextIndex = index if (index > 0 && (CommonUtil.getIsRTL() ? event.currentOffset < 0 : event.currentOffset > 0)) { nextIndex-- } else if (index < this.textInfos.length - 1 && (CommonUtil.getIsRTL() ? event.currentOffset > 0 : event.currentOffset < 0)) { nextIndex++ } let indexInfo = this.textInfos[index] let nextIndexInfo = this.textInfos[nextIndex] let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth) let currentIndex = swipeRatio > 0.5 ? nextIndex : index // 页面滑动超过一半,tabBar切换到下一页。 let currentLeft = indexInfo[0] + (nextIndexInfo[0] - indexInfo[0]) * swipeRatio let currentWidth = indexInfo[1] + (nextIndexInfo[1] - indexInfo[1]) * swipeRatio return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth } } private startAnimateTo(duration: number, leftMargin: number, width: number) { this.isStartAnimateTo = true animateTo({ duration: duration, // 动画时长 curve: Curve.Linear, // 动画曲线 iterations: 1, // 播放次数 playMode: PlayMode.Normal, // 动画模式 onFinish: () => { this.isStartAnimateTo = false console.info('play end') } }, () => { this.setIndicatorAttr(leftMargin, width) }) } private setIndicatorAttr(leftMargin: number, width: number) { this.indicatorWidth = width if (CommonUtil.getIsRTL()) { this.indicatorLeftMargin = this.tabsWidth - leftMargin - width } else { this.indicatorLeftMargin = leftMargin } } } ``` ![tabs10](figures/tabs10.gif) ### 示例10(预加载子节点) 本示例通过preloadItems接口实现了预加载指定子节点。 ```ts // xxx.ets import { BusinessError } from '@kit.BasicServicesKit' @Entry @Component struct TabsPreloadItems { @State currentIndex: number = 1 private tabsController: TabsController = new TabsController() build() { Column() { Tabs({ index: this.currentIndex, controller: this.tabsController }) { TabContent() { MyComponent({ color: '#00CB87' }) }.tabBar(SubTabBarStyle.of('green')) TabContent() { MyComponent({ color: '#007DFF' }) }.tabBar(SubTabBarStyle.of('blue')) TabContent() { MyComponent({ color: '#FFBF00' }) }.tabBar(SubTabBarStyle.of('yellow')) TabContent() { MyComponent({ color: '#E67C92' }) }.tabBar(SubTabBarStyle.of('pink')) } .width(360) .height(296) .backgroundColor('#F1F3F5') .onChange((index: number) => { this.currentIndex = index }) Button('preload items: [0, 2, 3]') .margin(5) .onClick(() => { // 预加载第0、2、3个子节点,提高滑动或点击切换至这些节点时的性能 this.tabsController.preloadItems([0, 2, 3]) .then(() => { console.info('preloadItems success.') }) .catch((error: BusinessError) => { console.error('preloadItems failed, error code: ' + error.code + ', error message: ' + error.message) }) }) } } } @Component struct MyComponent { private color: string = "" aboutToAppear(): void { console.info('aboutToAppear backgroundColor:' + this.color) } aboutToDisappear(): void { console.info('aboutToDisappear backgroundColor:' + this.color) } build() { Column() .width('100%') .height('100%') .backgroundColor(this.color) } } ``` ### 示例11(设置TabBar平移距离和不透明度) 本示例通过setTabBarTranslate、setTabBarOpacity等接口设置了TabBar的平移距离和不透明度。 ```ts // xxx.ets @Entry @Component struct TabsExample { private controller: TabsController = new TabsController() build() { Column() { Button('设置TabBar的平移距离').margin({ top: 20 }) .onClick(() => { this.controller.setTabBarTranslate({ x: -20, y: -20 }) }) Button('设置TabBar的透明度').margin({ top: 20 }) .onClick(() => { this.controller.setTabBarOpacity(0.5) }) Tabs({ barPosition: BarPosition.End, controller: this.controller }) { TabContent() { Column().width('100%').height('100%').backgroundColor('#00CB87') }.tabBar(BottomTabBarStyle.of($r('app.media.startIcon'), 'green')) TabContent() { Column().width('100%').height('100%').backgroundColor('#007DFF') }.tabBar(BottomTabBarStyle.of($r('app.media.startIcon'), 'blue')) TabContent() { Column().width('100%').height('100%').backgroundColor('#FFBF00') }.tabBar(BottomTabBarStyle.of($r('app.media.startIcon'), 'yellow')) TabContent() { Column().width('100%').height('100%').backgroundColor('#E67C92') }.tabBar(BottomTabBarStyle.of($r('app.media.startIcon'), 'pink')) } .width(360) .height(296) .margin({ top: 20 }) .barBackgroundColor('#F1F3F5') } .width('100%') } } ``` ![tabs11](figures/tabs11.gif) ### 示例12(页面懒加载和释放) 本示例通过使用自定义TabBar与Swiper配合LazyForEach实现页面懒加载和释放。 ```ts // xxx.ets class MyDataSource implements IDataSource { private list: number[] = [] constructor(list: number[]) { this.list = list } totalCount(): number { return this.list.length } getData(index: number): number { return this.list[index] } registerDataChangeListener(listener: DataChangeListener): void { } unregisterDataChangeListener() { } } @Entry @Component struct TabsSwiperExample { @State fontColor: string = '#182431' @State selectedFontColor: string = '#007DFF' @State currentIndex: number = 0 private list: number[] = [] private tabsController: TabsController = new TabsController() private swiperController: SwiperController = new SwiperController() private swiperData: MyDataSource = new MyDataSource([]) aboutToAppear(): void { for (let i = 0; i <= 9; i++) { this.list.push(i); } this.swiperData = new MyDataSource(this.list) } @Builder tabBuilder(index: number, name: string) { Column() { Text(name) .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor) .fontSize(16) .fontWeight(this.currentIndex === index ? 500 : 400) .lineHeight(22) .margin({ top: 17, bottom: 7 }) Divider() .strokeWidth(2) .color('#007DFF') .opacity(this.currentIndex === index ? 1 : 0) }.width('20%') } build() { Column() { Tabs({ barPosition: BarPosition.Start, controller: this.tabsController }) { ForEach(this.list, (item: number) => { TabContent().tabBar(this.tabBuilder(item, '页签 ' + this.list[item])) }) } .onTabBarClick((index: number) => { this.currentIndex = index this.swiperController.changeIndex(index, true) }) .barMode(BarMode.Scrollable) .backgroundColor('#F1F3F5') .height(56) .width('100%') Swiper(this.swiperController) { LazyForEach(this.swiperData, (item: string) => { Text(item.toString()) .onAppear(()=>{ console.info('onAppear ' + item.toString()) }) .onDisAppear(()=>{ console.info('onDisAppear ' + item.toString()) }) .width('100%') .height('100%') .backgroundColor(0xAFEEEE) .textAlign(TextAlign.Center) .fontSize(30) }, (item: string) => item) } .loop(false) .onChange((index: number) => { this.currentIndex = index }) .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => { this.currentIndex = targetIndex this.tabsController.changeIndex(targetIndex) }) } } } ``` ![tabs12](figures/tabs12.gif)