HarmonyOS鸿蒙Next中左右滑动数据延迟Tabs+ForEach+TabContent解决方案
HarmonyOS鸿蒙Next中左右滑动数据延迟Tabs+ForEach+TabContent解决方案
一个页面有三个list列表,可以左右滑动,可以点击切换 目前我的页面主代码如下所示:
Tabs({
barPosition: BarPosition.Start,
controller: this.subController
}) {
ForEach(this.mTitleList, (item: string, index: number) {
TabContent() {
Refresh({
refreshing: $$this.isRefreshing,
builder: CustomheaderRefreshlayout()
}) {
this.itemLayout()
}.onStateChange((refreshStatus: RefreshStatus) {
switch (refreshStatus) {
case RefreshStatus.Inactive:
break
case RefreshStatus.Drag:
break
case RefreshStatus.OverDrag:
break
case RefreshStatus.Refresh:
break
case RefreshStatus.Done:
break
}
}).onRefreshing(() {
setTimeout(() {
this.isRefreshing = false
}, 2000)
console.log('onRefreshing test')
})
}
.tabBar(this.TabBuilder(item, index))
.width('100%')
.height('100%')
.backgroundColor($r('app.color.project_main_bg_color'))
})
}
.scrollable(true) //允许滑动
.width(CommonConstant.FULL_WIDTH)
.barHeight($r('app.float.default_46'))
.barMode(BarMode.Fixed) //Fixed 宽度平分, scrollable可滑动
.onChange((index: number) {
this.currentIndex = index;
})
@ Builder
itemLayout() {
Column() {
Text(this.currentIndex == 0 ? '快讯' : this.currentIndex == 1 ? "提醒" : '消息')
.fontColor(Color.Red)
.fontSize($r('app.float.default_text_size_24'))
.fontWeight(FontWeight.Bold)
}.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width("100%")
.height("100%")
}
滑动的时候,滑到当前页面有1秒仍然是显示上个页面的数据,比较滞后,有没有相关属性或方法设置,滑动后快速显示滑动后页面的内容?
更多关于HarmonyOS鸿蒙Next中左右滑动数据延迟Tabs+ForEach+TabContent解决方案的实战教程也可以访问 https://www.itying.com/category-93-b0.html
itemLayout 里面内容跟currentIndex绑定了。界面加载完成后,三个界面是 ‘快讯 快讯 快讯’。滑动过程中,currentIndex没变。界面也没有变,就一直看到 ‘快讯’。onChange执行后,currentIndex就成1 三个界面 就成‘提醒 提醒 提醒’。感觉是保留了上个界面。其实是因为你三个界面都一样。
把itemLayout里的current干掉就好了
更多关于HarmonyOS鸿蒙Next中左右滑动数据延迟Tabs+ForEach+TabContent解决方案的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
您可以参考如下demo:
// xxx.ets
import ComponentUtils from '@ohos.arkui.UIContext';
import { router } from '@kit.ArkUI';
@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()
@State isRefreshing: boolean = false
private subController: TabsController = new TabsController(); //Tab 控制器
@State mMessageType: number = 1
@State private mTitleList: Array<string> = []
aboutToAppear(): void {
if (this.mMessageType == 1) {
this.mTitleList.push('安顿快讯')
this.mTitleList.push('疾病提醒')
this.mTitleList.push('互动消息')
} else if (this.mMessageType == 2) {
this.mTitleList.push('紧急提示')
this.mTitleList.push('疾病提醒')
this.mTitleList.push('器官异常')
}
}
@Builder
TabBuilder(title: string, index: number) {
Column() {
Text(title)
.width('100%')
.textAlign(TextAlign.Center)
.fontColor(index == this.currentIndex ? Color.Red : Color.Black)
.fontSize(16)
.fontWeight(index == this.currentIndex ? FontWeight.Bold : FontWeight.Normal)
.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
}
})
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height(46)
}
@Builder CustomheaderRefreshlayout(){
Column(){
Text("这是下拉刷新头部")
.width("100%")
.height(80)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
.backgroundColor(Color.Green)
.width("100%")
.height(80)
}
@Builder
itemLayout() {
Column(){
Text(this.currentIndex==0?'安顿快讯':this.currentIndex==1?"疾病提醒":'互动消息')
.fontColor(Color.Red)
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width("100%")
.height("100%")
}
build() {
Column() {
Stack({ alignContent: Alignment.TopEnd }) {
Row(){
Image($r('app.media.startIcon'))
.objectFit(ImageFit.None).width(36).height(36)
.onClick(()=> {
router.back();
})
Text(this.mMessageType==1?'系统消息':this.mMessageType==2?'健康消息':this.mMessageType==3?'SOS求助':'咨询问诊')
.fontColor(Color.White)
.fontSize(16)
Image($r('app.media.startIcon'))
.objectFit(ImageFit.None).visibility(Visibility.Hidden).width(36).height(36)
}
.height('50vp')
.width('100%')
.backgroundColor(Color.Green)
.justifyContent(FlexAlign.SpaceBetween)
Row(){
Text('一键已读')
.fontSize(14)
.fontColor(Color.White)
}
.alignItems(VerticalAlign.Center)
.height('50vp')
.margin({right:14})
}
Stack({ alignContent: Alignment.TopStart }) {
Tabs({ barPosition: BarPosition.Start , controller:this.subController}) {
ForEach(this.mTitleList, (item: string, index: number)=> {
TabContent() {
Refresh({ refreshing: $$this.isRefreshing,builder:this.CustomheaderRefreshlayout()}){
this.itemLayout()
}
.onStateChange((refreshStatus: RefreshStatus) => {
switch (refreshStatus){
case RefreshStatus.Inactive:
break
case RefreshStatus.Drag:
break
case RefreshStatus.OverDrag:
break
case RefreshStatus.Refresh:
break
case RefreshStatus.Done:
break
}
})
.onRefreshing(() => {
setTimeout(() => {
this.isRefreshing = false
}, 2000)
console.log('onRefreshing test')
})
}
.tabBar(this.TabBuilder(item,index))
.width('100%').height('100%')
// .backgroundColor($r('app.color.project_main_bg_color'))
})
}
.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%')
.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%')
}.width('100%').height("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
})
}
}
参考链接:示例9自定义tabbar切换动画
在HarmonyOS鸿蒙Next中,使用Tabs+ForEach+TabContent实现左右滑动时,可能会遇到数据延迟的问题。这个问题通常是由于数据加载和UI渲染的异步性导致的。
要解决这个问题,可以采取以下方法:
-
优化数据加载:确保数据在滑动前已经加载完成,避免在滑动过程中进行数据加载操作。可以使用
@State或@Link等装饰器来管理数据状态,确保数据在UI渲染前已经准备好。 -
使用LazyForEach:如果数据量较大,可以使用
LazyForEach代替ForEach。LazyForEach会根据需要动态加载数据,减少初始化时的性能开销,从而改善滑动时的延迟问题。 -
预加载数据:在滑动到下一个Tab之前,提前加载下一个Tab的数据。可以通过监听滑动事件,在滑动到某个位置时触发数据预加载。
-
减少UI复杂度:简化每个TabContent的UI结构,减少不必要的组件嵌套和复杂的布局计算,以提高渲染性能。
-
使用
@BuilderParam:通过@BuilderParam将TabContent的内容动态构建,减少不必要的UI更新操作。 -
合理使用
@Watch:利用@Watch装饰器监听数据变化,确保数据更新时UI能够及时响应。
通过以上方法,可以有效减少HarmonyOS鸿蒙Next中左右滑动时的数据延迟问题,提升用户体验。
在HarmonyOS鸿蒙Next中,遇到左右滑动数据延迟的问题,可以通过以下步骤优化Tabs+ForEach+TabContent的组合:
- 减少渲染负载:确保
ForEach内部组件尽可能轻量化,避免复杂计算或频繁更新。 - 预加载数据:在滑动前预加载相邻Tab的数据,减少切换时的延迟。
- 使用LazyForEach:如果数据量较大,考虑使用
LazyForEach替代ForEach,按需加载组件。 - 优化布局:避免嵌套过多布局,减少布局计算时间。
- 异步加载:将数据加载和UI更新分离,使用异步任务处理数据加载。
通过以上优化,可以有效减少左右滑动时的数据延迟,提升用户体验。

