HarmonyOS鸿蒙Next中tabbar切换每次都触发组件的生命周期
HarmonyOS鸿蒙Next中tabbar切换每次都触发组件的生命周期 代码如下:
import { MessageList } from './MessageList'
import { BillList } from './BillList'
import { HMRouter } from '@hadss/hmrouter';
import { common } from '@kit.AbilityKit'
// Tab
interface Tab {
title: string;
icon: Resource;
selectedIcon: Resource;
}
@HMRouter({ pageUrl: 'Home' })
@ComponentV2
export struct Home {
@Local currentTabIndex: number = 0
private tabsController: TabsController = new TabsController()
private tabs: Array<Tab> = [
{ title: "账单", icon: $r('app.media.bill'), selectedIcon: $r('app.media.bill_selected') },
{ title: "消息", icon: $r('app.media.message'), selectedIcon: $r('app.media.message_selected') },
]
@Builder
TabItemBuilder(title: string, index: number, normalImg: Resource, selectedImg: Resource) {
Column() {
Image(this.currentTabIndex === index ? selectedImg : normalImg)
.size({ width: 28, height: 28 })
Text(title)
.fontColor(this.currentTabIndex === index ? '#007AFF' : '#6B6B6B')
}
.onClick(() => {
this.currentTabIndex = index
this.tabsController.changeIndex(index)
})
}
build() {
Flex({ direction: FlexDirection.Column }) {
// 内容区域
Column() {
if (this.currentTabIndex === 0) {
BillList()
} else if (this.currentTabIndex === 1) {
MessageList()
}
}
.flexGrow(1)
// 底部导航
Tabs({ controller: this.tabsController, barPosition: BarPosition.End, index: this.currentTabIndex }) {
ForEach(this.tabs, (item: Tab, index) => {
TabContent() {
}
.tabBar(this.TabItemBuilder(
item.title,
index,
item.icon,
item.selectedIcon
))
})
}
.width('100%')
.height(60)
.backgroundColor($r('app.color.tab_bg_color'))
}
}
}
每次切换tab都会触发组件的aboutToAppear生命周期。
为什么不放在tabcontent?因为多个tab会导致中间所有的tab都被激活一次
更多关于HarmonyOS鸿蒙Next中tabbar切换每次都触发组件的生命周期的实战教程也可以访问 https://www.itying.com/category-93-b0.html
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-tabs#%E7%A4%BA%E4%BE%8B11%E9%A2%84%E5%8A%A0%E8%BD%BD%E5%AD%90%E8%8A%82%E7%82%B9 ,小伙伴可以使用与预加载子节点,这样只会初始化一次
更多关于HarmonyOS鸿蒙Next中tabbar切换每次都触发组件的生命周期的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
这个文档里面的代码是用的TabContent,但是TabContent好像是swiper,切换的时候会出现滑动,能看到其他tab的内容一闪而过,
不用循环去做,你直接平铺TabContent就不会一闪而过了,
【问题分析】
关于Tabs对于组件的缓存,楼主可以参考一下这个文档说明,如果子组件是自定义组件的话无法使用预加载

