HarmonyOS鸿蒙Next中滑动切换(Tabs)不支持获取偏移量吗,实现类似Android ViewPager效果,可以获取偏移量,有没有其他的方案

HarmonyOS鸿蒙Next中滑动切换(Tabs)不支持获取偏移量吗,实现类似Android ViewPager效果,可以获取偏移量,有没有其他的方案

期望: 横向切换页面的场景下,希望 tabBar 跟随页面滑动,能自定义交互效果。

问题:

  1. Tabs 滑动切换不支持获取偏移量吗,实现 android 类似 ViewPager 效果,可以获取偏移量,有没有其他的方案。

  2. 两个 Tabs 绑定,实现 tabBar 联动的效果,也要用到获取实时偏移位置。需求: 滑动页面,tab 标题跟随滑动,实现放大缩小效果。需要自定义,但自定义需要知道滑动的偏移量(或者百分比)

鸿蒙:

  1. 尝试 Tabs ,发现没有偏移监听;

  2. 使用 Swiper,切换页面,如果第一个页面竖直滑动到中间,这是横向切换到第二个页面,发现第二个页面也有偏移量了,跟第一个页面一样,不符合预期,应该偏移量为0;(Tabs的效果可以,但Tabs没有偏移监听)


更多关于HarmonyOS鸿蒙Next中滑动切换(Tabs)不支持获取偏移量吗,实现类似Android ViewPager效果,可以获取偏移量,有没有其他的方案的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

可以设置Tabs组件的barHeight为0隐藏tabs组件自带的tabbar,再使用Row组件+自定义tab页签的方式实现tabbar,将一个高度为2的column组件作为下划线,使用stack堆叠在tabbar下,这个下划线的width和相对左侧的margin通过@State修饰,在页签切换时,将当前页签的宽度,相对左侧的margin分别赋给下划线对应的属性,通过onGestureSwipe监听tab的滑动手势,逐帧更新下划线相对左侧的margin,使用onScroll监听自定义tabbar的滑动,逐帧更新下划线相对左侧的margin,通过onAnimationStart回调切换动画开始时触发该回调,触发下划线跟着页面一起滑动的回调

