HarmonyOS鸿蒙Next中如何解决父组件的fling效果无法被子组件继承滚动问题

HarmonyOS鸿蒙Next中如何解决父组件的fling效果无法被子组件继承滚动问题

【问题现象】

父组件为Scroll组件,其子组件包含一个List组件,且位于Scroll组件底部。当滑动触点在Scroll组件内容区域进行快速滑动,滚动到底部List组件位置的时,位于底部的List组件无法继承父组件的剩余滚动速度,进行完整的惯性滚动。

滑动前如下图:

点击放大

滑动后:

点击放大

【背景知识】

Fling效果,又被称为惯性滚动,是在APP很常见的一种交互设计。对于这种常见的效果,HarmonyOS原生可滚动组件其实是有着很好的实现与支持的。

那么首先,让我们先来熟悉下HarmonyOS几个基础的可滚动容器组件的使用。

重点关注其中的以下几个方法:

edgeEffect

用于设置边缘滑动效果。

List组件默认为EdgeEffect.Spring(惯性滚动),Scroll组件默认为EdgeEffect.None(无效果)。

nestedScroll

用于设置向前向后两个方向上的嵌套滚动模式,实现与父组件的滚动联动。

onScrollFrameBegin

每帧开始滚动时触发,事件参数传入即将发生的滚动量,事件处理函数中可根据应用场景计算实际需要的滚动量并作为事件处理函数的返回值返回,Scroll将按照返回值的实际滚动量进行滚动。

【定位思路】

如果只是单纯的进行可滚动容器组件的嵌套,那么当父级Scroll组件滚动到底部List组件时,子级List组件会视为Scroll组件的一部分,跟随Scroll组件进行整体滚动。

如果我们希望子组件能够继承父组件的惯性滚动的剩余速度,那么可以按照如下思路实现:

  1. 首先设置组件的边缘滑动效果为惯性滚动, 即EdgeEffect.Spring
  2. 在父级组件onScrollStart,onScrollStop事件中记录父级组件的滚动状态;
  3. 在父级组件onTouch事件中记录上下滑动的方向;
  4. 在父级组件onScrollFrameBegin进行剩余滚动量的计算,并通过子组件Scroller控制器,按照剩余滚动量计算值视情况进行滚动,同时返回最终计算后父级组件的实际滚动量。

【解决方案】

根据定位思路,fling效果继承滚动的主要实现代码如下,代码中附加一部分顶部tabbar吸顶逻辑:

@Component
struct StickyNestedScroll {
  @State message: string = 'Hello World'
  @State arr: number[] = []
  private touchDown: boolean = false;
  private listTouchDown: boolean = false;
  private scrolling: boolean = false;
  private scroller: Scroller = new Scroller()
  private listScroller: Scroller = new Scroller()
  private CONTENT_HEIGHT = 400;

  @Styles
  listCard() {
    .backgroundColor(Color.White)
    .height(72)
    .width("100%")
    .borderRadius(12)
  }

  aboutToAppear() {
    for (let i = 0; i < 30; i++) {
      this.arr.push(i)
    }
  }

  build() {
    Scroll(this.scroller) {
      Column() {
        Text("Scroll Area")
          .width("100%")
          .height(this.CONTENT_HEIGHT)
          .backgroundColor('# 0080DC')
          .textAlign(TextAlign.Center)
        Tabs({ barPosition: BarPosition.Start }) {
          TabContent() {
            List({ space: 10, scroller: this.listScroller }) {
              ForEach(this.arr, (item: number) => {
                ListItem() {
                  Text("item" + item)
                    .fontSize(16)
                }.listCard()
              }, (item: number) => item.toString())
            }.width("100%")
            .edgeEffect(EdgeEffect.Spring)
            .nestedScroll({
              scrollForward: NestedScrollMode.PARENT_FIRST,
              scrollBackward: NestedScrollMode.SELF_FIRST
            })
            .onTouch((event: TouchEvent) => {
              if (event.type == TouchType.Down) {
                this.listTouchDown = true;
              } else if (event.type == TouchType.Up) {
                this.listTouchDown = false;
              }
            })
          }.tabBar("Tab One")

          TabContent() {
          }.tabBar("Tab Two")
        }
        .vertical(false)
        .height("100%")
      }.width("100%")
    }
    .onTouch((event: TouchEvent) => {
      if (event.type == TouchType.Down) {
        this.touchDown = true;
      } else if (event.type == TouchType.Up) {
        this.touchDown = false;
      }
    })
    .onScrollFrameBegin((offset: number, state: ScrollState) => {
      if (this.scrolling && offset > 0) {
        let yOffset: number = this.scroller.currentOffset().yOffset
        if (yOffset >= this.CONTENT_HEIGHT) {
          this.listScroller.scrollBy(0, offset)
          return { offsetRemain: 0 }
        } else if (yOffset + offset > this.CONTENT_HEIGHT) {
          this.listScroller.scrollBy(0, yOffset + offset - this.CONTENT_HEIGHT)
          return { offsetRemain: this.CONTENT_HEIGHT - yOffset }
        }
      }
      return { offsetRemain: offset }
    })
    .onScrollStart(() => {
      if (this.touchDown && !this.listTouchDown) {
        this.scrolling = true;
      }
    })
    .onScrollStop(() => {
      this.scrolling = false;
    })
    .edgeEffect(EdgeEffect.Spring)
    .backgroundColor('# DCDCDC')
    .scrollBar(BarState.Off)
    .width('100%')
    .height('100%')
  }
}

