Tabs
通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。
说明:
该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
该组件从API Version 11开始默认支持安全区避让特性(默认值为:expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])),开发者可以重写该属性覆盖默认行为,API Version 11之前的版本需配合expandSafeArea属性实现安全区避让。
子组件
不支持自定义组件作为子组件, 仅可包含子组件TabContent, 以及渲染控制类型if/else和ForEach, 并且if/else和ForEach下也仅支持TabContent, 不支持自定义组件。
说明:
Tabs子组件的visibility属性设置为None,或者visibility属性设置为Hidden时,对应子组件不显示,但依然会在视窗内占位。
接口
Tabs(value?: {barPosition?: BarPosition, index?: number, controller?: TabsController})
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
barPosition | BarPosition | 否 | 设置Tabs的页签位置。 默认值:BarPosition.Start |
index | number | 否 | 设置当前显示页签的索引。 默认值:0 说明: 设置为小于0的值时按默认值显示。 可选值为[0, TabContent子节点数量-1]。 直接修改index跳页时,切换动效不生效。 使用TabController的changeIndex时,默认生效切换动效,可以设置animationDuration为0关闭动画。 从API version 10开始,该参数支持$$双向绑定变量。 |
controller | TabsController | 否 | 设置Tabs控制器。 |
BarPosition枚举说明
Tabs页签位置枚举。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
名称 | 描述 |
---|---|
Start | vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。 |
End | vertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。 |
属性
除支持通用属性外,还支持以下属性:
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.Fixed |
options10+ | ScrollableBarModeOptions | 否 | Scrollable模式下的TabBar的布局样式。 说明: 仅Scrollable模式下有效 |
barWidth
barWidth(value: Length)
设置TabBar的宽度值。设置为小于0或大于Tabs宽度值时,按默认值显示。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
value | Length8+ | 是 | TabBar的宽度值。 默认值: 未设置SubTabBarStyle和BottomTabBarStyle的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的高度值。设置为小于0或大于Tabs高度值时,按默认值显示。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
value | Length8+ | 是 | TabBar的高度值。 默认值: 未设置带样式的TabBar且vertical属性为false时,默认值为56vp。 未设置带样式的TabBar且vertical属性为true时,默认值为Tabs的高度。 设置SubTabbarStyle样式且vertical属性为false时,默认值为56vp。 设置SubTabbarStyle样式且vertical属性为true时,默认值为Tabs的高度。 设置BottomTabbarStyle样式且vertical属性为true时,默认值为Tabs的高度。 设置BottomTabbarStyle样式且vertical属性为false时,默认值为56vp, 从API Version 12开始,默认值变更为52vp。 |
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时,默认值为0ms,即点击TabBar页签和调用TabsController的changeIndex接口切换TabContent无动画。设置为小于0或undefined时,默认值为300ms。 API version 11及以后,不设置该属性或设置为异常值,且设置TabBar为BottomTabBarStyle样式时,默认值为0ms。设置TabBar为其他样式时,默认值为300ms。 |
animationMode12+
animationMode(mode: Optional<AnimationMode>)
设置点击TabBar页签时切换TabContent的动画形式。
原子化服务API: 从API version 12开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
mode | Optional<AnimationMode> | 是 | 点击TabBar页签时切换TabContent的动画形式。 默认值: 默认值是AnimationMode::CONTENT_FIRST,表示在点击TabBar切换TabContent时,先加载目标页内容,再开始切换动画。 |
divider10+
divider(value: DividerStyle | null)
设置区分TabBar和TabContent的分割线样式。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
value | DividerStyle | 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之上。 默认值:false |
barBackgroundColor10+
barBackgroundColor(value: ResourceColor)
设置TabBar的背景颜色。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
value | ResourceColor | 是 | TabBar的背景颜色。 默认值:透明 |
barBackgroundBlurStyle11+
barBackgroundBlurStyle(value: BlurStyle)
设置TabBar的背景模糊材质。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
value | BlurStyle | 是 | TabBar的背景模糊材质。 默认值:NONE |
barGridAlign10+
barGridAlign(value: BarGridColumnOptions)
以栅格化方式设置TabBar的可见区域。具体参见BarGridColumnOptions对象。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
value | BarGridColumnOptions | 是 | 以栅格化方式设置TabBar的可见区域。 |
DividerStyle10+对象说明
分割线样式对象。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
名称 | 参数类型 | 必填 | 描述 |
---|---|---|---|
strokeWidth | Length | 是 | 分割线的线宽(不支持百分比设置)。 默认值:0.0 单位:vp |
color | ResourceColor | 否 | 分割线的颜色。 默认值:#33182431 |
startMargin | Length | 否 | 分割线与侧边栏顶端的距离(不支持百分比设置)。 默认值:0.0 单位:vp |
endMargin | Length | 否 | 分割线与侧边栏底端的距离(不支持百分比设置)。 默认值:0.0 单位:vp |
BarGridColumnOptions10+对象说明
TabBar栅格化方式设置的对象,包括栅格模式下的column边距和间隔,以及小、中、大屏下,页签占用的columns数量。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
名称 | 参数类型 | 必填 | 描述 |
---|---|---|---|
margin | Dimension | 否 | 栅格模式下的column边距(不支持百分比设置)。 默认值:24.0 单位:vp |
gutter | Dimension | 否 | 栅格模式下的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 | 否 | Scrollable模式下的TabBar的左右边距(不支持百分比设置)。 默认值:0.0 单位:vp |
nonScrollableLayoutStyle | LayoutStyle | 否 | 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宽度。 仅水平模式下有效,否则视为LayoutStyle.ALWAYS_CENTER。 |
SPACE_BETWEEN_OR_CENTER | 2 | 当页签内容超过TabBar宽度时,TabBar可滚动。 当页签内容不超过TabBar宽度但超过TabBar宽度一半时,TabBar不可滚动,页签紧凑居中。 当页签内容不超过TabBar宽度一半时,TabBar不可滚动,保证页签居中排列在TabBar宽度一半,且间距相同。 |
事件
除支持通用事件外,还支持以下事件:
onChange
onChange(event: (index: number) => void)
Tab页签切换后触发的事件。
触发该事件的条件:
1、TabContent支持滑动时,组件触发滑动时触发。
2、通过控制器API接口调用。
3、通过状态变量构造的属性值进行修改。
4、通过页签处点击触发。
原子化服务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值)。
原子化服务API: 从API version 12开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
index | number | 是 | 当前显示元素的索引。 |
targetIndex | number | 是 | 切换动画目标元素的索引。 |
event | TabsAnimationEvent | 是 | 动画相关信息,包括主轴方向上当前显示元素和目标元素相对Tabs起始位置的位移,以及离手速度。 |
onAnimationEnd11+
onAnimationEnd(handler: (index: number, event: TabsAnimationEvent) => void)
切换动画结束时触发该回调。当Tabs切换动效结束时触发,包括动画过程中手势中断。参数为动画结束后的index值。
原子化服务API: 从API version 12开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
index | number | 是 | 当前显示元素的索引。 |
event | TabsAnimationEvent | 是 | 动画相关信息,只返回主轴方向上当前显示元素相对于Tabs起始位置的位移。 |
onGestureSwipe11+
onGestureSwipe(handler: (index: number, event: TabsAnimationEvent) => void)
在页面跟手滑动过程中,逐帧触发该回调。
原子化服务API: 从API version 12开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
index | number | 是 | 当前显示元素的索引。 |
event | TabsAnimationEvent | 是 | 动画相关信息,只返回主轴方向上当前显示元素相对于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 | 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无法切换到新页面,仍然显示原来页面内容。 |
TabsController
Tabs组件的控制器,用于控制Tabs组件进行页签切换。不支持一个TabsController控制多个Tabs组件。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
导入对象
let controller: TabsController = new TabsController()
constructor
constructor()
TabsController的构造函数。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
changeIndex
changeIndex(value: number)
控制Tabs切换到指定页签。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
value | number | 是 | 页签在Tabs里的索引值,索引值从0开始。 说明: 设置小于0或大于最大数量的值时,取默认值0。 |
示例
示例1
本示例通过onChange实现切换时自定义tabBar和TabContent的联动。
// xxx.ets
@Entry
@Component
struct TabsExample {
@State fontColor: string = '#182431'
@State selectedFontColor: string = '#007DFF'
@State currentIndex: number = 0
private controller: TabsController = new TabsController()
@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('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) => {
this.currentIndex = index
})
.width(360)
.height(296)
.margin({ top: 52 })
.backgroundColor('#F1F3F5')
}.width('100%')
}
}
示例2
本示例通过divider实现了分割线各种属性的展示。
// 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' })
}
}
示例3
本示例通过fadingEdge实现了切换子页签渐隐和不渐隐。
// 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' })
}
}
示例4
本示例通过barOverlap实现了TabBar是否背后变模糊并叠加在TabContent之上。
// xxx.ets
@Entry
@Component
struct barBackgroundColorTest {
private controller: TabsController = new TabsController()
@State barOverlap: boolean = true;
@State barBackgroundColor: string = '#88888888';
build() {
Column() {
Button("barOverlap变化").width('100%').margin({ bottom: '12vp' })
.onClick((event?: ClickEvent) => {
if (this.barOverlap) {
this.barOverlap = false;
} else {
this.barOverlap = true;
}
})
Tabs({ barPosition: BarPosition.Start, index: 0, controller: this.controller }) {
TabContent() {
Column() {
Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 })
Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16)
}.width('100%').width('100%').height('100%')
.backgroundColor(Color.Pink)
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "1"))
TabContent() {
Column() {
Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 })
Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16)
}.width('100%').width('100%').height('100%')
.backgroundColor(Color.Yellow)
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "2"))
TabContent() {
Column() {
Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 })
Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16)
}.width('100%').width('100%').height('100%')
.backgroundColor(Color.Green)
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "3"))
}
.vertical(false)
.barMode(BarMode.Fixed)
.height('60%')
.barOverlap(this.barOverlap)
.scrollable(true)
.animationDuration(10)
.barBackgroundColor(this.barBackgroundColor)
}
.height(500)
.padding({ top: '24vp', left: '24vp', right: '24vp' })
}
}
ts
示例5
本示例通过barGridAlign实现了以栅格化方式设置TabBar的可见区域。
// 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')
}
}
ts
示例6
本示例实现了barMode的ScrollableBarModeOptions参数,该参数仅在Scrollable模式下有效。
// 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')
}
}
示例7
本示例通过customContentTransition实现了自定义Tabs页面的切换动画。
// 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)
}
}
}
示例8
本示例通过onContentWillChange实现了自定义页面手势滑动切换拦截。
//xxx.ets
@Entry
@Component
struct TabsExample {
@State currentIndex: number = 2
private controller: TabsController = new TabsController()
@Builder tabBuilder(title: string,targetIndex: number) {
Column(){
Text(title).fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
}.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
}
build() {
Column() {
Tabs({ barPosition: BarPosition.End, controller: this.controller, index: this.currentIndex }) {
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
})
.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%')
}
}
示例9
本示例通过onChange、onAnimationStart、onAnimationEnd、onGestureSwipe等接口实现了自定义TabBar的切换动画。
// xxx.ets
import ComponentUtils from '@ohos.arkui.UIContext';
@Entry
@Component
struct TabsExample {
@State currentIndex: number = 0
@State animationDuration: number = 300
@State indicatorLeftMargin: number = 0
@State indicatorWidth: number = 0
private tabsWidth: number = 0
private componentUtils: ComponentUtils.ComponentUtils = this.getUIContext().getComponentUtils()
@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) => {
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
}
})
}.width('100%')
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
Tabs({ barPosition: BarPosition.Start }) {
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'))
}
.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
})
Column()
.height(2)
.width(this.indicatorWidth)
.margin({ left: this.indicatorLeftMargin, top:48})
.backgroundColor('#007DFF')
}.width('100%')
}
private getTextInfo(index: number): Record<string, number> {
let rectangle = this.componentUtils.getRectangleById(index.toString())
return { 'left': px2vp(rectangle.windowOffset.x), 'width': px2vp(rectangle.size.width) }
}
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
})
}
}