HarmonyOS鸿蒙Next中Scroll如何添加如下这种动画效果:

发布于 1周前 作者 songsunli 来自 鸿蒙OS

HarmonyOS鸿蒙Next中Scroll如何添加如下这种动画效果: 代码如下

  1. 进入页面首次从模块三展示,
  2. scroll内容右滑50vp后,控制scroller滑动到模块一,这期间有一个动画效果,
  3. 在模块一这里滑动,使得scroll中的内容左滑50vp,再回到模块三,这期间也有一个动画效果。

请问这种效果如何实现?

@Entry
@Component
struct RefreshPage {
  scroller: Scroller = new Scroller()

  build() {
    Column {
      Scroll(this.scroller) {
        Row({ space: 10 }) {
          Text('模块一').textBor()
          Text('模块二').textBor()
          Text('模块三').textBor()
          Text('模块四').textBor()
          Text('模块五').textBor()
          Text('模块六').textBor()
          Text('模块七').textBor()
          Text('模块八').textBor()
          Text('模块九').textBor()
        }
      }
      .width('100%')
      .height(100)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .onAppear(() => {
        this.scroller.scrollTo({ xOffset: 220, yOffset: 0 })
      })
    }
  }
}

@Extend(Text)
function textBor() {
  .width(100).height('80%').textAlign(TextAlign.Center).border({ width: 1, color: '#FF5500' })
}

更多关于HarmonyOS鸿蒙Next中Scroll如何添加如下这种动画效果:的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

感谢分享

更多关于HarmonyOS鸿蒙Next中Scroll如何添加如下这种动画效果:的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


最终版本(应该是没有瑕疵了):

@Entry
@Component
struct RefreshPage {
  scroller: Scroller = new Scroller()
  @State isShow: boolean = false

  build() {
    Column() {
      Scroll(this.scroller) {
        Row({ space: 10 }) {
          Row({ space: 10 }) {
            Text('模块一').textBor()
            Text('模块二').textBor()
          }
          .onTouch((event: TouchEvent) => {

          })
          .onVisibleAreaChange([0.2, 0.8], (isVisible: boolean, currentRatio: number) => {
            if (isVisible) {
              this.isShow = true
              this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 800, curve: Curve.FastOutLinearIn, canOverScroll: false } })
            } else {
              this.isShow = false
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 800, curve: Curve.FastOutLinearIn, canOverScroll: false } })
            }
          })

          Text('模块三').textBor()
          Text('模块四').textBor()
          Text('模块五').textBor()
          Text('模块六').textBor()
          Text('模块七').textBor()
          Text('模块八').textBor()
          Text('模块九').textBor()
        }
      }
      .width('100%')
      .height(100)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .onAppear(() => {
        this.scroller.scrollTo({ xOffset: 220, yOffset: 0 })
      })
      .onScrollStop(() => {
        if (this.scroller.currentOffset().xOffset <= 220 && !this.isShow) {
          this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 800, curve: Curve.FastOutLinearIn, canOverScroll: false } })
        } else if (this.scroller.currentOffset().xOffset <= 220 && this.isShow) {
          this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 800, curve: Curve.FastOutLinearIn, canOverScroll: false } })
        }
      })
    }
  }
}

@Extend(Text)
function textBor() {
  .width(100).height('80%').textAlign(TextAlign.Center).border({ width: 1, color: '#FF5500' })
}

解决:内容滑动到最右边后,左滑一点距离,就会自动滑到模块三,

新问题:在模块三这里,左滑一定距离,不松开手指,再右滑一定距离,模块二会漏出来一块,

期望:模块一和模块二要么完全展示,要么全不展示

@Entry
@Component
struct RefreshPage {
  scroller: Scroller = new Scroller()
  @State isShow: boolean = false
  @State scrollDirection: number = -1

