鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Tabs)

通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。

说明:

该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。

该组件从API Version 11开始默认支持安全区避让特性(默认值为:expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])),开发者可以重写该属性覆盖默认行为,API Version 11之前的版本需配合expandSafeArea属性实现安全区避让。

子组件

仅可包含子组件TabContent。

说明:

Tabs子组件的visibility属性设置为None,或者visibility属性设置为Hidden时,对应子组件不显示,但依然会在视窗内占位。

接口

Tabs(value?: {barPosition?: BarPosition, index?: number, controller?: TabsController})

参数:

参数名参数类型必填参数描述
barPositionBarPosition设置Tabs的页签位置。
默认值:BarPosition.Start
indexnumber设置当前显示页签的索引。
默认值:0
说明:
设置为小于0的值时按默认值显示。
可选值为[0, TabContent子节点数量-1]。
直接修改index跳页时,切换动效不生效。 使用TabController的changeindex时,默认生效切换动效,可以设置animationDuration为0关闭动画。
从API version 10开始,该参数支持$$双向绑定变量。
controllerTabsController设置Tabs控制器。

BarPosition枚举说明

名称描述
Startvertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。
Endvertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。

属性

除支持通用属性外,还支持以下属性:

名称参数类型描述
verticalboolean设置为false是为横向Tabs,设置为true时为纵向Tabs。
默认值:false
scrollableboolean设置为true时可以通过滑动页面进行页面切换,为false时不可滑动切换页面。
默认值:true
barModeBarMode,ScrollableBarModeOptionsTabBar布局模式,BarMode为必选项,ScrollableBarModeOptions为可选项,具体描述见BarMode枚举说明、ScrollableBarModeOptions对象说明。从API version 10开始,支持ScrollableBarModeOptions参数。其中ScrollableBarModeOptions参数仅Scrollable模式下有效,非必填参数。
默认值:BarMode.Fixed
barWidthnumber | 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的宽度。
说明:
设置为小于0或大于Tabs宽度值时,按默认值显示。
barHeightnumber | 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。
说明:
设置为小于0或大于Tabs高度值时,按默认值显示。
animationDurationnumber点击TabBar页签切换TabContent的动画时长。
默认值:
API version 10及以前,不设置该属性时,默认值为0ms,即点击TabBar页签切换TabContent无动画。设置为小于0的异常值时,默认值为300ms。
API version 11及以后,不设置该属性或设置为小于0的异常值,且设置TabBar为BottomTabBarStyle样式时,默认值为0ms。设置TabBar为其他样式时,默认值为300ms。
说明:
该参数不支持百分比设置。
divider10+DividerStyle | null用于设置区分TabBar和TabContent的分割线样式设置分割线样式,默认不显示分割线。
DividerStyle: 分割线的样式;
null: 不显示分割线。
fadingEdge10+boolean设置页签超过容器宽度时是否渐隐消失。
默认值:true
说明:
建议配合barBackgroundColor属性一起使用,如果barBackgroundColor属性没有定义,会默认显示页签末端为白色的渐隐效果。
barOverlap10+boolean设置TabBar是否背后变模糊并叠加在TabContent之上。
默认值:false
barBackgroundColor10+ResourceColor设置TabBar的背景颜色。
默认值:透明
barGridAlign10+BarGridColumnOptions以栅格化方式设置TabBar的可见区域。具体参见BarGridColumnOptions对象。仅水平模式下有效,不适用于XS、XL和XXL设备。

DividerStyle10+对象说明

名称参数类型必填描述
strokeWidthLength分割线的线宽(不支持百分比设置)。
colorResourceColor分割线的颜色。
默认值:#33182431
startMarginLength分割线与侧边栏顶端的距离(不支持百分比设置)。
默认值:0.0
单位:vp
endMarginLength分割线与侧边栏底端的距离(不支持百分比设置)。
默认值:0.0
单位:vp

BarGridColumnOptions10+对象说明

名称参数类型必填描述
marginDimension网格模式下的column边距(不支持百分比设置)。
默认值:24.0
单位:vp
gutterDimension网格模式下的column间隔(不支持百分比设置)。
默认值:24.0
单位:vp
smnumber小屏下,页签占用的columns数量,必须是非负偶数。小屏为大于等于320vp但小于600vp。
默认值为-1,代表页签占用TabBar全部宽度。
mdnumber中屏下,页签占用的columns数量,必须是非负偶数。中屏为大于等于600vp但小于800vp。
默认值为-1,代表页签占用TabBar全部宽度。
lgnumber大屏下,页签占用的columns数量,必须是非负偶数。大屏为大于等于840vp但小于1024vp。
默认值为-1,代表页签占用TabBar全部宽度。