//示例代码 第一部分
@Entry
@Component
struct tabTest {
  [@State](/user/State) tabArray: Array<number> = [0, 1, 2, 3,4]
  [@State](/user/State) focusIndex: number = 0
  [@State](/user/State) pre: number = 0
  [@State](/user/State) index: number = 0
  private controller: TabsController = new TabsController()
  [@State](/user/State) test: boolean = false
  [@State](/user/State) animationDuration: number = 300
  [@State](/user/State) indicatorLeftMargin: number = 0
  [@State](/user/State) indicatorWidth: number = 0
  private tabsWidth: number = 0
  private tabWidth: number = 0;
  private scrollerForScroll: Scroller = new Scroller()
  // 单独的页签
  @Builder
  Tab(tabName: string, tabItem: number, tabIndex: number) {
    Row({ space: 20 }) {
      Text(tabName)
        .fontSize(18)
        .fontColor(tabIndex === this.focusIndex ? Color.Blue :Color.Black)
        .id(tabIndex.toString())
        .onAreaChange((oldValue: Area,newValue: Area) => {
          if (this.focusIndex === tabIndex && (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.tabWidth = Number.isNaN(width) ? 0 : width
            this.indicatorWidth = this.tabWidth
          }
        })
    }
    .justifyContent(FlexAlign.Center)
    .constraintSize({ minWidth: 35 })
    .width(100)
    .height(30)
    .onClick(() => {
      this.controller.changeIndex(tabIndex)
      this.focusIndex = tabIndex
    })
    .backgroundColor("#ffb7b7b7")
  }
  //示例代码 第3部分
  .alignItems(HorizontalAlign.Start)
  .width('100%')
  Column()
  .height(2)
  .width(this.indicatorWidth)
  .margin({ left: this.indicatorLeftMargin, top:30})
  .backgroundColor(Color.Blue)
  Column()
  .height(10)
  .width("20%")
  .margin({ left: '80%', top:28})
  .backgroundColor("#ffb7b7b7")
}
.height(40)
  .width('100%')
  .backgroundColor("#ffb7b7b7")
//tabs
Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
  ForEach(this.tabArray, (item: number, index: number) => {
    TabContent() {
      Text('我是页面 '+ item + " 的内容")
        .height(300)
        .width('100%')
        .fontSize(30)
    }
    .backgroundColor(Color.White)
  }, (item: string) => item)
}
.onAreaChange((oldValue: Area,newValue: Area)=> {
  let width = Number.parseFloat(newValue.width.toString())
  this.tabsWidth = Number.isNaN(width) ? 0 : width
})
.width('100%')
.barHeight(0)
.animationDuration(100)
.onChange((index: number) => {
  console.log('foo change')
  this.focusIndex = index
  this.scrollerForScroll.scrollToIndex(index-1,true)
})
//示例代码 第4部分
.onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
  // 切换动画开始时触发该回调。下划线跟着页面一起滑动
  this.focusIndex = 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.focusIndex = currentIndicatorInfo.index
  this.indicatorLeftMargin = currentIndicatorInfo.left
  this.tabWidth = currentIndicatorInfo.width
  this.indicatorWidth = currentIndicatorInfo.width
})
}
.height('100%')
}
private getTextInfo(index: number): Record<string, number> {
  let strJson = getInspectorByKey(index.toString())
  try {
  let obj: Record<string, string> = JSON.parse(strJson)
  let rectInfo: number[][] = JSON.parse('[' + obj.$rect + ']')
  return { 'left': px2vp(rectInfo[0][0]), 'width': px2vp(rectInfo[1][0] - rectInfo[0][0]) }
} catch (error) {
  return { 'left': 0, 'width': 0 }
}
}
//示例代码 第5部分
private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
  let nextIndex = index
  if (index > 0 && event.currentOffset > 0) {
  nextIndex--
} else if (index < 4 && 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.tabWidth = width
    this.indicatorWidth = width
  })
}
}
//示例代码 第2部分
@Builder
textTest(textName:string){
  Row({ space: 20 }) {
    Text(textName).fontSize(18)
  }
  .justifyContent(FlexAlign.Center)
  .constraintSize({ minWidth: 35 })
  .height(30)
  .backgroundColor("#ffb7b7b7")
}
build() {
  Column() {
    Stack({ alignContent: Alignment.TopStart }) {
      Column() {
        // 页签
        Row({ space: 8 }) {
          List({ space: 20, initialIndex: 0, scroller: this.scrollerForScroll }) {
            ForEach(this.tabArray, (item: number, index: number) => {
              ListItem() {
                this.Tab("页签 "+item, item, index)
              }
            }, (item: string) => item)
          }
          .listDirection(Axis.Horizontal)
          .height(30)
          .width('80%')
          .friction(0.6)
          .alignListItem(ListItemAlign.Start)
          .scrollBar(BarState.Off)
          .width('80%')
          .backgroundColor("#ffb7b7b7")
          .onScroll((xOffset: number, yOffset: number) => {
            this.indicatorLeftMargin -= xOffset
          })
          this.textTest('更多')
        }
        .alignItems(VerticalAlign.Bottom)
        .width('100%')
        .backgroundColor("#ffb7b7b7")
      }
    }
  }
}

更多关于HarmonyOS鸿蒙Next中滑动切换(Tabs)不支持获取偏移量吗,实现类似Android ViewPager效果,可以获取偏移量,有没有其他的方案的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,Tabs组件默认不支持直接获取滑动偏移量。如果你需要实现类似Android ViewPager的效果,可以通过自定义组件或使用Scroll组件来实现。Scroll组件提供了onScroll事件,可以监听滚动位置,从而获取偏移量。你可以结合Scroll和Tabs组件,通过监听onScroll事件来计算当前页面的偏移量,并同步更新Tabs的选中状态。

在HarmonyOS鸿蒙Next中,Tabs组件本身确实不直接提供获取偏移量的功能。要实现类似Android ViewPager的效果,并且获取偏移量,你可以考虑以下方案:

  1. 自定义滑动容器:使用ScrollViewPageSlider等组件,结合手势事件监听,手动计算偏移量。
  2. PageSlider组件:PageSlider支持滑动切换页面,并且可以通过onPageSelectedonPageScrollStateChanged等回调方法来获取页面切换的状态和位置信息。
  3. 第三方库:如果有现成的第三方库支持类似功能,可以考虑集成到项目中。

通过这些方式,你可以在HarmonyOS中实现类似ViewPager的效果,并获取滑动偏移量。

回到顶部