HarmonyOS 鸿蒙Next 上下拉动画效果实现

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

HarmonyOS 鸿蒙Next 上下拉动画效果实现

效果如图:(为什么用xhs?因为手头没华为设备没法导出视频……orz)

cke_2228.gif

2 回复
const TopHeight = 150;

@Entry @Component export default struct QHNearbyOutletListView { @ObjectLink outletServiceResult: QHOutletServiceResult @ObjectLink outletService: QHOutletServiceItem

private listScroller: Scroller = new Scroller();

// 列表y坐标偏移量 @State offsetY: number = 0 // 按下的y坐标 private downY = 0 // 上一次移动的y坐标 private lastMoveY = 0 // 当前列表首部的索引 private startIndex = 0 // 当前列表尾部的索引 private endIndex = 0

// 下拉刷新的布局高度 private pullRefreshHeight = 50 // 下拉刷新文字:下拉刷新、松开刷新、正在刷新、刷新成功 @State pullRefreshText: string= ‘下拉刷新’ // 下拉刷新图标:与文字对应 @State pullRefreshImage: Resource = $r(“app.media.qh_refresh_icon”) // 是否可以刷新:未达到刷新条件,收缩回去 private isCanRefresh = false // 是否正在刷新:刷新中不进入触摸逻辑 private isRefreshing: boolean = false // 是否已经进入了下拉刷新操作 private isPullRefreshOperation = false

// 上拉加载的布局默认高度 private loadMoreHeight = 40 // 上拉加载的布局是否显示 @State isVisibleLoadMore: boolean = false // 是否可以加载更多 private isCanLoadMore = false // 是否加载中:加载中不进入触摸逻辑 private isLoading: boolean = false

// 自定义下拉刷新布局 @Builder CustomPullRefreshLayout(){ Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { Image(this.pullRefreshImage) .width(18) .height(18) .rotate({ x: 0, y: 0, z: 1, centerX: ‘50%’, centerY: ‘50%’, angle: this.isCanRefresh && this.isRefreshing ? 0 : this.offsetY*3 }) // 组件以矢量(0,0,1)为旋转轴,绕中心点顺时针旋转 angle 度

  Text(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pullRefreshText)
    .margin({ left: <span class="hljs-number"><span class="hljs-number">7</span></span>, bottom: <span class="hljs-number"><span class="hljs-number">1</span></span> })
    .fontSize(<span class="hljs-number"><span class="hljs-number">17</span></span>)
}
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pullRefreshHeight)
<span class="hljs-comment"><span class="hljs-comment">// 布局跟着列表偏移量移动</span></span>
.offset({ x: <span class="hljs-number"><span class="hljs-number">0</span></span>, y: `${vp2px(-<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pullRefreshHeight) + <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY}px` })

}

// 自定义加载更多布局 @Builder CustomLoadMoreLayout(){ Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { Image($r(“app.media.qh_refresh_icon”)) .width(18) .height(18) .rotate({ x: 0, y: 0, z: 1, centerX: ‘50%’, centerY: ‘50%’, angle: -this.offsetY*20 })

  Text(<span class="hljs-string"><span class="hljs-string">'放开以刷新...'</span></span>)
    .margin({ left: <span class="hljs-number"><span class="hljs-number">7</span></span>, bottom: <span class="hljs-number"><span class="hljs-number">1</span></span> })
    .fontSize(<span class="hljs-number"><span class="hljs-number">17</span></span>)
}
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.loadMoreHeight)
.backgroundColor(<span class="hljs-string"><span class="hljs-string">'#f4f4f4'</span></span>)
.visibility(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.isVisibleLoadMore ? Visibility.Visible : Visibility.None)

}

// 刷新测试数据 private refreshData(){ OutletServiceViewModel.requestBranchData( this.outletServiceResult, ‘1’, this.outletService.PROV_CODE, this.outletService.CITY_CODE, this.outletService.TOWN_CODE, this.outletService.BRCH_TYPE, this.outletService.BRCH_POS_X, this.outletService.BRCH_POS_Y, ) this.listScroller.scrollToIndex(0) }

// 加载更多测试数据 private loadMoreData(){ OutletServiceViewModel.requestBranchData( this.outletServiceResult, this.outletServiceResult.NEXT_KEY, this.outletService.PROV_CODE, this.outletService.CITY_CODE, this.outletService.TOWN_CODE, this.outletService.BRCH_TYPE, this.outletService.BRCH_POS_X, this.outletService.BRCH_POS_Y, ) this.listScroller.scrollToIndex(0) }