  build() {
    Column() {
      Scroll(this.scroller) {
        Row({ space: 10 }) {
          Row({ space: 10 }) {
            Text('模块一').textBor
            Text('模块二').textBor
          }
          .onTouch((event: TouchEvent) => {

          })
          .onVisibleAreaChange([0.3, 0.7], (isVisible: boolean, currentRatio: number) => {
            if (isVisible) {
              this.isShow = true
              this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            } else {
              this.isShow = false
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            }
          })

          Text('模块三').textBor
          Text('模块四').textBor
          Text('模块五').textBor
          Text('模块六').textBor
          Text('模块七').textBor
          Text('模块八').textBor
          Text('模块九').textBor
        }
      }
      .width('100%')
      .height(100)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .onAppear(() => {
        this.scroller.scrollTo({ xOffset: 220, yOffset: 0 })
      })
      .onWillScroll((x: number, y: number, scrollState: ScrollState, scrollSource: ScrollSource) => {
        if (x > 0) {
          this.scrollDirection = 1
        } else {
          this.scrollDirection = 2
        }
      })
      .onTouch((event: TouchEvent) => {
        if (event.type == TouchType.Up) { // 手指抬起时判断
          if (this.isShow) {
            this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
          } else {
            if (this.scrollDirection == 2 && this.scroller.currentOffset().xOffset <= 220) {
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            }
          }
        }
      })
    }

  }
}

@Extend(Text)
function textBor() {
  .width(100).height('80%').textAlign(TextAlign.Center).border({ width: 1, color: '#FF5500' })
}

在项目中使用到了onWillScroll和onDidScroll冲突了,所以换了一种方式

@Entry
@Component
struct RefreshPage {
  scroller: Scroller = new Scroller()
  @State isShow: boolean = false
  @State scrollDirection: number = -1

  build() {
    Column() {
      Scroll(this.scroller) {
        Row({ space: 10 }) {
          Row({ space: 10 }) {
            Text('模块一').textBor()
            Text('模块二').textBor()
          }
          .onTouch((event: TouchEvent) => {

          })
          .onVisibleAreaChange([0.3, 0.7], (isVisible: boolean, currentRatio: number) => {
            if (isVisible) {
              this.isShow = true
              this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            } else {
              this.isShow = false
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            }
          })

          Text('模块三').textBor()
          Text('模块四').textBor()
          Text('模块五').textBor()
          Text('模块六').textBor()
          Text('模块七').textBor()
          Text('模块八').textBor()
          Text('模块九').textBor()
        }
      }
      .width('100%')
      .height(100)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .onAppear(() => {
        this.scroller.scrollTo({ xOffset: 220, yOffset: 0 })
      })
      .onWillScroll((x: number, y: number, scrollState: ScrollState, scrollSource: ScrollSource) => {
        if (x > 0) {
          this.scrollDirection = 1
        } else {
          this.scrollDirection = 2
        }
      })
      .onTouch((event: TouchEvent) => {
        if (event.type == TouchType.Up) { // 手指抬起时判断
          if (this.isShow) {
            this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
          } else {
            if (this.scrollDirection == 2) {
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            }
          }
        }
      })
    }

  }
}

@Extend(Text)
function textBor() {
  .width(100).height('80%').textAlign(TextAlign.Center).border({ width: 1, color: '#FF5500' })
}

解决办法如下:

@Entry
@Component
struct RefreshPage {
  scroller: Scroller = new Scroller()
  @State isShow: boolean = false

