张善主

feat(tab):顶部tab切换同步

@@ -58,6 +58,11 @@ export struct TopNavigationComponent { @@ -58,6 +58,11 @@ export struct TopNavigationComponent {
58 // 当前底导index 58 // 当前底导index
59 @State navIndex: number = 0 59 @State navIndex: number = 0
60 60
  61 + @State animationDuration: number = 0
  62 + @State indicatorLeftMargin: number = 0
  63 + @State indicatorWidth: number = 0
  64 + private tabsWidth: number = 0
  65 +
61 topOrBottomNavChange() { 66 topOrBottomNavChange() {
62 if (this.currentBottomNavName === this.currentBottomNavInfo?.name) { 67 if (this.currentBottomNavName === this.currentBottomNavInfo?.name) {
63 this.setBarBackgroundColor() 68 this.setBarBackgroundColor()
@@ -162,6 +167,7 @@ export struct TopNavigationComponent { @@ -162,6 +167,7 @@ export struct TopNavigationComponent {
162 return item.name === '版面' 167 return item.name === '版面'
163 } 168 }
164 169
  170 +
165 build() { 171 build() {
166 Column() { 172 Column() {
167 // 顶部搜索、日报logo、早晚报 173 // 顶部搜索、日报logo、早晚报
@@ -237,9 +243,13 @@ export struct TopNavigationComponent { @@ -237,9 +243,13 @@ export struct TopNavigationComponent {
237 .barMode(BarMode.Scrollable) 243 .barMode(BarMode.Scrollable)
238 .vertical(false) 244 .vertical(false)
239 .barBackgroundColor(this.barBackgroundColor) 245 .barBackgroundColor(this.barBackgroundColor)
  246 + .onAreaChange((oldValue: Area,newValue: Area)=> {
  247 + let width = Number.parseFloat(newValue.width.toString())
  248 + this.tabsWidth = Number.isNaN(width) ? 0 : width
  249 + })
  250 + .animationDuration(this.animationDuration)
240 .onChange((index: number) => { 251 .onChange((index: number) => {
241 this.currentTopNavName = this._currentNavIndex === 0 ? this.myChannelList[index].name : this.topNavList[index].name 252 this.currentTopNavName = this._currentNavIndex === 0 ? this.myChannelList[index].name : this.topNavList[index].name
242 -  
243 Logger.info(TAG, `onChange index : ${index}`); 253 Logger.info(TAG, `onChange index : ${index}`);
244 if (!this.isBroadcast(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index]) && 254 if (!this.isBroadcast(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index]) &&
245 !this.isLayout(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index]) 255 !this.isLayout(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index])
@@ -256,6 +266,39 @@ export struct TopNavigationComponent { @@ -256,6 +266,39 @@ export struct TopNavigationComponent {
256 this.tabsController.changeIndex(this.currentTopNavSelectedIndex) 266 this.tabsController.changeIndex(this.currentTopNavSelectedIndex)
257 } 267 }
258 }) 268 })
  269 + .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
  270 + if (!this.isBroadcast(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index]) &&
  271 + !this.isLayout(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index])
  272 + ) {
  273 + return
  274 + }
  275 + this.currentTopNavSelectedIndex = targetIndex
  276 + // 切换动画开始时触发该回调。下划线跟着页面一起滑动,同时宽度渐变。
  277 + let targetIndexInfo = this.getTextInfo(targetIndex)
  278 + this.startAnimateTo(this.animationDuration, targetIndexInfo.left, targetIndexInfo.width)
  279 + })
  280 + .onAnimationEnd((index: number,event: TabsAnimationEvent) => {
  281 + if (!this.isBroadcast(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index]) &&
  282 + !this.isLayout(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index])
  283 + ) {
  284 + return
  285 + }
  286 + // 切换动画结束时触发该回调。下划线动画停止。
  287 + let currentIndicatorInfo = this.getCurrentIndicatorInfo(index,event)
  288 + this.startAnimateTo(0,currentIndicatorInfo.left,currentIndicatorInfo.width)
  289 + })
  290 + .onGestureSwipe((index: number,event: TabsAnimationEvent) => {
  291 + if (!this.isBroadcast(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index]) &&
  292 + !this.isLayout(this._currentNavIndex === 0 ? this.myChannelList[index] : this.topNavList[index])
  293 + ) {
  294 + return
  295 + }
  296 + // 在页面跟手滑动过程中,逐帧触发该回调。
  297 + let currentIndicatorInfo = this.getCurrentIndicatorInfo(index,event)
  298 + this.currentTopNavSelectedIndex = currentIndicatorInfo.index
  299 + this.indicatorLeftMargin = currentIndicatorInfo.left
  300 + this.indicatorWidth = currentIndicatorInfo.width
  301 + })
259 302
260 // 分类列表最右侧频道设置 303 // 分类列表最右侧频道设置
261 if (this._currentNavIndex === 0) { 304 if (this._currentNavIndex === 0) {
@@ -296,6 +339,17 @@ export struct TopNavigationComponent { @@ -296,6 +339,17 @@ export struct TopNavigationComponent {
296 .fontColor(this.getTopNavFontColor(item, index)) 339 .fontColor(this.getTopNavFontColor(item, index))
297 .padding({ top: $r('app.float.top_tab_item_padding_top'), bottom: $r('app.float.top_tab_item_padding_bottom') }) 340 .padding({ top: $r('app.float.top_tab_item_padding_top'), bottom: $r('app.float.top_tab_item_padding_bottom') })
298 .maxLines(this.MAX_LINE) 341 .maxLines(this.MAX_LINE)
  342 + .id(index.toString())
  343 + .onAreaChange((oldValue: Area,newValue: Area) => {
  344 + if (this.currentTopNavSelectedIndex === index && (this.indicatorLeftMargin === 0 || this.indicatorWidth === 0)){
  345 + if (newValue.position.x != undefined) {
  346 + let positionX = Number.parseFloat(newValue.position.x.toString())
  347 + this.indicatorLeftMargin = Number.isNaN(positionX) ? 0 : positionX
  348 + }
  349 + let width = Number.parseFloat(newValue.width.toString())
  350 + this.indicatorWidth = Number.isNaN(width) ? 0 : width
  351 + }
  352 + })
299 // .backgroundImage(this.currentTopNavSelectedIndex === index ? item.iconCUrl : item.iconUrl) 353 // .backgroundImage(this.currentTopNavSelectedIndex === index ? item.iconCUrl : item.iconUrl)
300 if (this.currentTopNavSelectedIndex === index) { 354 if (this.currentTopNavSelectedIndex === index) {
301 Row() 355 Row()
@@ -418,4 +472,46 @@ export struct TopNavigationComponent { @@ -418,4 +472,46 @@ export struct TopNavigationComponent {
418 } 472 }
419 return null 473 return null
420 } 474 }
  475 +
  476 + private getTextInfo(index: number): Record<string, number> {
  477 + let strJson = getInspectorByKey(index.toString())
  478 + try {
  479 + let obj: Record<string, string> = JSON.parse(strJson)
  480 + let rectInfo: number[][] = JSON.parse('[' + obj.$rect + ']')
  481 + return { 'left': px2vp(rectInfo[0][0]), 'width': px2vp(rectInfo[1][0] - rectInfo[0][0]) }
  482 + } catch (error) {
  483 + return { 'left': 0, 'width': 0 }
  484 + }
  485 + }
  486 +
  487 + private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
  488 + let nextIndex = index
  489 + if (index > 0 && event.currentOffset > 0) {
  490 + nextIndex--
  491 + } else if (index < 3 && event.currentOffset < 0) {
  492 + nextIndex++
  493 + }
  494 + let indexInfo = this.getTextInfo(index)
  495 + let nextIndexInfo = this.getTextInfo(nextIndex)
  496 + let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth)
  497 + let currentIndex = swipeRatio > 0.5 ? nextIndex : index // 页面滑动超过一半,tabBar切换到下一页。
  498 + let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * swipeRatio
  499 + let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * swipeRatio
  500 + return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }
  501 + }
  502 +
  503 + private startAnimateTo(duration: number, leftMargin: number, width: number) {
  504 + animateTo({
  505 + duration: duration, // 动画时长
  506 + curve: Curve.Linear, // 动画曲线
  507 + iterations: 1, // 播放次数
  508 + playMode: PlayMode.Normal, // 动画模式
  509 + onFinish: () => {
  510 + console.info('play end')
  511 + }
  512 + }, () => {
  513 + this.indicatorLeftMargin = leftMargin
  514 + this.indicatorWidth = width
  515 + })
  516 + }
421 } 517 }