build() { Column() { // 下拉刷新布局 this.CustomPullRefreshLayout()

  <span class="hljs-comment"><span class="hljs-comment">// 列表布局</span></span>
  List({scroller: <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.listScroller}) {
    ForEach(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.outletServiceResult.LIST,(item: QHOutletServiceItem, index: number)=&gt;{
      ListItem(){
        QHNearbyOutletItemView({
          index: index,
          outletServiceItem: item,
          clickNavEvent: ()=&gt;{

          },
          clickTelEvent: ()=&gt;{
            YTCallUtil.call(item.BRCH_TEL)
          }
        })

      }
    })

    <span class="hljs-comment"><span class="hljs-comment">// 加载更多布局</span></span>
    ListItem(){
      <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.CustomLoadMoreLayout()
    }
  }
  .scrollBar(BarState.Off)
  .alignListItem(ListItemAlign.Center)
  .width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
  .backgroundColor(<span class="hljs-string"><span class="hljs-string">'#FFF2F3F4'</span></span>)<span class="hljs-comment"><span class="hljs-comment">// 背景</span></span>
  .edgeEffect(EdgeEffect.None) <span class="hljs-comment"><span class="hljs-comment">// 去掉回弹效果</span></span>
  .offset({ x: <span class="hljs-number"><span class="hljs-number">0</span></span>, y: `${<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY - TopHeight}px` }) <span class="hljs-comment"><span class="hljs-comment">// touch事件计算的偏移量单位是px,记得加上单位</span></span>
  .onScrollIndex((start, end) =&gt; { <span class="hljs-comment"><span class="hljs-comment">// 监听当前列表首位索引</span></span>
    console.info(`${start}=start============end=${end}`)
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.startIndex = start
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.endIndex = end
  })

}
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.backgroundColor(<span class="hljs-string"><span class="hljs-string">'#f4f4f4'</span></span>)
.onTouch((event) =&gt; <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.listTouchEvent(event))<span class="hljs-comment"><span class="hljs-comment">// 父容器设置touch事件,当列表无数据也可以下拉刷新</span></span>
.onAppear(() =&gt; {
  <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.refreshData()
})

}

// 触摸事件 listTouchEvent(event: TouchEvent){ switch (event.type) { case TouchType.Down: // 手指按下 // 记录按下的y坐标 this.downY = event.touches[0].y this.lastMoveY = event.touches[0].y break case TouchType.Move: // 手指移动 // 下拉刷新中 或 加载更多中,不进入处理逻辑 if(this.isRefreshing || this.isLoading){ console.info(’========Move刷新中,返回=========’) return } // 判断手势 let isDownPull = event.touches[0].y - this.lastMoveY > 0 // 下拉手势 或 已经进入了下拉刷新操作 if ((isDownPull || this.isPullRefreshOperation) && !this.isCanLoadMore) { this.touchMovePullRefresh(event) } else { this.touchMoveLoadMore(event) } this.lastMoveY = event.touches[0].y break case TouchType.Up: // 手指抬起 case TouchType.Cancel: // 触摸意外中断:来电界面 // 刷新中 或 加载更多中,不进入处理逻辑 if(this.isRefreshing || this.isLoading){ console.info(’========Up刷新中,返回=========’) return } if (this.isPullRefreshOperation) { this.touchUpPullRefresh() } else { this.touchUpLoadMore() } break } }

//============================================下拉刷新================================================== // 手指移动,处理下拉刷新 touchMovePullRefresh(event:TouchEvent){ // 当首部索引位于0 if (this.startIndex == 0) { this.isPullRefreshOperation = true // 下拉刷新布局高度 let height = vp2px(this.pullRefreshHeight) // 滑动的偏移量 this.offsetY = event.touches[0].y - this.downY

  <span class="hljs-comment"><span class="hljs-comment">// 偏移量大于下拉刷新布局高度,达到刷新条件</span></span>
  <span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY &gt;= height) {
    <span class="hljs-comment"><span class="hljs-comment">// 状态1:松开刷新</span></span>
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pullRefreshState(<span class="hljs-number"><span class="hljs-number">1</span></span>)
    <span class="hljs-comment"><span class="hljs-comment">// 偏移量的值缓慢增加</span></span>
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY = height + <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY * <span class="hljs-number"><span class="hljs-number">0.15</span></span>
  } <span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
    <span class="hljs-comment"><span class="hljs-comment">// 状态0:下拉刷新</span></span>
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pullRefreshState(<span class="hljs-number"><span class="hljs-number">0</span></span>)
  }

  <span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY &lt; <span class="hljs-number"><span class="hljs-number">0</span></span>) {
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.offsetY = <span class="hljs-number"><span class="hljs-number">0</span></span>
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.isPullRefreshOperation = <span class="hljs-literal"><span class="hljs-literal">false</span></span>
  }
}

}

