中国优质的IT技术网站
专业IT技术创作平台
IT职业在线教育平台
微信扫码分享
import curves from '@ohos.curves'; import display from '@ohos.display'; import { BusinessError } from '@ohos.base'; import componentUtils from '@ohos.ArkUI.componentUtils'; class MyDataSource implements IDataSource { private list: number[] = [] constructor(list: number[]) { this.list = list } totalCount(): number { return this.list.length } getData(index: number): number { return this.list[index] } registerDataChangeListener(listener: DataChangeListener): void { } unregisterDataChangeListener() { } } @Entry @Component struct swiperTab { private displayInfo: display.Display | null = null; private controller: TabsController = new TabsController() private data: MyDataSource = new MyDataSource([]); private initialTabMargin: number = 5; //初始化时tabbarmargin private animationDuration: number = 300; //动画时间 private animationCurve: ICurve = curves.interpolatingSpring(7, 1, 328, 34); //动画曲线 @State currentIndex: number = 0; //内容区当前Index @State tabsWidth: number = 0; // vp 内容区宽度 @State indicatorWidth: number = 0; // vp 页签宽度 @State indicatorMarginLeft: number = 5; // vp 当前页签距离左边margin @State indicatorIndex: number = 0; //当前页签Index @State nextIndicatorIndex: number = 0; //下一个目标页签Index @State swipeRatio: number = 0; //判断是否翻页,页面滑动超过一半,tabBar切换到下一页。 private scroller: Scroller = new Scroller(); private arr: string[] = ['关注', '推荐', '热点', '上海', '视频', '新时代', '新歌', '新碟', '新片']; private textLength: number[] = [2, 2, 2, 2, 2, 3, 2, 2, 2] // 控制页签所在父容器宽度,避免造成下划线目标位置计算错误 aboutToAppear(): void { this.displayInfo = display.getDefaultDisplaySync(); //获取屏幕实例 let list: number[] = []; for (let i = 1; i <= this.arr.length; i++) { list.push(i); } this.data = new MyDataSource(list) } // 获取屏幕宽度,单位vp private getDisplayWidth(): number { return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0; } // 获取组件大小、位置、平移缩放旋转及仿射矩阵属性信息。 private getTextInfo(index: number): Record<string, number> { let modePosition: componentUtils.ComponentInfo = componentUtils.getRectangleById(index.toString()); try { return { 'left': px2vp(modePosition.windowOffset.x), 'width': px2vp(modePosition.size.width) } } catch (error) { return { 'left': 0, 'width': 0 } } } // 当前下划线动画 private getCurrentIndicatorInfo(index: number, event: SwiperAnimationEvent): Record<string, number> { let nextIndex = index; // 滑动范围限制,Swiper不可循环,Scroll保持不可循环 if (index > 0 && event.currentOffset > 0) { nextIndex--; // 左滑 } else if (index < this.data.totalCount() - 1 && event.currentOffset < 0) { nextIndex++; // 右滑 } this.nextIndicatorIndex = nextIndex; // 获取当前tabbar的属性信息 let indexInfo = this.getTextInfo(index); // 获取目标tabbar的属性信息 let nextIndexInfo = this.getTextInfo(nextIndex); // 滑动页面超过一半时页面切换 this.swipeRatio = Math.abs(event.currentOffset / this.tabsWidth); let currentIndex = this.swipeRatio > 0.5 ? nextIndex : index; // 页面滑动超过一半,tabBar切换到下一页。 let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * this.swipeRatio; let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * this.swipeRatio; this.indicatorIndex = currentIndex; return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }; } private scrollIntoView(currentIndex: number): void { const indexInfo = this.getTextInfo(currentIndex); let tabPositionLeft = indexInfo.left; let tabWidth = indexInfo.width; // 获取屏幕宽度,单位vp const screenWidth = this.getDisplayWidth(); const currentOffsetX: number = this.scroller.currentOffset().xOffset; //当前滚动的偏移量 this.scroller.scrollTo({ // 将tabbar可滑动时候定位在正中间 xOffset: currentOffsetX + tabPositionLeft - screenWidth / 2 + tabWidth / 2, yOffset: 0, animation: { duration: this.animationDuration, curve: this.animationCurve, // 动画曲线 } }); this.underlineScrollAuto(this.animationDuration, currentIndex); } private startAnimateTo(duration: number, marginLeft: number, width: number): void { animateTo({ duration: duration, // 动画时长 curve: this.animationCurve, // 动画曲线 onFinish: () => { console.info('play end') } }, () => { this.indicatorMarginLeft = marginLeft; this.indicatorWidth = width; }) } // 下划线动画 private underlineScrollAuto(duration: number, index: number): void { let indexInfo = this.getTextInfo(index); this.startAnimateTo(duration, indexInfo.left, indexInfo.width); } getStringFromResource(source: Resource): string { try { getContext(this).resourceManager.getStringSync(source.id); let str = getContext(this).resourceManager.getStringSync(source.id); return str } catch (error) { let code = (error as BusinessError).code; let message = (error as BusinessError).message; console.error(`getStringSync failed, error code: ${code}, message: ${message}.`); return '' } } build() { Column() { // tabbar Row() { Column() { Scroll() { Column() { Scroll(this.scroller) { Row() { ForEach(this.arr, (item: string, index: number) => { Column() { Text(item) .fontSize(16) .borderRadius(5) .fontColor(this.indicatorIndex === index ? Color.Red : Color.Black) .fontWeight(this.indicatorIndex === index ? FontWeight.Bold : FontWeight.Normal) .margin({ left: this.initialTabMargin, right: this.initialTabMargin }) .id(index.toString()) .onAreaChange((oldValue: Area, newValue: Area) => { if (this.indicatorIndex === index && (this.indicatorMarginLeft === 0 || this.indicatorWidth === 0)) { if (newValue.globalPosition.x != undefined) { let positionX = Number.parseFloat(newValue.globalPosition.x.toString()); this.indicatorMarginLeft = Number.isNaN(positionX) ? 0 : positionX; } let width = Number.parseFloat(newValue.width.toString()); this.indicatorWidth = Number.isNaN(width) ? 0 : width; } }) .onClick(() => { this.indicatorIndex = index; // 点击tabbar下划线动效 this.underlineScrollAuto(this.animationDuration, index); this.scrollIntoView(index); // 跟tabs进行联动 this.controller.changeIndex(index) }) } .width(this.textLength[index] * 28) }, (item: string) => item) } .height(32) } .width('100%') .scrollable(ScrollDirection.Horizontal) .scrollBar(BarState.Off) .edgeEffect(EdgeEffect.None) // 滚动事件回调, 返回滚动时水平、竖直方向偏移量 .onScroll((xOffset: number, yOffset: number) => { console.info(xOffset + ' ' + yOffset) this.indicatorMarginLeft -= xOffset; }) // 滚动停止事件回调 .onScrollStop(() => { console.info('Scroll Stop') this.underlineScrollAuto(0, this.indicatorIndex); }) Column() .width(this.indicatorWidth) .height(2) .borderRadius(2) .backgroundColor(Color.Red) .alignSelf(ItemAlign.Start) .margin({ left: this.indicatorMarginLeft, top: 5 }) } } } .width('100%') .margin({ top: 15, bottom: 10 }) } Tabs({ controller: this.controller }) { LazyForEach(this.data, (item: number, index: number) => { TabContent() { List({ space: 10 }) { ListItem() { Text(item.toString()) } } .padding({ left: 10, right: 10 }) .width("100%") .height('95%') } .onAreaChange((oldValue: Area, newValue: Area) => { let width = Number.parseFloat(newValue.width.toString()); this.tabsWidth = Number.isNaN(width) ? 0 : width; }) }, (item: string) => item) } .onChange((index: number) => { this.currentIndex = index; }) .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => { // 切换动画开始时触发该回调。下划线跟着页面一起滑动,同时宽度渐变。 this.indicatorIndex = targetIndex; this.underlineScrollAuto(this.animationDuration, targetIndex); }) .onAnimationEnd((index: number, event: TabsAnimationEvent) => { // 切换动画结束时触发该回调。下划线动画停止。 let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event); this.startAnimateTo(0, currentIndicatorInfo.left, currentIndicatorInfo.width); this.scrollIntoView(index); }) .onGestureSwipe((index: number, event: TabsAnimationEvent) => { // 在页面跟手滑动过程中,逐帧触发该回调。 let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event); this.indicatorIndex = currentIndicatorInfo.index; //当前页签index this.indicatorMarginLeft = currentIndicatorInfo.left; //当前页签距离左边margin this.indicatorWidth = currentIndicatorInfo.width; //当前页签宽度 }) } .width('100%') } }