【参考文档】
为什么不放在tabcontent?因为多个tab会导致中间所有的tab都被激活一次
正常情况下不会出现中间每个tab都被激活一次的。
建议你不要使用foreach循环初始化tab。
直接按照最简单的方案,多写几个TabContent就行了。
而且swiper是可以控制的,不让它滑动就行了。
开发者你好,参考一楼回复使用预加载,方案参考如下:
【解决方案】
方案一:调用Tabs组件的preloadItems接口后会一次性加载所有指定的子节点,为了性能考虑,建议分批加载子节点。具体代码示例可参考官网:示例11预加载子节点。
方案二:Swiper组件能够支持预加载功能。可以利用Swiper组件来构建自定义的Tabs,并借此实现预加载功能,请参阅以下示例代码:
@Entry
@ComponentV2
struct TabsPreLoadDemo {
@Local tabNames: string[] = ['飞机', '铁路', '自驾', '地铁', '公交', '骑行']
@Local selectedTabIndex: number = 0
@Local indicatorLeftOffset: number = 0
@Local indicatorOffset: number = 0
@Local firstWidth: number = -1
@Local otherWidth: number = -1
@Local swiperController: SwiperController = new SwiperController()
@Local swiperWidth: number = 0
build() {
RelativeContainer() {
Stack() {
Rect()
.height(30)
.stroke(Color.Black)
.radius(10)
.width(this.firstWidth)
.fill('#bff9f2')
.position({
left: this.indicatorLeftOffset + this.indicatorOffset,
bottom: 0
})
.animation({ duration: 300, curve: Curve.LinearOutSlowIn })
}
.width('100%')
.alignRules({
center: { anchor: 'Tabs', align: VerticalAlign.Center }
})
Row() {
ForEach(this.tabNames, (name: string, index: number) => {
Row() {
Text(name)
.fontSize(16)
.fontWeight(this.selectedTabIndex === index ? FontWeight.Bold : FontWeight.Normal)
.textAlign(TextAlign.Center)
.animation({ duration: 300 })
Image($r('app.media.startIcon'))
.width(14)
.height(14)
.margin({ left: 2 })
.visibility(this.selectedTabIndex === index ? Visibility.Visible : Visibility.None)
.animation({ duration: 300 })
}
.justifyContent(FlexAlign.Center)
.layoutWeight(this.selectedTabIndex === index ? 1.5 : 1)
.animation({ duration: 300 })
.onClick(() => {
this.selectedTabIndex = index;
this.swiperController.changeIndex(index, false);
this.getUIContext().animateTo({ duration: 500, curve: Curve.LinearOutSlowIn }, () => {
this.indicatorLeftOffset = this.otherWidth * index;
})
})
})
}
.width('100%')
.height(30)
.id('Tabs')
.onAreaChange((oldValue: Area, newValue: Area) => {
let tabWidth = newValue.width.valueOf() as number;
this.firstWidth = 1.5 * tabWidth / (this.tabNames.length + 0.5);
this.otherWidth = tabWidth / (this.tabNames.length + 0.5);
})
Swiper(this.swiperController) {
ForEach(this.tabNames, (name: string, index: number) => {
Column() {
Text(`${name} - ${index}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
})
}
.cachedCount(3)
.onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {
if (targetIndex > index) {
this.indicatorLeftOffset += this.otherWidth;
} else if (targetIndex < index) {
this.indicatorLeftOffset -= this.otherWidth;
}
this.indicatorOffset = 0
this.selectedTabIndex = targetIndex
})
.onAnimationEnd((index: number, extraInfo: SwiperAnimationEvent) => {
this.indicatorOffset = 0
})
.onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
let move: number = this.GetOffset(extraInfo.currentOffset);
if ((this.selectedTabIndex === 0 && extraInfo.currentOffset > 0) ||
(this.selectedTabIndex === this.tabNames.length - 1 && extraInfo.currentOffset < 0)) {
return;
}
this.indicatorOffset = extraInfo.currentOffset < 0 ? move : -move;
})
.onAreaChange((oldValue: Area, newValue: Area) => {
let width = newValue.width.valueOf() as number;
this.swiperWidth = width;
})
.curve(Curve.LinearOutSlowIn)
.loop(false)
.indicator(false)
.width('100%')
.id('MainContext')
.alignRules({
top: { anchor: 'Tabs', align: VerticalAlign.Bottom },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.height('100%')
.width('100%')
.padding(10)
}
GetOffset(swiperOffset: number): number {
let swiperMoveRatio: number = Math.abs(swiperOffset / this.swiperWidth);
let tabMoveValue: number = swiperMoveRatio >= 1 ? this.otherWidth : this.otherWidth * swiperMoveRatio;
return tabMoveValue;
}
}
两种方案对比:
| 方案 | 优缺点 | 适用场景 |
|---|---|---|
| 方案一:preloadItems | 可以分批次加载,使用方法简单。 | 在常规场景下,推荐使用该方法。 |
| 方案二:Swiper | 无法分批预加载,且需要自定义使用方法相对复杂。 | 自定义Tabs,使用Swiper组件特性的场景。 |
针对你说的其他tab的内容一闪而过问题:
Tabs组件采用了默认的Tabs页面切换动画,会导致动画过程中闪过其他Tabs页面:
- Tabs组件的animationMode属性的值不是AnimationMode.NO_ANIMATION,没有关闭Tabs页面切换动画。
- Tabs组件的animationDuration属性的值不为0,没有关闭Tabs页面切换动画。
- Tabs组件没有使用自定义Tabs页面切换动画,Tabs页面切换时中间会闪过其他Tabs页面。
【修改建议】
根据需要采用以下方案:关闭Tabs页面切换动画或者自定义Tabs页面切换动画。
- 配置animationMode属性值为AnimationMode.NO_ANIMATION,关闭Tabs页面切换动画。
Tabs(){
// ...
}
.animationMode(AnimationMode.NO_ANIMATION)
- 配置animationDuration属性值为0,关闭Tabs页面切换动画。
Tabs(){
// ...
}
.animationDuration(0)
- 参照自定义Tabs页面切换动画示例,通过customContentTransition事件,自定义Tabs页面切换动画。
在HarmonyOS Next中,TabBar切换时会触发组件生命周期。每次切换标签页,对应页面的aboutToAppear和aboutToDisappear方法会被调用,实现页面状态管理。系统通过ArkUI的声明式开发范式自动处理组件挂载与卸载,确保页面资源正确初始化和释放。若需保持页面状态,可使用@State装饰器或LazyForEach优化加载性能。