  build() {
    Column() {
      Scroll(this.scroller) {
        Row({ space: 10 }) {
          Row({ space: 10 }) {
            Text('模块一').textBor
            Text('模块二').textBor
          }
          .onTouch((event: TouchEvent) => {
          })
          .onVisibleAreaChange([0.3, 0.7], (isVisible: boolean, currentRatio: number) => {
            if (isVisible) {
              this.isShow = true
              this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            } else {
              this.isShow = false
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
            }
          })

          Text('模块三').textBor
          Text('模块四').textBor
          Text('模块五').textBor
          Text('模块六').textBor
          Text('模块七').textBor
          Text('模块八').textBor
          Text('模块九').textBor
        }
      }
      .width('100%')
      .height(100)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .onAppear(() => {
        this.scroller.scrollTo({ xOffset: 220, yOffset: 0 })
      })
      .onDidScroll((xOffset: number, yOffset: number, scrollState: ScrollState) => {
        if (scrollState == ScrollState.Idle) {
          if (this.isShow) {
            this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
          } else {
            this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 500, curve: Curve.Linear, canOverScroll: false } })
          }
        }
      })
    }
  }
}

@Extend(Text)
function textBor() {
  .width(100).height('80%').textAlign(TextAlign.Center).border({ width: 1, color: '#FF5500' })
}

使用onVisibleAreaChange实现了这种动画效果,但是感觉不够完善,而且,

模块一和模块二是不可以只展示一半的,要么全展示出来,要么不展示

还要计算具体的偏移量,来区分

大家还有什么更好的办法吗?

@Entry
@Component
struct RefreshPage {
  scroller: Scroller = new Scroller()

  build() {
    Column() {
      Scroll(this.scroller) {
        Row({ space: 10 }) {
          Row({ space: 10 }) {
            Text('模块一').textBor
            Text('模块二').textBor
          }.onVisibleAreaChange([0.3, 0.7], (isVisible: boolean, currentRatio: number) => {
            if (isVisible) {
              this.scroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 1000, curve: Curve.Linear, canOverScroll: false } })
            } else {
              this.scroller.scrollTo({ xOffset: 220, yOffset: 0, animation: { duration: 1000, curve: Curve.Linear, canOverScroll: false } })
            }
          })

          Text('模块三').textBor
          Text('模块四').textBor
          Text('模块五').textBor
          Text('模块六').textBor
          Text('模块七').textBor
          Text('模块八').textBor
          Text('模块九').textBor
        }
      }
      .width('100%')
      .height(100)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .onAppear(() => {
        this.scroller.scrollTo({ xOffset: 220, yOffset: 0 })
      })
    }

  }
}

@Extend(Text)
function textBor() {
  .width(100).height('80%').textAlign(TextAlign.Center).border({ width: 1, color: '#FF5500' })
}

在HarmonyOS鸿蒙Next中,Scroll组件可以通过自定义动画效果实现。使用@ohos.animator模块创建动画,结合Scroll的滚动事件,动态调整子组件的属性。例如,可以在onScroll回调中根据滚动位置计算动画进度,通过Animatorupdate方法实时更新组件的透明度、位置等属性。具体实现步骤如下:

  1. 创建Animator对象,定义动画的起始值和结束值。
  2. ScrollonScroll事件中,根据滚动距离计算动画进度。
  3. 通过Animatorupdate方法,将计算出的值应用到目标组件的属性上。

示例代码片段如下:

import { Animator, Scroll } from '@ohos.animator';

const scroll = new Scroll();
const animator = new Animator({
  startValue: 0,
  endValue: 1,
  duration: 300
});

scroll.onScroll((scrollOffset) => {
  const progress = calculateProgress(scrollOffset);
  animator.update(progress);
});

function calculateProgress(offset) {
  // 根据滚动偏移量计算动画进度
  return offset / maxScrollOffset;
}

在HarmonyOS鸿蒙Next中,可以通过Scroll组件结合Animation来实现滚动时的动画效果。首先,使用Scroll组件包裹需要滚动的内容,然后通过onScroll事件监听滚动位置。在onScroll回调中,根据滚动位置动态调整目标组件的属性(如透明度、缩放、位移等),并利用Animation对象创建平滑的过渡效果。例如,可以通过animateTo方法实现渐隐渐现或缩放动画。具体实现时,建议使用@State@Prop装饰器管理动画状态,确保UI的响应性和流畅性。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!