ScrollableBarModeOptions10+对象说明

名称参数类型必填描述
marginDimensionScrollable模式下的TabBar的左右边距(不支持百分比设置)。
默认值:0.0
单位:vp
nonScrollableLayoutStyleLayoutStyleScrollable模式下不滚动时的页签排布方式。
默认值:LayoutStyle.ALWAYS_CENTER

BarMode枚举说明

名称描述
Scrollable每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。
Fixed所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度)。

LayoutStyle10+枚举说明

名称描述
ALWAYS_CENTER当页签内容超过TabBar宽度时,TabBar可滚动。
当页签内容不超过TabBar宽度时,TabBar不可滚动,页签紧凑居中。
ALWAYS_AVERAGE_SPLITE当页签内容超过TabBar宽度时,TabBar可滚动。
当页签内容不超过TabBar宽度时,TabBar不可滚动,且所有页签平均分配TabBar宽度。
仅水平模式下有效,否则视为LayoutStyle.ALWAYS_CENTER。
SPACE_BETWEEN_OR_CENTER当页签内容超过TabBar宽度时,TabBar可滚动。
当页签内容不超过TabBar宽度但超过TabBar宽度一半时,TabBar不可滚动,页签紧凑居中。
当页签内容不超过TabBar宽度一半时,TabBar不可滚动,保证页签居中排列在TabBar宽度一半,且间距相同。

事件

除支持通用事件外,还支持以下事件:

名称功能描述
onChange(event: (index: number) => void)Tab页签切换后触发的事件。
- index:当前显示的index索引,索引从0开始计算。
触发该事件的条件:
1、TabContent支持滑动时,组件触发滑动时触发。
2、通过控制器API接口调用。
3、通过状态变量构造的属性值进行修改。
4、通过页签处点击触发。
onTabBarClick(event: (index: number) => void)10+Tab页签点击后触发的事件。
- index:被点击的index索引,索引从0开始计算。
触发该事件的条件:
通过页签处点击触发。
onAnimationStart11+(handler: (index: number, targetIndex: number, event: TabsAnimationEvent) => void)切换动画开始时触发该回调。
- index:当前显示元素的索引。
- targetIndex:切换动画目标元素的索引。
- event:动画相关信息,包括主轴方向上当前显示元素和目标元素相对Tabs起始位置的位移,以及离手速度。
说明:
参数为动画开始前的index值(不是最终结束动画的index值)。
onAnimationEnd11+(handler: (index: number, event: TabsAnimationEvent) => void)切换动画结束时触发该回调。
- index:当前显示元素的索引。
- event:动画相关信息,只返回主轴方向上当前显示元素相对于Tabs起始位置的位移。
说明:
当Tabs切换动效结束时触发,包括动画过程中手势中断。参数为动画结束后的index值。
onGestureSwipe11+(handler: (index: number, event: TabsAnimationEvent) => void)在页面跟手滑动过程中,逐帧触发该回调。
- index:当前显示元素的索引。
- event:动画相关信息,只返回主轴方向上当前显示元素相对于Tabs起始位置的位移。
customContentTransition11+(delegate: (from: number, to: number) => TabContentAnimatedTransition | undefined)自定义Tabs页面切换动画。其中,from和to参数为返回给开发者使用的值,代表的含义如下:
- from:动画开始时,当前页面的index值。
- to:动画开始时,目标页面的index值。
使用说明:
1、当使用自定义切换动画时,Tabs组件自带的默认切换动画会被禁用,同时,页面也无法跟手滑动。
2、当设置为undefined时,表示不使用自定义切换动画,仍然使用组件自带的默认切换动画。
3、当前自定义切换动画不支持打断。
4、目前自定义切换动画只支持两种场景触发:点击页签和调用TabsController.changeIndex()接口。
5、当使用自定义切换动画时,Tabs组件支持的事件中,除了onGestureSwipe,其他事件均支持。
6、onChange和onAnimationEnd事件的触发时机需要特殊说明:如果在第一次自定义动画执行过程中,触发了第二次自定义动画,那么在开始第二次自定义动画时,就会触发第一次自定义动画的onChange和onAnimationEnd事件。
7、当使用自定义动画时,参与动画的页面布局方式会改为Stack布局。如果开发者未主动设置相关页面的zIndex属性,那么所有页面的zIndex值是一样的,页面的渲染层级会按照在组件树上的顺序(即页面的index值顺序)确定。因此,开发者需要主动修改页面的zIndex属性,来控制页面的渲染层级。

