HarmonyOS 鸿蒙Next 上下拉动画效果实现
HarmonyOS 鸿蒙Next 上下拉动画效果实现
效果如图:(为什么用xhs?因为手头没华为设备没法导出视频……orz)
更多关于HarmonyOS 鸿蒙Next 上下拉动画效果实现的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
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)=>{ ListItem(){ QHNearbyOutletItemView({ index: index, outletServiceItem: item, clickNavEvent: ()=>{ }, clickTelEvent: ()=>{ 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) => { <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) => <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(() => { <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 >= 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 < <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(() => { <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 鸿蒙Next 上下拉动画效果实现的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS(鸿蒙)中实现上下拉动画效果,通常涉及到自定义组件和动画资源的运用。你可以通过编写自定义的CustomScrollView
或者扩展现有的ScrollView
,在其中使用Animator
或Animation
类来定义动画效果。设置动画的属性,如translationY
,以模拟上下拉的视觉效果。
此外,利用HarmonyOS的Canvas API在绘制层面上直接操作也可能是一种选择,但通常这种方式更为复杂且不易于维护。
确保你的动画在合适的生命周期事件中启动和停止,如滑动开始和结束。
如果问题依旧没法解决请加我微信,我的微信是itying888。
更多关于HarmonyOS 鸿蒙Next 上下拉动画效果实现的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html