ArkUI实战,自定义下拉刷新组件RefreshList
Posted llew2011
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArkUI实战,自定义下拉刷新组件RefreshList相关的知识,希望对你有一定的参考价值。
下拉刷新是一个高频使用的功能,ArkUI 开发框架也提供了下拉刷新组件 Refresh,该组件的使用非常简单,读者可参阅笔者在《ArkUI实战》第六章 第 5 小节 的介绍,本文笔者讲解一下笔者在项目上实现的一个下拉刷新组件 RefreshList,该组件的运行效果如下图所示:
- 布局拆分
下拉刷新组件都是分为上下两部分,上边是刷新头:refreshHead,该刷新头根据手指的下滑距离提示是否达到刷新条件;下边是刷新体:refreshContent,当触发下拉刷新条件后对外回调,从而实现内容更新。笔者实现的 RefreshList 也是按照以上布局实现的,简化图如下所示:
默认情况下 refreshHead 是布局在 RefreshList 可视区域外边,笔者在第三章 第 1 小节 讲解过可以使用 position() 方法实现布局定位,简化代码如下所示:
@Component struct RefreshList
build()
Column()
Row()
// header布局
.id("refresh_header")
.width("100%")
.height(50)
.position( // 利用该属性,把refresh_header布局在 Column 顶部
x: 0,
y: -50
)
Column()
// content 布局
.id("refresh_content")
.width("100%")
.height("100%")
.position( // 利用该属性,把refresh_content布局向上做偏移
x: 0,
y: 0
)
.id("refresh_list")
.width("100%")
.height("100%")
- 滑动处理
ArkUI 开发框架对于手势事件的处理遵循 W3C 标准,首先是目标捕获阶段,然后再是事件冒泡阶段,下拉刷新的操作就是在事件冒泡阶段处理的,因此直接实现 refresh_list 的 onTouch() 方法即可,在该方法内根据手指的滑动距离动态实现 refreshHeader 和 refreshContent 的布局定位即可,简化代码如下所示:
@Component struct RefreshList
private refreshHeaderHeight: number = 50;
private offsetY: number = -this.refreshHeaderHeight;
private lastX: number;
private lastY: number;
private downY: number;
build()
Column()
Row()
.id("refresh_header")
.width("100%")
.height(this.refreshHeaderHeight)
.backgroundColor("#bbaacc")
.position(
x: 0,
y: this.offsetY
)
Column()
.id("refresh_content")
.width("100%")
.height("100%")
.backgroundColor("#aabbcc")
.position(
x: 0,
y: this.offsetY + this.refreshHeaderHeight
)
.id("refresh_list")
.width("100%")
.height("100%")
.onTouch((event) =>
if (event.type == TouchType.Down)
// 处理 down 事件
this.onTouchDown(event);
else if (event.type == TouchType.Move)
// 处理 move 事件
this.onTouchMove(event);
else if (event.type == TouchType.Cancel || event.type == TouchType.Up)
// 处理 up 事件
this.onTouchUp(event);
)
private onTouchDown(event: TouchEvent)
this.lastX = event.touches[0].screenX;
this.lastY = event.touches[0].screenY;
this.downY = this.lastY;
private onTouchMove(event: TouchEvent)
let currentX = event.touches[0].screenX;
let currentY = event.touches[0].screenY;
let deltaX = currentX - this.lastX;
let deltaY = currentY - this.lastY;
if (Math.abs(deltaX) < Math.abs(deltaY) && Math.abs(deltaY) > 5)
// 达到滑动条件
private onTouchUp(event: TouchEvent)
- 滑动冲突
由于 refreshContent 内部包含的是 List 组件,该组件比较特殊,它会默认响应手势的滑动操作,在处理外层滑动的时候该 List 也会跟着一起滑动,这种体验是非常不友好的,因此可以在 List 的 onScrollBegin() 方法中处理滑动冲突,简化代码如下所示:
@Component struct RefreshList
build()
Column()
Row()
.id("refresh_header")
.position(
x: 0,
y: this.offsetY
)
Column()
List(scroller: this.listScroller)
.edgeEffect(EdgeEffect.None)
.onScrollBegin((dx: number, dy: number) => // 处理滑动冲突
dy = this.listScrollable ? dy : 0;
return dxRemain: dx, dyRemain: dy
)
.id("refresh_content")
.position(
x: 0,
y: this.offsetY + this.refreshHeaderHeight
)
.id("refresh_list")
listScrollable 属性表示 List 是否可以滚动,当在处理外部滑动的时候禁止内部的 List 滑动,此时让 onScrollBegin() 方法返回的 dyRemain 为 0 即可。
- 完整代码
export namespace refresh
export class Constant
static readonly REFRESH_PULL_TO_REFRESH = "下拉刷新";
static readonly REFRESH_FREE_TO_REFRESH = "释放立即刷新";
static readonly REFRESH_REFRESHING = "正在刷新";
static readonly REFRESH_SUCCESS = "刷新成功";
@Component
export struct RefreshList
@BuilderParam
itemLayout?: (item: any, index: number) => any;
@Watch("notifyRefreshingChanged")
@Link refreshing: boolean;
@Link dataSet: Array<any>;
onRefresh?: () => void;
onStatusChanged?: (status: RefreshStatus) => void;
private headHeight: number = 55;
private lastX: number = 0;
private lastY: number = 0;
private downY: number = 0;
private flingFactor: number = 0.75;
private touchSlop: number = 2;
private offsetStep: number = 10;
private intervalTime: number = 20;
private listScrollable: boolean = true;
private dragging: boolean = false;
private refreshStatus: RefreshStatus = RefreshStatus.Inactive;
@Watch("notifyOffsetYChanged")
@State offsetY: number = -this.headHeight;
@State refreshHeadIcon: Resource = $r("app.media.icon_refresh_down");
@State refreshHeadText: string = refresh.Constant.REFRESH_PULL_TO_REFRESH;
@State refreshContentH: number = 0;
@State touchEnabled: boolean = true;
@State headerVisibility: Visibility = Visibility.None;
private listScroller: Scroller = new Scroller();
private notifyRefreshingChanged()
if (this.refreshing)
this.showRefreshingStatus();
else
this.finishRefresh();
private notifyOffsetYChanged()
this.headerVisibility = (this.offsetY == -this.headHeight) ? Visibility.None : Visibility.Visible;
@Builder headLayout()
Row()
Blank()
Image(this.refreshHeadIcon)
.width(30)
.aspectRatio(1)
.objectFit(ImageFit.Contain)
Text(this.refreshHeadText)
.fontSize(16)
.width(150)
.textAlign(TextAlign.Center)
Blank()
.width("100%")
.height(this.headHeight)
.backgroundColor("#44bbccaa")
.visibility(this.headerVisibility)
.position(
x: 0,
y: this.offsetY
)
build()
Column()
this.headLayout()
Column()
List(scroller: this.listScroller)
if (this.dataSet)
ForEach(this.dataSet, (item, index) =>
ListItem()
if (this.itemLayout)
this.itemLayout(item, index)
.width("100%")
, item => item)
.width("100%")
.height("100%")
.edgeEffect(EdgeEffect.None)
.onScrollBegin((dx: number, dy: number) =>
dy = this.listScrollable ? dy : 0;
return dxRemain: dx, dyRemain: dy
)
.width("100%")
.layoutWeight(1)
.backgroundColor(Color.Pink)
.position(
x: 0,
y: this.offsetY + this.headHeight
)
.width("100%")
.height("100%")
.enabled(this.touchEnabled)
.onAreaChange((oldArea, newAre) =>
console.log("Refresh height: " + newAre.height);
this.refreshContentH = Number(newAre.height);
)
.clip(true)
.onTouch((event) =>
if (event.touches.length != 1)
this.logD("TOUCHES LENGTH INVALID: " + JSON.stringify(event.touches))
event.stopPropagation();
return
switch (event.type)
case TouchType.Down:
this.onTouchDown(event);
break;
case TouchType.Move:
this.onTouchMove(event);
break;
case TouchType.Up:
case TouchType.Cancel:
this.onTouchUp(event);
break;
event.stopPropagation();
)
private setRefreshStatus(status: RefreshStatus)
this.refreshStatus = status;
this.refreshing = (status == RefreshStatus.Refresh);
this.touchEnabled = (status != RefreshStatus.Refresh && status != RefreshStatus.Done);
this.notifyStatusChanged();
private canRefresh()
return this.listScroller.currentOffset().yOffset == 0;
private onTouchDown(event: TouchEvent)
this.lastX = event.touches[0].screenX;
this.lastY = event.touches[0].screenY;
this.downY = this.lastY;
this.dragging = false;
this.listScrollable = true;
this.logD("Touch DOWN: " + event.touches[0].screenX.toFixed(2) + " x " + event.touches[0].screenY.toFixed(2) + ", offset: " + this.offsetY);
private onTouchMove(event: TouchEvent)
let currentX = event.touches[0].screenX;
let currentY = event.touches[0].screenY;
let deltaX = currentX - this.lastX;
let deltaY = currentY - this.lastY;
if (this.dragging)
this.logD("offsetY: " + this.offsetY.toFixed(2) + ", head: " + (-this.headHeight));
if (deltaY < 0)
if (this.offsetY > -this.headHeight)
this.offsetY = this.offsetY + px2vp(deltaY) * this.flingFactor;
this.listScrollable = false;
else
this.offsetY = -this.headHeight;
this.listScrollable = true;
this.downY = this.lastY;
else
if (this.canRefresh())
以上是关于ArkUI实战,自定义下拉刷新组件RefreshList的主要内容,如果未能解决你的问题,请参考以下文章