实现后滑动结果如下:

点击放大

【总结】

  1. 通过父组件的onScrollFrameBegin事件,我们可以明确的知道组件即将发生的滑动量,并根据需要计算实际需要的滑动量,作为返回值返回;
  2. 再结合onTouch,onScrollStart等事件,收集用户具体的滑动行为;
  3. 最后通过滚动组件的Scroll控制器,触发我们想要的操作。

整体实现逻辑并不复杂,前提是得理清思路,对可滚动组件的基本属性和事件需要有一定熟悉度。


更多关于HarmonyOS鸿蒙Next中如何解决父组件的fling效果无法被子组件继承滚动问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中如何解决父组件的fling效果无法被子组件继承滚动问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,解决父组件的fling效果无法被子组件继承滚动问题

可以通过以下步骤实现:

  1. 设置子组件的边缘滑动效果为惯性滚动 EdgeEffect.Spring
  2. 在父组件的 onScrollStartonScrollStop 事件中记录父组件的滚动状态。
  3. 在父组件的 onTouch 事件中记录上下滑动的方向。
  4. 在父组件的 onScrollFrameBegin 事件中进行剩余滚动量的计算,并通过子组件的 Scroller 控制器,按照剩余滚动量计算值视情况进行滚动,同时返回最终计算后父组件的实际滚动量。

具体实现代码如下:

@Component
struct StickyNestedScroll {
  @State message: string = 'Hello World'
  @State arr: number[] = []
  private touchDown: boolean = false;
  private listTouchDown: boolean = false;
  private scrolling: boolean = false;
  private scroller: Scroller = new Scroller()
  private listScroller: Scroller = new Scroller()
  private CONTENT_HEIGHT = 400;

  @Styles
  listCard() {
    .backgroundColor(Color.White)
    .height(72)
    .width("100%")
    .borderRadius(12)
  }

  aboutToAppear() {
    for (let i = 0; i < 30; i++) {
      this.arr.push(i)
    }
  }

  build() {
    Scroll(this.scroller) {
      Column() {
        Text("Scroll Area")
          .width("100%")
          .height(this.CONTENT_HEIGHT)
          .backgroundColor('#0080DC')
          .textAlign(TextAlign.Center)
        Tabs({ barPosition: BarPosition.Start }) {
          TabContent() {
            List({ space: 10, scroller: this.listScroller }) {
              ForEach(this.arr, (item: number) => {
                ListItem() {
                  Text("item" + item)
                    .fontSize(16)
                }.listCard()
              }, (item: number) => item.toString())
            }.width("100%")
            .edgeEffect(EdgeEffect.Spring)
            .nestedScroll({
              scrollForward: NestedScrollMode.PARENT_FIRST,
              scrollBackward: NestedScrollMode.SELF_FIRST
            })
            .onTouch((event: TouchEvent) => {
              if (event.type == TouchType.Down) {
                this.listTouchDown = true;
              } else if (event.type == TouchType.Up) {
                this.listTouchDown = false;
              }
            })
          }.tabBar("Tab One")

          TabContent() {
          }.tabBar("Tab Two")
        }
        .vertical(false)
        .height("100%")
      }.width("100%")
    }
    .onTouch((event: TouchEvent) => {
      if (event.type == TouchType.Down) {
        this.touchDown = true;
      } else if (event.type == TouchType.Up) {
        this.touchDown = false;
      }
    })
    .onScrollFrameBegin((offset: number, state: ScrollState) => {
      if (this.scrolling && offset > 0) {
        let yOffset: number = this.scroller.currentOffset().yOffset
        if (yOffset >= this.CONTENT_HEIGHT) {
          this.listScroller.scrollBy(0, offset)
          return { offsetRemain: 0 }
        } else if (yOffset + offset > this.CONTENT_HEIGHT) {
          this.listScroller.scrollBy(0, yOffset + offset - this.CONTENT_HEIGHT)
          return { offsetRemain: this.CONTENT_HEIGHT - yOffset }
        }
      }
      return { offsetRemain: offset }
    })
    .onScrollStart(() => {
      if (this.touchDown && !this.listTouchDown) {
        this.scrolling = true;
      }
    })
    .onScrollStop(() => {
      this.scrolling = false;
    })
    .edgeEffect(EdgeEffect.Spring)
    .backgroundColor('#DCDCDC')
    .scrollBar(BarState.Off)
    .width('100%')
    .height('100%')
  }
}

通过上述代码,子组件可以继承父组件的惯性滚动效果,实现平滑的滚动过渡。

回到顶部