// 手指抬起,处理下拉刷新 touchUpPullRefresh(){ // 是否可以刷新 if (this.isCanRefresh) { console.info(’======执行下拉刷新========’) // 偏移量为下拉刷新布局高度 this.offsetY = vp2px(this.pullRefreshHeight) // 状态2:正在刷新 this.pullRefreshState(2)

  <span class="hljs-comment"><span class="hljs-comment">// 模拟耗时操作</span></span>
  setTimeout(() =&gt; {
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.refreshData()
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.closeRefresh()
  }, <span class="hljs-number"><span class="hljs-number">500</span></span>)

} <span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
  console.info(<span class="hljs-string"><span class="hljs-string">'======关闭下拉刷新!未达到条件========'</span></span>)
  <span class="hljs-comment"><span class="hljs-comment">// 关闭刷新</span></span>
  <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.closeRefresh()
}

}

// 下拉刷新状态 // 0下拉刷新、1松开刷新、2正在刷新、3刷新成功 pullRefreshState(state:number){ switch (state) { case 0: // 初始状态 this.pullRefreshText = ‘下拉刷新…’ this.pullRefreshImage = $r(“app.media.qh_refresh_icon”) this.isCanRefresh = false this.isRefreshing = false break; case 1: this.pullRefreshText = ‘松开以刷新…’ this.pullRefreshImage = $r(“app.media.qh_refresh_icon”) this.isCanRefresh = true this.isRefreshing = false break; case 2: this.offsetY = vp2px(this.pullRefreshHeight) this.pullRefreshText = ‘正在载入…’ this.pullRefreshImage = $r(“app.media.qh_refresh_loading_icon”) this.isCanRefresh = true this.isRefreshing = true break; case 3: this.pullRefreshText = ‘刷新成功’ this.pullRefreshImage = $r(“app.media.qh_refresh_done_icon”) this.isCanRefresh = true this.isRefreshing = true break; } }

// 关闭刷新 closeRefresh() { // 如果允许刷新,延迟进入,为了显示刷新中 setTimeout(() => { let delay = 50 if (this.isCanRefresh) { // 状态3:刷新成功 this.pullRefreshState(3) // 为了显示刷新成功,延迟执行收缩动画 delay = 500 } animateTo({ duration: 150, // 动画时长 delay: delay, // 延迟时长 onFinish: () => { // 状态0:下拉刷新 this.pullRefreshState(0) this.isPullRefreshOperation = false } }, () => { this.offsetY = 0 }) }, this.isCanRefresh ? 500 : 0) }

//============================================加载更多================================================== // 手指移动,处理加载更多 touchMoveLoadMore(event:TouchEvent) { // 因为加载更多是在列表后面新增一个item,当一屏能够展示全部列表,endIndex 为 length+1 if (this.endIndex == this.outletServiceResult.LIST.length - 1 || this.endIndex == this.outletServiceResult.LIST.length) { // 滑动的偏移量 this.offsetY = event.touches[0].y - this.downY if (Math.abs(this.offsetY) > vp2px(this.loadMoreHeight)/2) { // 可以刷新了 this.isCanLoadMore = true // 显示加载更多布局 this.isVisibleLoadMore = true // 偏移量缓慢增加 this.offsetY = - vp2px(this.loadMoreHeight) + this.offsetY * 0.1 } } }

// 手指抬起,处理加载更多 touchUpLoadMore() { animateTo({ duration: 200, // 动画时长 }, () => { // 偏移量设置为0 this.offsetY = 0 }) if (this.isCanLoadMore) { console.info(’======执行加载更多========’) // 加载中… this.isLoading = true // 模拟耗时操作 setTimeout(() => { this.closeLoadMore() this.loadMoreData() }, 500) } else { console.info(’======关闭加载更多!未达到条件========’) this.closeLoadMore() } }

// 关闭加载更多 closeLoadMore() { this.isCanLoadMore = false this.isLoading = false this.isVisibleLoadMore = false }

} <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

在HarmonyOS(鸿蒙)中实现上下拉动画效果,通常涉及到自定义组件和动画资源的运用。你可以通过编写自定义的CustomScrollView或者扩展现有的ScrollView,在其中使用AnimatorAnimation类来定义动画效果。设置动画的属性,如translationY,以模拟上下拉的视觉效果。

此外,利用HarmonyOS的Canvas API在绘制层面上直接操作也可能是一种选择,但通常这种方式更为复杂且不易于维护。

确保你的动画在合适的生命周期事件中启动和停止,如滑动开始和结束。

如果问题依旧没法解决请加我微信,我的微信是itying888。

回到顶部