TabsController

Tabs组件的控制器,用于控制Tabs组件进行页签切换。不支持一个TabsController控制多个Tabs组件。

导入对象

let controller: TabsController = new TabsController()

changeIndex

changeIndex(value: number): void

控制Tabs切换到指定页签。

参数:

参数名参数类型必填参数描述
valuenumber页签在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 = 0private 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%')}
}

tabs2

示例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 = falsebuild() {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

示例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' })}
}

tabs4

示例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' })}
}

tabs5

示例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')}
}

tabs5

示例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')}
}

tabs5

示例7

本示例通过customContentTransition实现了自定义Tabs页面的切换动画。

// xxx.ets
@Entry
@Component
struct TabsCustomAnimationExample {@State useCustomAnimation: boolean = true@State tabContent0Scale: number = 1.0@State tabContent1Scale: number = 1.0@State tabContent0Opacity: number = 1.0@State tabContent1Opacity: number = 1.0@State tabContent2Opacity: number = 1.0tabsController: TabsController = new TabsController()private firstTimeout: number = 3000private secondTimeout: number = 5000private first2secondDuration: number = 3000private second2thirdDuration: number = 5000private first2thirdDuration: number = 2000private baseCustomAnimation: (from: number, to: number) => TabContentAnimatedTransition = (from: number, to: number) => {if ((from === 0 && to === 1) || (from === 1 && to === 0)) {// 缩放动画let firstCustomTransition = {timeout: this.firstTimeout,transition: (proxy: TabContentTransitionProxy) => {if (proxy.from === 0 && proxy.to === 1) {this.tabContent0Scale = 1.0this.tabContent1Scale = 0.5} else {this.tabContent0Scale = 0.5this.tabContent1Scale = 1.0}animateTo({duration: this.first2secondDuration,onFinish: () => {proxy.finishTransition()}}, () => {if (proxy.from === 0 && proxy.to === 1) {this.tabContent0Scale = 0.5this.tabContent1Scale = 1.0} else {this.tabContent0Scale = 1.0this.tabContent1Scale = 0.5}})}} as TabContentAnimatedTransition;return firstCustomTransition;} else {// 透明度动画let secondCustomTransition = {timeout: this.secondTimeout,transition: (proxy: TabContentTransitionProxy) => {if ((proxy.from === 1 && proxy.to === 2) || (proxy.from === 2 && proxy.to === 1)) {if (proxy.from === 1 && proxy.to === 2) {this.tabContent1Opacity = 1.0this.tabContent2Opacity = 0.5} else {this.tabContent1Opacity = 0.5this.tabContent2Opacity = 1.0}animateTo({duration: this.second2thirdDuration,onFinish: () => {proxy.finishTransition()}}, () => {if (proxy.from === 1 && proxy.to === 2) {this.tabContent1Opacity = 0.5this.tabContent2Opacity = 1.0} else {this.tabContent1Opacity = 1.0this.tabContent2Opacity = 0.5}})} else if ((proxy.from === 0 && proxy.to === 2) || (proxy.from === 2 && proxy.to === 0)) {if (proxy.from === 0 && proxy.to === 2) {this.tabContent0Opacity = 1.0this.tabContent2Opacity = 0.5} else {this.tabContent0Opacity = 0.5this.tabContent2Opacity = 1.0}animateTo({duration: this.first2thirdDuration,onFinish: () => {proxy.finishTransition()}}, () => {if (proxy.from === 0 && proxy.to === 2) {this.tabContent0Opacity = 0.5this.tabContent2Opacity = 1.0} else {this.tabContent0Opacity = 1.0this.tabContent2Opacity = 0.5}})}}} as TabContentAnimatedTransition;return secondCustomTransition;}}build() {Column() {Tabs({ controller: this.tabsController }) {TabContent() {Text("Red")}.tabBar("Red").scale({ x: this.tabContent0Scale, y: this.tabContent0Scale }).backgroundColor(Color.Red).opacity(this.tabContent0Opacity).width(100).height(100)TabContent() {Text("Yellow")}.tabBar("Yellow").scale({ x: this.tabContent1Scale, y: this.tabContent1Scale }).backgroundColor(Color.Yellow).opacity(this.tabContent1Opacity).width(200).height(200)TabContent() {Text("Blue")}.tabBar("Blue").backgroundColor(Color.Blue).opacity(this.tabContent2Opacity).width(300).height(300)}.backgroundColor(0xf1f3f5).width('100%').height(500).margin({ top: 5 }).customContentTransition(this.useCustomAnimation ? this.baseCustomAnimation : undefined).barMode(BarMode.Scrollable).onChange((index: number) => {console.info("onChange index: " + index)}).onTabBarClick((index: number) => {console.info("onTabBarClick index: " + index)})}}
}

tabs5

示例8

本示例通过onChange、onAnimationStart、onAnimationEnd、onGestureSwipe等接口实现了自定义TabBar的切换动画。

// xxx.ets
@Entry
@Component
struct TabsExample {@State currentIndex: number = 0@State animationDuration: number = 300@State indicatorLeftMargin: number = 0@State indicatorWidth: number = 0private tabsWidth: number = 0@BuildertabBuilder(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 = targetIndexlet 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.indexthis.indicatorLeftMargin = currentIndicatorInfo.leftthis.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 strJson = getInspectorByKey(index.toString())try {let obj: Record<string, string> = JSON.parse(strJson)let rectInfo: number[][] = JSON.parse('[' + obj.$rect + ']')return { 'left': px2vp(rectInfo[0][0]), 'width': px2vp(rectInfo[1][0] - rectInfo[0][0]) }} catch (error) {return { 'left': 0, 'width': 0 }}}private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {let nextIndex = indexif (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) * swipeRatiolet currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * swipeRatioreturn { '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 = leftMarginthis.indicatorWidth = width})}
}

最后,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/751027.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【老旧小区用电安全谁能管?】安科瑞智慧用电安全管理系统解决方案

行业背景 电气火灾指由电气故障引发的火灾。每年以30%的比例高居各类火灾原因之首。以50%到80%的比例高居重特大火灾之首。已成为业界重点关注的对象并为此进行着孜孜不倦的努力。 国务院安委会也于2017年5月至2020年4月年开展了为期3年的电气火灾综合治理工作。在各界努力的…

HJ212协议C#代码解析实现

HJ212协议C#代码解析实现 HJ212协议是环保中一个非常重要的标准协议&#xff08;字符串协议&#xff09;&#xff0c;之前写了两篇C HJ212协议解析的相关博文&#xff1a; 环保 HJ212协议解析基于Qt5.14.2的HJ212 TCP服务端接收解析入库程序 最近在学习C#&#xff0c;所以打算…

接口幂等性问题和常见解决方案

接口幂等性问题和常见解决方案 1.什么是接口幂等性问题1.1 会产生接口幂等性的问题1.2 解决思路 2.接口幂等性的解决方案2.1 唯一索引解决方案2.2 乐观锁解决方案2.3 分布式锁解决方案2.4 Token解决方案(最优方案) 3 Token解决方案落地3.1 token获取、token校验3.2 自定义注解,…

小蓝的漆房——算法思路

题目链接&#xff1a;1.小蓝的漆房 - 蓝桥云课 (lanqiao.cn) 本题只要是通过枚举的方法&#xff0c;算出涂成每一种颜色所需的天数&#xff0c;最后在所有天数中找出最小值&#xff08;由题可知&#xff0c;最多只有60种颜色&#xff0c;所以可以尝试算出每种颜色所需的时间&am…

LeetCode刷题小记 八、【回溯算法】

1.回溯算法 文章目录 1.回溯算法写在前面1.1回溯算法基本知识1.2组合问题1.3组合问题的剪枝操作1.4组合总和III1.5电话号码的字母组合1.6组合总和1.7组合总和II1.8分割回文串1.9复原IP地址1.10子集问题1.11子集II1.12非递减子序列1.13全排列1.14全排列II1.15N皇后1.16解数独 写…

react中hooks使用限制

只能在最顶层使用Hook 不要在循环、条件中调用hook&#xff0c;确保总是在React函数最顶层使用它们 只能React函数中调用Hook 不要在普通的js函数中调用 在React的函数组件中调用Hook 在自定义hook中调用其他hook 原因&#xff1a; 我们每次的状态值或者依赖项存在哪里&…

springCloudeAlibaba的使用

父pom文件&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.o…

数据库中逻辑运算符的介绍以及优先级表

简介&#xff1a;逻辑运算符主要判断表达式的真假&#xff0c;返回值为1&#xff0c;0&#xff0c;null 其中包含&#xff1a; 逻辑非&#xff1a;not或&#xff01; 逻辑与&#xff1a;and或&& 逻辑或&#xff1a;or或|| 逻辑异或&#xff1a;XOR 1.逻辑非运算 规则…

mac打开exe文件的三大方法 mac怎么运行exe文件 mac打开exe游戏 macbookpro打开exe

exe文件是Windows系统的可执行文件&#xff0c;虽然Mac系统上无法直接打开exe文件&#xff0c;但是你可以在Mac电脑上安装双系统或者虚拟机来实现mac电脑上运行exe文件。除了这两种方法之外&#xff0c;你还可以在Mac电脑上使用类虚拟机软件打开exe文件&#xff0c;这三种方法各…

双指针 | 移动零 | 复写零

1.移动零 题目描述&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 示例&#xff1a; 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0]解题思路&#xff1a; right指针一直往后移动&#xff0c;当…

欧洲跨境物流仓库视频监控系统开发的解决方案

中国联通针对仓储物流行业的数字化需求&#xff0c;提供全面的解决方案&#xff0c;特别是在海外仓储领域&#xff0c;我们深谙企业面临的挑战&#xff0c;如预算限制、仓储空间需求以及城郊网络覆盖不足等问题。我们利用自身强大的网络资源和技术实力&#xff0c;为出海仓储企…

redis学习-Hash类型相关命令及特殊情况分析

目录 1. hset KEY key1 value1 key2 value2 ... 2. hget KEY key 3. hgetall KEY 4. hmget KEY key1 key2 ... 5. hkeys KEY 6. hvals KEY 7. hdel KEY key1 key2 ... 8. hlen KEY 9. hexists KEY key 10. hincrby KEY key num 11. hsetnx KEY key value Hash的内部…

实时数仓项目《一》-实时数仓架构

目录 1. 实时数仓与离线数仓 2. 实时数仓需求 3. 架构图 3.1 行为日志处理&#xff1a; 3.2 业务库表处理&#xff1a; 4. 面试题&#xff1a; &#x1f9c0;你们的实时数仓有分层吗&#xff1f; &#x1f9c0;那你们的业务数据呢&#xff1f; &#x1f9c0;那你们的O…

字符分类函数(iscntrl、i是space.....)---c语言

目录 一、定义二、字符分类函数2.1 -iscntrl&#xff08;&#xff09;2.1.1定义2.1.2使用举例 2.2 -isspace&#xff08;&#xff09;2.2.1描述2.2.2使用举例 2.3-isdigit()2.3.1描述2.3.2使用举例 2.4-isxdigit()2.4.1描述 2.5-islower()2.5.1描述2.5.2使用举例 2.6-isupper()…

THM学习笔记—Simple CTF

nmap扫描&#xff0c;发现2222端口很奇怪啊&#xff0c;重新换一种方式扫描2222端口 发现是ssh 先用ftp试试&#xff0c;尝试匿名登录 下载所有文件 发现只有一个ForMitch.txt&#xff0c;告诉我们其账号密码为弱密码&#xff0c;我们猜测Mitch为其用户名&#xff0c;尝试暴力…

MechanicalSoup,一个非常实用的 Python 自动化浏览器交互工具库!

目录 前言 什么是 Python MechanicalSoup 库&#xff1f; 核心功能 使用方法 1. 安装 MechanicalSoup 库 2. 创建 MechanicalSoup 客户端 3. 打开网页并与之交互 实际应用场景 1. 网页自动化测试 2. 网络爬虫与数据提取 3. 网页自动化操作 4. 自动化填写和提交多个表单 5.…

V-JEPA模型,非LLM另外的选择,AGI的未来:迈向Yann LeCun先进机器智能(AMI)愿景的下一步

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

EPICS和Arduino Uno之间基于串行文本协议的控制开发

Arduino Uno的串口服务程序设置如文本的串口通信协议设计以及在Arduino上的应用-CSDN博客中所示。通过在串口上发送约定的文本协议&#xff0c;它实现的功能如下&#xff1a; 实现功能&#xff1a; 读取三路0.0V~5.0V模拟量输入&#xff0c;读取端口A0~A2设置三路0.0V~5.0V的模…

typeof 与 instanceof 区别

文章目录 一、typeof二、instanceof三、区别 一、typeof typeof 操作符返回一个字符串&#xff0c;表示未经计算的操作数的类型 使用方法如下&#xff1a; typeof operand typeof(operand)operand表示对象或原始值的表达式&#xff0c;其类型将被返回 举个例子 typeof 1 /…

K8S之持久化存储

持久化存储 支持的持久化存储类型EmptyDirHostPathNFS 在K8S中部署的应用都是以pod容器的形式运行的&#xff0c;假如部署数据库服务 例如&#xff1a;MySQL、Redis等&#xff0c;需要对产生的数据做备份。如果pod不挂载数据卷&#xff0c;那pod被删除或重启后这些数据会随之消…