1# Tabs 2 3The **\<Tabs>** component is a container component that allows users to switch between content views through tabs. Each tab page corresponds to a content view. 4 5> **NOTE** 6> 7> This component is supported since API version 7. Updates will be marked with a superscript to indicate their earliest API version. 8> 9> Since API version 11, this component supports the safe area attribute by default, with the default attribute value being **expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]))**. You can override this attribute to change the default behavior. In earlier versions, you need to use the [expandSafeArea](ts-universal-attributes-expand-safe-area.md) attribute to implement the safe area feature. 10 11 12## Child Components 13 14Only the [\<TabContent>](ts-container-tabcontent.md) child component is supported. 15 16> **NOTE** 17> 18> If the child component has the **visibility** attribute set to **None** or **Hidden**, it is hidden but takes up space in the layout. 19 20 21## APIs 22 23Tabs(value?: {barPosition?: [BarPosition](#barposition), index?: number, controller?: [TabsController](#tabscontroller)}) 24 25**Parameters** 26 27| Name | Type | Mandatory | Description | 28| ----------- | --------------------------------- | ---- | ---------------------------------------- | 29| barPosition | [BarPosition](#barposition)| No | Position of the **\<Tabs>** component.<br>Default value: **BarPosition.Start** | 30| index | number | No | Index of the currently displayed tab.<br>Default value: **0**<br>**NOTE**<br>A value less than 0 evaluates to the default value.<br>The value ranges from 0 to the number of **\<TabContent>** subnodes minus 1.<br>When the tab is switched by changing the index, the tab switching animation does not take effect. When **changeindex** of **TabController** is used for tab switching, the tab switching animation is enabled by default. You can disable the animation by setting **animationDuration** to **0**.<br>Since API version 10, this parameter supports [$$](../../quick-start/arkts-two-way-sync.md) for two-way binding of variables.| 31| controller | [TabsController](#tabscontroller) | No | Tab controller. | 32 33## BarPosition 34 35| Name | Description | 36| ----- | ---------------------------------------- | 37| Start | If the **vertical** attribute is set to **true**, the tab is on the left of the container. If the **vertical** attribute is set to **false**, the tab is on the top of the container.| 38| End | If the **vertical** attribute is set to **true**, the tab is on the right of the container. If the **vertical** attribute is set to **false**, the tab is at the bottom of the container.| 39 40 41## Attributes 42 43In addition to the [universal attributes](ts-universal-attributes-size.md), the following attributes are supported. 44 45| Name | Type | Description | 46| -------------------------------- | ---------------------------------------- | ---------------------------------------- | 47| vertical | boolean | Whether to use vertical tabs. The value **true** means to use vertical tabs, and **false** means to use horizontal tabs.<br>Default value: **false**| 48| scrollable | boolean | Whether the tabs are scrollable. The value **true** means that the tabs are scrollable, and **false** means the opposite.<br>Default value: **true**| 49| barMode | [BarMode](#barmode),[ScrollableBarModeOptions](#scrollablebarmodeoptions10) | Tab bar layout mode. **BarMode** is mandatory, and **ScrollableBarModeOptions** is optional. For details, see **BarMode** and **ScrollableBarModeOptions**. Since API version 10, the optional **ScrollableBarModeOptions** parameter is supported. It is effective only when the tab bar is in scrollable mode.<br>Default value: **BarMode.Fixed**| 50| barWidth | number \| Length<sup>8+</sup> | Width of the tab bar.<br>The default value varies.<br>If the tab bar has the **vertical** attribute set to **false** and does not have [SubTabBarStyle](ts-container-tabcontent.md#subtabbarstyle9) or [BottomTabBarStyle](ts-container-tabcontent.md#bottomtabbarstyle9) specified, the default value is the width of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **true** and does not have [SubTabBarStyle](ts-container-tabcontent.md#subtabbarstyle9) or [BottomTabBarStyle](ts-container-tabcontent.md#bottomtabbarstyle9) specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **false** and **SubTabbarStyle** specified, the default value is the width of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **true** and **SubTabbarStyle** specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **true** and **BottomTabbarStyle** specified, the default value is **96vp**.<br>If the tab bar has the **vertical** attribute set to **false** and **BottomTabbarStyle** specified, the default value is the width of the **\<Tabs>** component.<br>**NOTE**<br><br>A value less than 0 or greater than the width of the **\<Tabs>** component evaluates to the default value.| 51| barHeight | number \| Length<sup>8+</sup> | Height of the tab bar.<br>The default value varies.<br>If the tab bar has the **vertical** attribute set to **false** and does not have a style specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **true** and does not have a style specified, the default value is the height of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **false** and **SubTabbarStyle** specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **true** and **SubTabbarStyle** specified, the default value is the height of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **true** and **BottomTabbarStyle** specified, the default value is the height of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **false** and **BottomTabbarStyle** specified, the default value is **56vp**.<br>**NOTE**<br><br>A value less than 0 or greater than the height of the **\<Tabs>** component evaluates to the default value.| 52| animationDuration | number | Length of time required to complete the tab switching animation that is initiated by clicking a specific tab.<br>The default value varies.<br>API version 10 and earlier versions: If this parameter is not set, the default value 0 ms is used, which means that no tab switching animation is displayed when a specific tab is clicked. If this parameter is set to a value less than 0, the default value 300 ms is used.<br>API version 11 and later versions: If this parameter is set to a value less than 0 or is not set, and the tab bar is set to **BottomTabBarStyle**, the default value 0 ms is used. If the tab bar is set to other styles, the default value 300 ms is used.<br>**NOTE**<br><br>This parameter cannot be set in percentage.| 53| divider<sup>10+</sup> | [DividerStyle](#dividerstyle10) \| null | Whether the divider is displayed for the **\<TabBar>** and **\<TabContent>** components and the divider style. By default, the divider is not displayed.<br> **DividerStyle**: divider style.<br> **null**: The divider is not displayed.| 54| fadingEdge<sup>10+</sup> | boolean | Whether the tab fades out when it exceeds the container width.<br>Default value: **true**<br>**NOTE**<br>It is recommended that this attribute be used together with the **barBackgroundColor** attribute. If the **barBackgroundColor** attribute is not defined, the tab fades out in white when it exceeds the container width by default.| 55| barOverlap<sup>10+</sup> | boolean | Whether the tab bar is superimposed on the **\<TabContent>** component after having its background blurred.<br>Default value: **false**| 56| barBackgroundColor<sup>10+</sup> | [ResourceColor](ts-types.md#resourcecolor) | Background color of the tab bar.<br>Default value: transparent | 57| barGridAlign<sup>10+</sup> | [BarGridColumnOptions](#bargridcolumnoptions10) | Visible area of the tab bar in grid mode. For details, see **BarGridColumnOptions**. This attribute is effective only in horizontal mode. It is not applicable to [XS, XL, and XXL devices](../../ui/arkts-layout-development-grid-layout.md#grid-breakpoints). | 58 59## DividerStyle<sup>10+</sup> 60 61| Name | Type | Mandatory | Description | 62| ----------- | ---------------------------------------- | ---- | ---------------------------------------- | 63| strokeWidth | [Length](ts-types.md#length) | Yes | Width of the divider. It cannot be set in percentage. | 64| color | [ResourceColor](ts-types.md#resourcecolor) | No | Color of the divider.<br>Default value: **#33182431** | 65| startMargin | [Length](ts-types.md#length) | No | Distance between the divider and the top of the sidebar. It cannot be set in percentage.<br>Default value: **0.0**<br>Unit: vp| 66| endMargin | [Length](ts-types.md#length) | No | Distance between the divider and the bottom of the sidebar. It cannot be set in percentage.<br>Default value: **0.0**<br>Unit: vp| 67 68## BarGridColumnOptions<sup>10+</sup> 69 70| Name | Type | Mandatory | Description | 71| ----------- | ---------------------------------------- | ---- | ---------------------------------------- | 72| margin | [Dimension](ts-types.md#dimension10) | No | Column margin in grid mode. It cannot be set in percentage.<br>Default value: **24.0**<br>Unit: vp | 73| gutter | [Dimension](ts-types.md#dimension10) | No | Column gutter (that is, gap between columns) in grid mode. It cannot be set in percentage.<br>Default value: **24.0**<br>Unit: vp | 74| sm | number | No | Number of columns occupied by a tab on a screen whose width is greater than or equal to 320 vp but less than 600 vp.<br>The value must be a non-negative even number. The default value is **-1**, indicating that the tab takes up the entire width of the tab bar.| 75| md | number | No | Number of columns occupied by a tab on a screen whose width is greater than or equal to 600 vp but less than 800 vp.<br>The value must be a non-negative even number. The default value is **-1**, indicating that the tab takes up the entire width of the tab bar.| 76| lg | number | No | Number of columns occupied by a tab on a screen whose width is greater than or equal to 840 vp but less than 1024 vp.<br>The value must be a non-negative even number. The default value is **-1**, indicating that the tab takes up the entire width of the tab bar.| 77 78## ScrollableBarModeOptions<sup>10+</sup> 79 80| Name | Type | Mandatory | Description | 81| ----------- | ---------------------------------------- | ---- | ---------------------------------------- | 82| margin | [Dimension](ts-types.md#dimension10) | No | Left and right margin of the tab bar in scrollable mode. It cannot be set in percentage.<br>Default value: **0.0**<br>Unit: vp | 83| nonScrollableLayoutStyle | [LayoutStyle](#layoutstyle10) | No | Tab layout mode of the tab bar when not scrolling in scrollable mode.<br>Default value: **LayoutStyle.ALWAYS_CENTER** | 84 85## BarMode 86 87| Name | Description | 88| ---------- | ---------------------------------------- | 89| Scrollable | The width of each tab is determined by the actual layout. The tabs are scrollable in the following case: In horizontal layout, the total width exceeds the tab bar width; in horizontal layout, the total height exceeds the tab bar height.| 90| Fixed | The width of each tab is determined by equally dividing the number of tabs by the bar width (or bar height in the vertical layout).| 91 92## LayoutStyle<sup>10+</sup> 93 94| Name | Description | 95| ---------- | ---------------------------------------- | 96| ALWAYS_CENTER | When the tab content exceeds the tab bar width, the tabs are scrollable.<br>Otherwise, the tabs are compactly centered and not scrollable.| 97| ALWAYS_AVERAGE_SPLITE | When the tab content exceeds the tab bar width, the tabs are scrollable.<br>Otherwise, the tabs are not scrollable, and the tab bar width is distributed evenly between all tabs.<br>This option is valid only in horizontal mode, and is equivalent to **LayoutStyle.ALWAYS_CENTER** otherwise.| 98| SPACE_BETWEEN_OR_CENTER | When the tab content exceeds the tab bar width, the tabs are scrollable.<br>When the tab content exceeds half of the tab bar width but still within the tab bar width, the tabs are compactly centered and not scrollable.<br>When the tab content does not exceed half of the tab bar width, the tabs are centered within half of the tab bar width, with even spacing between, and not scrollable.| 99 100## Events 101 102In addition to the [universal events](ts-universal-events-click.md), the following events are supported. 103 104| Name | Description | 105| ---------------------------------------- | ---------------------------------------- | 106| onChange(event: (index: number) => void) | Triggered when a tab is switched.<br>- **index**: index of the active tab. The index starts from 0.<br>This event is triggered when any of the following conditions is met:<br>1. The **\<TabContent>** component supports sliding, and the user slides on the tab bar.<br>2. The [Controller](#tabscontroller) API is called.<br>3. The attribute value is updated using a [state variable](../../quick-start/arkts-state.md).<br>4. A tab is clicked.| 107| onTabBarClick(event: (index: number) => void)<sup>10+</sup> | Triggered when a tab is clicked.<br>- **index**: index of the clicked tab. The index starts from 0.| 108| onAnimationStart<sup>11+</sup>(handler: (index: number, targetIndex: number, event: [TabsAnimationEvent](ts-types.md#tabsanimationevent11)) => void) | Triggered when the tab switching animation starts.<br>- **index**: index of the currently displayed element.<br>- **targetIndex**: index of the target element to switch to.<br>- **event**: animation-related information, including the offset of the currently displayed element and target element relative to the start position of the **\<Tabs>** along the main axis, and the hands-off velocity.<br>**NOTE**<br>The **index** parameter indicates the index before the animation starts (not the one after).| 109| onAnimationEnd<sup>11+</sup>(handler: (index: number, event: [TabsAnimationEvent](ts-types.md#tabsanimationevent11)) => void) | Triggered when the tab switching animation ends.<br>- **index**: index of the currently displayed element.<br>- **event**: animation-related information, including the offset of the currently displayed element relative to the start position of the **\<Tabs>** along the main axis.<br>**NOTE**<br>This event is triggered when the tab switching animation ends, whether it is caused by gesture interruption or not. The **index** parameter indicates the index after the animation ends.| 110| onGestureSwipe<sup>11+</sup>(handler: (index: number, event: [TabsAnimationEvent](ts-types.md#tabsanimationevent11)) => void) | Triggered on a frame-by-frame basis when the tab is switched by a swipe.<br>- **index**: index of the currently displayed element.<br>- **event**: animation-related information, including the offset of the currently displayed element relative to the start position of the **\<Tabs>** along the main axis.| 111| customContentTransition<sup>11+</sup>(delegate: (from: number, to: number) => [TabContentAnimatedTransition](ts-types.md#tabcontentanimatedtransition11) \| undefined) | Custom tab switching animation. **from** and **to** indicate the return values.<br> - **from**: index of the currently displayed tab before the animation starts.<br>- **to**: index of the target tab before the animation starts.<br> Instructions:<br> 1. When the custom tab switching animation is used, the default switching animation of the **\<Tabs>** component is disabled, and tabs cannot be switched through swiping.<br> 2. The value **undefined** means not to use the custom tab switching animation, in which case the default switching animation is used.<br> 3. The custom tab switching animation cannot be interrupted.<br> 4. Currently, the custom tab switching animation can be triggered only by clicking a tab or by calling the **TabsController.changeIndex()** API.<br> 5. When the custom tab switching animation is used, the **\<Tabs>** component supports all events except **onGestureSwipe**.<br> 6. Notes about the **onChange** and **onAnimationEnd** events: If the second custom animation is triggered during the execution of the first custom animation, the **onChange** and **onAnimationEnd** events of the first custom animation will be triggered when the second custom animation starts.<br> 7. When the custom animation is used, the stack layout is used for pages involved in the animation. If the **zIndex** attribute is not set for related pages, the **zIndex** values of all pages are the same. In this case, the pages are rendered in the order in which they are added to the component tree (that is, the sequence of page indexes). In light of this, to control the rendering levels of pages, set the **zIndex** attribute of the pages.<br>| 112 113## TabsController 114 115Defines a tab controller, which is used to control switching of tabs. One **TabsController** cannot control multiple **\<Tabs>** components. 116 117### Objects to Import 118 119```ts 120let controller: TabsController = new TabsController() 121``` 122 123### changeIndex 124 125changeIndex(value: number): void 126 127Switches to the specified tab. 128 129**Parameters** 130 131| Name | Type | Mandatory | Description | 132| ----- | ------ | ---- | ---------------------------------------- | 133| value | number | Yes | Index of the tab. The value starts from 0.<br>**NOTE**<br>If this parameter is set to a value less than 0 or greater than the maximum number, the default value **0** is used.| 134 135 136## Example 137 138### Example 1 139 140```ts 141// xxx.ets 142@Entry 143@Component 144struct TabsExample { 145 @State fontColor: string = '#182431' 146 @State selectedFontColor: string = '#007DFF' 147 @State currentIndex: number = 0 148 private controller: TabsController = new TabsController() 149 150 @Builder tabBuilder(index: number, name: string) { 151 Column() { 152 Text(name) 153 .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor) 154 .fontSize(16) 155 .fontWeight(this.currentIndex === index ? 500 : 400) 156 .lineHeight(22) 157 .margin({ top: 17, bottom: 7 }) 158 Divider() 159 .strokeWidth(2) 160 .color('#007DFF') 161 .opacity(this.currentIndex === index ? 1 : 0) 162 }.width('100%') 163 } 164 165 build() { 166 Column() { 167 Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) { 168 TabContent() { 169 Column().width('100%').height('100%').backgroundColor('#00CB87') 170 }.tabBar(this.tabBuilder(0, 'green')) 171 172 TabContent() { 173 Column().width('100%').height('100%').backgroundColor('#007DFF') 174 }.tabBar(this.tabBuilder(1, 'blue')) 175 176 TabContent() { 177 Column().width('100%').height('100%').backgroundColor('#FFBF00') 178 }.tabBar(this.tabBuilder(2, 'yellow')) 179 180 TabContent() { 181 Column().width('100%').height('100%').backgroundColor('#E67C92') 182 }.tabBar(this.tabBuilder(3, 'pink')) 183 } 184 .vertical(false) 185 .barMode(BarMode.Fixed) 186 .barWidth(360) 187 .barHeight(56) 188 .animationDuration(400) 189 .onChange((index: number) => { 190 this.currentIndex = index 191 }) 192 .width(360) 193 .height(296) 194 .margin({ top: 52 }) 195 .backgroundColor('#F1F3F5') 196 }.width('100%') 197 } 198} 199``` 200 201 202 203### Example 2 204 205```ts 206// xxx.ets 207@Entry 208@Component 209struct TabsDivider1 { 210 private controller1: TabsController = new TabsController() 211 @State dividerColor: string = 'red' 212 @State strokeWidth: number = 2 213 @State startMargin: number = 0 214 @State endMargin: number = 0 215 @State nullFlag: boolean = false 216 217 build() { 218 Column() { 219 Tabs({ controller: this.controller1 }) { 220 TabContent() { 221 Column().width('100%').height('100%').backgroundColor(Color.Pink) 222 }.tabBar('pink') 223 224 TabContent() { 225 Column().width('100%').height('100%').backgroundColor(Color.Yellow) 226 }.tabBar('yellow') 227 228 TabContent() { 229 Column().width('100%').height('100%').backgroundColor(Color.Blue) 230 }.tabBar('blue') 231 232 TabContent() { 233 Column().width('100%').height('100%').backgroundColor(Color.Green) 234 }.tabBar('green') 235 236 TabContent() { 237 Column().width('100%').height('100%').backgroundColor(Color.Red) 238 }.tabBar('red') 239 } 240 .vertical(true) 241 .scrollable(true) 242 .barMode(BarMode.Fixed) 243 .barWidth(70) 244 .barHeight(200) 245 .animationDuration(400) 246 .onChange((index: number) => { 247 console.info(index.toString()) 248 }) 249 .height('200vp') 250 .margin({ bottom: '12vp' }) 251 .divider(this.nullFlag ? null : { 252 strokeWidth: this.strokeWidth, 253 color: this.dividerColor, 254 startMargin: this.startMargin, 255 endMargin: this.endMargin 256 }) 257 258 Button ('Regular Divider').width('100%').margin({ bottom: '12vp'}) 259 .onClick(() => { 260 this.nullFlag = false; 261 this.strokeWidth = 2; 262 this.dividerColor = 'red'; 263 this.startMargin = 0; 264 this.endMargin = 0; 265 }) 266 Button('Empty Divider').width('100%').margin({ bottom: '12vp' }) 267 .onClick(() => { 268 this.nullFlag = true 269 }) 270 Button('Change to Blue').width('100%').margin({ bottom: '12vp'}) 271 .onClick(() => { 272 this.dividerColor = 'blue' 273 }) 274 Button('Increase Width').width('100%').margin({ bottom: '12vp' }) 275 .onClick(() => { 276 this.strokeWidth += 2 277 }) 278 Button('Decrease Width').width('100%').margin({ bottom: '12vp'}) 279 .onClick(() => { 280 if (this.strokeWidth > 2) { 281 this.strokeWidth -= 2 282 } 283 }) 284 Button ('Increase Top Margin').width ('100%').margin ({ bottom:'12vp'}) 285 .onClick(() => { 286 this.startMargin += 2 287 }) 288 Button ('Decrease Top Margin').width ('100%').margin ({ bottom:'12vp' }) 289 .onClick(() => { 290 if (this.startMargin > 2) { 291 this.startMargin -= 2 292 } 293 }) 294 Button ('Increase Bottom Margin').width ('100%').margin ({ bottom:'12vp'}) 295 .onClick(() => { 296 this.endMargin += 2 297 }) 298 Button ('Decrease Bottom Margin').width ('100%').margin ({ bottom:'12vp' }) 299 .onClick(() => { 300 if (this.endMargin > 2) { 301 this.endMargin -= 2 302 } 303 }) 304 }.padding({ top: '24vp', left: '24vp', right: '24vp' }) 305 } 306} 307``` 308 309 310 311### Example 3 312 313```ts 314// xxx.ets 315@Entry 316@Component 317struct TabsOpaque { 318 @State message: string = 'Hello World' 319 private controller: TabsController = new TabsController() 320 private controller1: TabsController = new TabsController() 321 @State selfFadingFade: boolean = true; 322 323 build() { 324 Column() { 325 Button (Set Tab to Fade').width ('100%').margin ({bottom: '12vp'}) 326 .onClick((event?: ClickEvent) => { 327 this.selfFadingFade = true; 328 }) 329 Button (Set Tab Not to Fade').width ('100%').margin ({bottom: '12vp'}) 330 .onClick((event?: ClickEvent) => { 331 this.selfFadingFade = false; 332 }) 333 Tabs({ barPosition: BarPosition.End, controller: this.controller }) { 334 TabContent() { 335 Column().width('100%').height('100%').backgroundColor(Color.Pink) 336 }.tabBar('pink') 337 338 TabContent() { 339 Column().width('100%').height('100%').backgroundColor(Color.Yellow) 340 }.tabBar('yellow') 341 342 TabContent() { 343 Column().width('100%').height('100%').backgroundColor(Color.Blue) 344 }.tabBar('blue') 345 346 TabContent() { 347 Column().width('100%').height('100%').backgroundColor(Color.Green) 348 }.tabBar('green') 349 350 TabContent() { 351 Column().width('100%').height('100%').backgroundColor(Color.Green) 352 }.tabBar('green') 353 354 TabContent() { 355 Column().width('100%').height('100%').backgroundColor(Color.Green) 356 }.tabBar('green') 357 358 TabContent() { 359 Column().width('100%').height('100%').backgroundColor(Color.Green) 360 }.tabBar('green') 361 362 TabContent() { 363 Column().width('100%').height('100%').backgroundColor(Color.Green) 364 }.tabBar('green') 365 } 366 .vertical(false) 367 .scrollable(true) 368 .barMode(BarMode.Scrollable) 369 .barHeight(80) 370 .animationDuration(400) 371 .onChange((index: number) => { 372 console.info(index.toString()) 373 }) 374 .fadingEdge(this.selfFadingFade) 375 .height('30%') 376 .width('100%') 377 378 Tabs({ barPosition: BarPosition.Start, controller: this.controller1 }) { 379 TabContent() { 380 Column().width('100%').height('100%').backgroundColor(Color.Pink) 381 }.tabBar('pink') 382 383 TabContent() { 384 Column().width('100%').height('100%').backgroundColor(Color.Yellow) 385 }.tabBar('yellow') 386 387 TabContent() { 388 Column().width('100%').height('100%').backgroundColor(Color.Blue) 389 }.tabBar('blue') 390 391 TabContent() { 392 Column().width('100%').height('100%').backgroundColor(Color.Green) 393 }.tabBar('green') 394 395 TabContent() { 396 Column().width('100%').height('100%').backgroundColor(Color.Green) 397 }.tabBar('green') 398 399 TabContent() { 400 Column().width('100%').height('100%').backgroundColor(Color.Green) 401 }.tabBar('green') 402 } 403 .vertical(true) 404 .scrollable(true) 405 .barMode(BarMode.Scrollable) 406 .barHeight(200) 407 .barWidth(80) 408 .animationDuration(400) 409 .onChange((index: number) => { 410 console.info(index.toString()) 411 }) 412 .fadingEdge(this.selfFadingFade) 413 .height('30%') 414 .width('100%') 415 } 416 .padding({ top: '24vp', left: '24vp', right: '24vp' }) 417 } 418} 419``` 420 421 422 423### Example 4 424 425```ts 426// xxx.ets 427@Entry 428@Component 429struct barBackgroundColorTest { 430 private controller: TabsController = new TabsController() 431 @State barOverlap: boolean = true; 432 @State barBackgroundColor: string = '#88888888'; 433 434 build() { 435 Column() { 436 Button ("Change barOverlap").width ('100%').margin ({ bottom:'12vp'}) 437 .onClick((event?: ClickEvent) => { 438 if (this.barOverlap) { 439 this.barOverlap = false; 440 } else { 441 this.barOverlap = true; 442 } 443 }) 444 445 Tabs({ barPosition: BarPosition.Start, index: 0, controller: this.controller }) { 446 TabContent() { 447 Column() { 448 Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 }) 449 Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16) 450 }.width('100%').width('100%').height('100%') 451 .backgroundColor(Color.Pink) 452 } 453 .tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "1")) 454 455 TabContent() { 456 Column() { 457 Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 }) 458 Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16) 459 }.width('100%').width('100%').height('100%') 460 .backgroundColor(Color.Yellow) 461 } 462 .tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "2")) 463 464 TabContent() { 465 Column() { 466 Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 }) 467 Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16) 468 }.width('100%').width('100%').height('100%') 469 .backgroundColor(Color.Green) 470 } 471 .tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "3")) 472 } 473 .vertical(false) 474 .barMode(BarMode.Fixed) 475 .height('60%') 476 .barOverlap(this.barOverlap) 477 .scrollable(true) 478 .animationDuration(10) 479 .barBackgroundColor(this.barBackgroundColor) 480 } 481 .height(500) 482 .padding({ top: '24vp', left: '24vp', right: '24vp' }) 483 } 484} 485``` 486 487 488 489 490### Example 5 491 492```ts 493// xxx.ets 494@Entry 495@Component 496struct TabsExample5 { 497 private controller: TabsController = new TabsController() 498 @State gridMargin: number = 10 499 @State gridGutter: number = 10 500 @State sm: number = -2 501 @State clickedContent: string = ""; 502 503 build() { 504 Column() { 505 Row() { 506 Button("gridMargin+10 " + this.gridMargin) 507 .width('47%') 508 .height(50) 509 .margin({ top: 5 }) 510 .onClick((event?: ClickEvent) => { 511 this.gridMargin += 10 512 }) 513 .margin({ right: '6%', bottom: '12vp' }) 514 Button("gridMargin-10 " + this.gridMargin) 515 .width('47%') 516 .height(50) 517 .margin({ top: 5 }) 518 .onClick((event?: ClickEvent) => { 519 this.gridMargin -= 10 520 }) 521 .margin({ bottom: '12vp' }) 522 } 523 524 Row() { 525 Button("gridGutter+10 " + this.gridGutter) 526 .width('47%') 527 .height(50) 528 .margin({ top: 5 }) 529 .onClick((event?: ClickEvent) => { 530 this.gridGutter += 10 531 }) 532 .margin({ right: '6%', bottom: '12vp' }) 533 Button("gridGutter-10 " + this.gridGutter) 534 .width('47%') 535 .height(50) 536 .margin({ top: 5 }) 537 .onClick((event?: ClickEvent) => { 538 this.gridGutter -= 10 539 }) 540 .margin({ bottom: '12vp' }) 541 } 542 543 Row() { 544 Button("sm+2 " + this.sm) 545 .width('47%') 546 .height(50) 547 .margin({ top: 5 }) 548 .onClick((event?: ClickEvent) => { 549 this.sm += 2 550 }) 551 .margin({ right: '6%' }) 552 Button("sm-2 " + this.sm).width('47%').height(50).margin({ top: 5 }) 553 .onClick((event?: ClickEvent) => { 554 this.sm -= 2 555 }) 556 } 557 558 Text ("Tab clicks: "+ this.clickedContent).width ('100%').height (200).margin ({ top: 5 }) 559 560 561 Tabs({ barPosition: BarPosition.End, controller: this.controller }) { 562 TabContent() { 563 Column().width('100%').height('100%').backgroundColor(Color.Pink) 564 }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "1")) 565 566 TabContent() { 567 Column().width('100%').height('100%').backgroundColor(Color.Green) 568 }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "2")) 569 570 TabContent() { 571 Column().width('100%').height('100%').backgroundColor(Color.Blue) 572 }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "3")) 573 } 574 .width('350vp') 575 .animationDuration(300) 576 .height('60%') 577 .barGridAlign({ sm: this.sm, margin: this.gridMargin, gutter: this.gridGutter }) 578 .backgroundColor(0xf1f3f5) 579 .onTabBarClick((index: number) => { 580 this.clickedContent += "index " + index + " was clicked\n"; 581 }) 582 } 583 .width('100%') 584 .height(500) 585 .margin({ top: 5 }) 586 .padding('10vp') 587 } 588} 589``` 590 591 592 593### Example 6 594 595```ts 596// xxx.ets 597@Entry 598@Component 599struct TabsExample6 { 600 private controller: TabsController = new TabsController() 601 @State scrollMargin: number = 0 602 @State layoutStyle: LayoutStyle = LayoutStyle.ALWAYS_CENTER 603 @State text: string = "Text" 604 605 build() { 606 Column() { 607 Row() { 608 Button("scrollMargin+10 " + this.scrollMargin) 609 .width('47%') 610 .height(50) 611 .margin({ top: 5 }) 612 .onClick((event?: ClickEvent) => { 613 this.scrollMargin += 10 614 }) 615 .margin({ right: '6%', bottom: '12vp' }) 616 Button("scrollMargin-10 " + this.scrollMargin) 617 .width('47%') 618 .height(50) 619 .margin({ top: 5 }) 620 .onClick((event?: ClickEvent) => { 621 this.scrollMargin -= 10 622 }) 623 .margin({ bottom: '12vp' }) 624 } 625 626 Row() { 627 Button ("Add Text") 628 .width('47%') 629 .height(50) 630 .margin({ top: 5 }) 631 .onClick((event?: ClickEvent) => { 632 this.text += 'Add Text' 633 }) 634 .margin({ right: '6%', bottom: '12vp' }) 635 Button ("Reset Text") 636 .width('47%') 637 .height(50) 638 .margin({ top: 5 }) 639 .onClick((event?: ClickEvent) => { 640 this.text = "Text" 641 }) 642 .margin({ bottom: '12vp' }) 643 } 644 645 Row() { 646 Button("layoutStyle.ALWAYS_CENTER") 647 .width('100%') 648 .height(50) 649 .margin({ top: 5 }) 650 .fontSize(15) 651 .onClick((event?: ClickEvent) => { 652 this.layoutStyle = LayoutStyle.ALWAYS_CENTER; 653 }) 654 .margin({ bottom: '12vp' }) 655 } 656 657 Row() { 658 Button("layoutStyle.ALWAYS_AVERAGE_SPLIT") 659 .width('100%') 660 .height(50) 661 .margin({ top: 5 }) 662 .fontSize(15) 663 .onClick((event?: ClickEvent) => { 664 this.layoutStyle = LayoutStyle.ALWAYS_AVERAGE_SPLIT; 665 }) 666 .margin({ bottom: '12vp' }) 667 } 668 669 Row() { 670 Button("layoutStyle.SPACE_BETWEEN_OR_CENTER") 671 .width('100%') 672 .height(50) 673 .margin({ top: 5 }) 674 .fontSize(15) 675 .onClick((event?: ClickEvent) => { 676 this.layoutStyle = LayoutStyle.SPACE_BETWEEN_OR_CENTER; 677 }) 678 .margin({ bottom: '12vp' }) 679 } 680 681 Tabs({ barPosition: BarPosition.End, controller: this.controller }) { 682 TabContent() { 683 Column().width('100%').height('100%').backgroundColor(Color.Pink) 684 }.tabBar(SubTabBarStyle.of(this.text)) 685 686 TabContent() { 687 Column().width('100%').height('100%').backgroundColor(Color.Green) 688 }.tabBar(SubTabBarStyle.of(this.text)) 689 690 TabContent() { 691 Column().width('100%').height('100%').backgroundColor(Color.Blue) 692 }.tabBar(SubTabBarStyle.of(this.text)) 693 } 694 .animationDuration(300) 695 .height('60%') 696 .backgroundColor(0xf1f3f5) 697 .barMode(BarMode.Scrollable, { margin: this.scrollMargin, nonScrollableLayoutStyle: this.layoutStyle }) 698 } 699 .width('100%') 700 .height(500) 701 .margin({ top: 5 }) 702 .padding('24vp') 703 } 704} 705``` 706 707 708 709### Example 7 710 711```ts 712// xxx.ets 713@Entry 714@Component 715struct TabsCustomAnimationExample { 716 @State useCustomAnimation: boolean = true 717 @State tabContent0Scale: number = 1.0 718 @State tabContent1Scale: number = 1.0 719 @State tabContent0Opacity: number = 1.0 720 @State tabContent1Opacity: number = 1.0 721 @State tabContent2Opacity: number = 1.0 722 tabsController: TabsController = new TabsController() 723 private firstTimeout: number = 3000 724 private secondTimeout: number = 5000 725 private first2secondDuration: number = 3000 726 private second2thirdDuration: number = 5000 727 private first2thirdDuration: number = 2000 728 private baseCustomAnimation: (from: number, to: number) => TabContentAnimatedTransition = (from: number, to: number) => { 729 if ((from === 0 && to === 1) || (from === 1 && to === 0)) { 730 // Scale animation 731 let firstCustomTransition = { 732 timeout: this.firstTimeout, 733 transition: (proxy: TabContentTransitionProxy) => { 734 if (proxy.from === 0 && proxy.to === 1) { 735 this.tabContent0Scale = 1.0 736 this.tabContent1Scale = 0.5 737 } else { 738 this.tabContent0Scale = 0.5 739 this.tabContent1Scale = 1.0 740 } 741 742 animateTo({ 743 duration: this.first2secondDuration, 744 onFinish: () => { 745 proxy.finishTransition() 746 } 747 }, () => { 748 if (proxy.from === 0 && proxy.to === 1) { 749 this.tabContent0Scale = 0.5 750 this.tabContent1Scale = 1.0 751 } else { 752 this.tabContent0Scale = 1.0 753 this.tabContent1Scale = 0.5 754 } 755 }) 756 } 757 } as TabContentAnimatedTransition; 758 return firstCustomTransition; 759 } else { 760 // Opacity animation 761 let secondCustomTransition = { 762 timeout: this.secondTimeout, 763 transition: (proxy: TabContentTransitionProxy) => { 764 if ((proxy.from === 1 && proxy.to === 2) || (proxy.from === 2 && proxy.to === 1)) { 765 if (proxy.from === 1 && proxy.to === 2) { 766 this.tabContent1Opacity = 1.0 767 this.tabContent2Opacity = 0.5 768 } else { 769 this.tabContent1Opacity = 0.5 770 this.tabContent2Opacity = 1.0 771 } 772 animateTo({ 773 duration: this.second2thirdDuration, 774 onFinish: () => { 775 proxy.finishTransition() 776 } 777 }, () => { 778 if (proxy.from === 1 && proxy.to === 2) { 779 this.tabContent1Opacity = 0.5 780 this.tabContent2Opacity = 1.0 781 } else { 782 this.tabContent1Opacity = 1.0 783 this.tabContent2Opacity = 0.5 784 } 785 }) 786 } else if ((proxy.from === 0 && proxy.to === 2) || (proxy.from === 2 && proxy.to === 0)) { 787 if (proxy.from === 0 && proxy.to === 2) { 788 this.tabContent0Opacity = 1.0 789 this.tabContent2Opacity = 0.5 790 } else { 791 this.tabContent0Opacity = 0.5 792 this.tabContent2Opacity = 1.0 793 } 794 animateTo({ 795 duration: this.first2thirdDuration, 796 onFinish: () => { 797 proxy.finishTransition() 798 } 799 }, () => { 800 if (proxy.from === 0 && proxy.to === 2) { 801 this.tabContent0Opacity = 0.5 802 this.tabContent2Opacity = 1.0 803 } else { 804 this.tabContent0Opacity = 1.0 805 this.tabContent2Opacity = 0.5 806 } 807 }) 808 } 809 } 810 } as TabContentAnimatedTransition; 811 return secondCustomTransition; 812 } 813 } 814 815 build() { 816 Column() { 817 Tabs({ controller: this.tabsController }) { 818 TabContent() { 819 Text("Red") 820 } 821 .tabBar("Red") 822 .scale({ x: this.tabContent0Scale, y: this.tabContent0Scale }) 823 .backgroundColor(Color.Red) 824 .opacity(this.tabContent0Opacity) 825 .width(100) 826 .height(100) 827 828 TabContent() { 829 Text("Yellow") 830 } 831 .tabBar("Yellow") 832 .scale({ x: this.tabContent1Scale, y: this.tabContent1Scale }) 833 .backgroundColor(Color.Yellow) 834 .opacity(this.tabContent1Opacity) 835 .width(200) 836 .height(200) 837 838 TabContent() { 839 Text("Blue") 840 } 841 .tabBar("Blue") 842 .backgroundColor(Color.Blue) 843 .opacity(this.tabContent2Opacity) 844 .width(300) 845 .height(300) 846 847 } 848 .backgroundColor(0xf1f3f5) 849 .width('100%') 850 .height(500) 851 .margin({ top: 5 }) 852 .customContentTransition(this.useCustomAnimation ? this.baseCustomAnimation : undefined) 853 .barMode(BarMode.Scrollable) 854 .onChange((index: number) => { 855 console.info("onChange index: " + index) 856 }) 857 .onTabBarClick((index: number) => { 858 console.info("onTabBarClick index: " + index) 859 }) 860 } 861 } 862} 863``` 864 865 866