HarmonyOS鸿蒙Next中请教下Scroller滚动问题

HarmonyOS鸿蒙Next中请教下Scroller滚动问题 我的需求是Scroller里面有很长的文本,现在要求竖屏滚动和暂停

下面是我的代码,能实现这个要求。但是滚动的时候有时候会时快时慢的感觉,当时我在鸿蒙5.0系统测速没什么问题,客户用鸿蒙6.0系统 会卡卡的,应该是鸿蒙6.0帧率更高

如下是我的代码,有什么可以优化吗?我用的倒计时滚动,是不是倒计时不精准导致的?

private scroller: Scroller = new Scroller()
private timer: number = -1
@State speed: number = 2 // 滑动的速度
@State isScrolling: boolean = false // 当前是否在滚动
private reachedBottom: boolean = false
.scrollBar(BarState.Off)
.onScroll((xOffset: number, yOffset: number) => { 
  this.reachedBottom = this.scroller.isAtEnd()
  if (this.reachedBottom) {
    this.stopAutoScroll()
    this.isScrolling = false
  }
})
// 自动滚动逻辑
private startAutoScroll() {
  if (this.timer !== -1) {
    return
  }
  this.timer = setInterval(() => {
    if (this.reachedBottom) {
      this.stopAutoScroll()
      this.isScrolling = false
      return
    }
    let pos = this.scroller.currentOffset()
    this.scroller.scrollTo({
      xOffset: 0,
      yOffset: pos.yOffset + (this.speed  * 0.15)
    })
  }, 18)
}

private stopAutoScroll() {
  if (this.timer !== -1) {
    clearInterval(this.timer)
    this.timer = -1
  }
}

更多关于HarmonyOS鸿蒙Next中请教下Scroller滚动问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

开发者您好,使用6.0.0.120版本手机在API20下运行如下示例代码未复现问题,请提供一下能复现问题的最小demo及当前开发工具版本(Help->About DevEco Studio)、运行时API版本(File->Project Structure->Project->Basic Info->Compatible SDK)、手机系统版本信息 (设置->关于手机),感谢您的理解与支持。

示例代码:

@Entry
@Component
struct ScrollPage {

  @State message: string = '超长文本超长文本超长文本';
  @State yOffset: number = 0;
  @State flag: boolean = true;
  scroller: Scroller = new Scroller();
  private timer: number = -1
  @State speed: number = 2 // 滑动的速度
  @State isScrolling: boolean = false // 当前是否在滚动
  private reachedBottom: boolean = false
  
  // 自动滚动逻辑
  private startAutoScroll() {
    if (this.timer !== -1) {
      return
    }
    this.timer = setInterval(() => {
      if (this.reachedBottom) {
        this.stopAutoScroll()
        this.isScrolling = false
        return
      }
      let pos = this.scroller.currentOffset()
      this.scroller.scrollTo({
        xOffset: 0,
        yOffset: pos.yOffset + (this.speed  * 0.15)
      })
    }, 18)
  }

  private stopAutoScroll() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
      this.timer = -1
    }
  }

  aboutToAppear(): void {
    // 设置超长文本
    for(let i = 0; i < 2000; i++) {
      this.message += '超长文本'
    }
  }

  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      Button('开始')
        .onClick(()=>{
          this.startAutoScroll();
        })
        .zIndex(5)
      Button('停止')
        .onClick(()=>{
          this.stopAutoScroll();
        }).margin({"left":200})
        .zIndex(5)
      Scroll(this.scroller) {
        Column() {
          Text(this.message)
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .onScroll((xOffset: number, yOffset: number) => {
        this.reachedBottom = this.scroller.isAtEnd()
        if (this.reachedBottom) {
          this.stopAutoScroll()
          this.isScrolling = false
        }
      })

    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xDCDCDC)
  }
}

更多关于HarmonyOS鸿蒙Next中请教下Scroller滚动问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


刷新率不匹配,您使用的是 18ms 的间隔,这相当于约 55 FPS。鸿蒙 6.0 流畅模式下通常是 90Hz (11.1ms/帧) 或 120Hz (8.3ms/帧),甚至是 60Hz (16.6ms/帧)。当您的 18ms 定时器触发滚动的瞬间,屏幕可能刚好刷新了一半(没对齐 Vsync 信号),或者在 120Hz 屏幕上,您每隔 2.x 帧才动一次。这会导致肉眼可见的“抖动”或“快慢不均”, 使用 displaySync 模块。它会在每一帧屏幕刷新时回调,保证您的滚动逻辑与屏幕刷新完全同步(60Hz时触发60次,120Hz时触发120次),从而实现极致丝滑。

鸿蒙Next中Scroller滚动问题

可通过Scroller组件实现。在ArkTS中,使用Scroller的scrollToscrollBy方法控制滚动位置。需在布局中设置滚动区域,并绑定Scroller实例。滚动事件通过onScroll回调处理。注意设置滚动方向和边界值。

在HarmonyOS Next中,使用setInterval进行滚动控制确实存在精度问题,尤其是在高刷新率设备上(如鸿蒙6.0的120Hz屏幕)。setInterval无法保证精确的时间间隔,且其回调执行可能被主线程任务阻塞,导致滚动卡顿或速度不均。

推荐使用requestAnimationFrame替代setInterval,它能确保回调在每一帧渲染前执行,实现更流畅的动画效果。以下是优化后的核心代码示例:

private animationId: number = -1;
private lastTimestamp: number = 0;
private readonly FRAME_DURATION: number = 16; // 约60Hz下的帧时间

private startAutoScroll() {
  if (this.animationId !== -1) return;
  
  const scrollStep = () => {
    if (this.reachedBottom) {
      this.stopAutoScroll();
      this.isScrolling = false;
      return;
    }
    
    const currentPos = this.scroller.currentOffset();
    this.scroller.scrollTo({
      xOffset: 0,
      yOffset: currentPos.yOffset + (this.speed * 0.15)
    });
    
    this.animationId = requestAnimationFrame(scrollStep);
  };
  
  this.animationId = requestAnimationFrame(scrollStep);
}

private stopAutoScroll() {
  if (this.animationId !== -1) {
    cancelAnimationFrame(this.animationId);
    this.animationId = -1;
  }
}

同时,建议在onScroll回调中减少不必要的状态更新,避免频繁的UI重绘:

.onScroll((xOffset: number, yOffset: number) => { 
  this.reachedBottom = this.scroller.isAtEnd();
  // 移除此处的stopAutoScroll调用,改为在scrollStep中检查
})

这样修改后,滚动动画将与设备刷新率同步,在鸿蒙6.0等高帧率设备上也能保持平滑。

回到顶部