HarmonyOS 實現下拉刷新,上拉加載更多
組件介紹
PullToRefreshList允許用戶通過下拉動作來刷新列表內容,以及通過上拉動作來加載更多的數據。組件內部封裝了滾動監聽、狀態管理和動畫效果,使得開發者可以輕松集成到自己的項目中。
1. 實現思路
- 封裝成可復用的公共控件:將下拉刷新和上拉加載更多功能封裝為一個可復用的組件,便于在不同列表場景中應用。
- 狀態管理:使用狀態變量來管理刷新狀態(如
refreshing、loadingMoreIng)、列表是否滑動到頂部(isAtTopOfList)等。 - 事件回調:通過定義回調函數(如
onRefresh、onLoadMore、onStatusChanged)來處理刷新和加載邏輯,并與外部邏輯解耦。 - 手勢識別:利用觸摸事件(
TouchType.Down、TouchType.Move、TouchType.Up)來識別用戶的下拉的手勢。 - 動畫效果:通過控制滾動偏移量和刷新頭部的顯示與隱藏,實現平滑的下拉刷新動畫效果。
3. 下拉刷新實現原理
下拉刷新的實現依賴于對觸摸事件的監聽和處理:
- 觸摸按下(
TouchType.Down):記錄用戶觸摸按下時的坐標,用于后續的滑動判斷。 - 觸摸移動(
TouchType.Move):在用戶移動手指時,計算移動的距離,并根據列表的當前滾動位置和偏移量來判斷是否觸發下拉刷新的動作。若用戶在列表頂部下拉,則通過更新offsetY來控制刷新頭部的顯示和隱藏,同時更新刷新狀態。 - 觸摸抬起或取消(
TouchType.Up或TouchType.Cancel):在用戶完成下拉動作后,根據偏移量判斷是否滿足刷新條件。如果滿足,則觸發刷新邏輯,并通過回調函數onRefresh通知外部進行數據刷新。如果不滿足,則恢復列表到未拖動狀態。
4. 加載更多實現原理
- 滾動到底部:使用
onReachEnd事件監聽器來檢測用戶是否滾動到列表底部。當觸發此事件時,如果loadingMoreIng為false,表示當前沒有在加載更多數據,則將其設置為true,開始加載更多數據。 - 加載邏輯:調用
onLoadMore回調函數來執行加載更多的邏輯。在加載數據的過程中,可以更新列表項的布局以顯示加載提示文本,如“努力加載中...”。 - 數據更新:加載完成后,更新數據源
dataSet,并重置loadingMoreIng為false,表示加載更多操作已完成。
PullToRefreshList完整代碼:
import { Constant } from '../constant/Constant';
@Preview
@Component
export struct PullToRefreshList {
// 通過BuilderParam裝飾器聲明的函數參數,用于自定義列表項的布局
@BuilderParam
itemLayout?: (item: Object, index: number) => void;
// 通過Watch和Link裝飾器監聽刷新狀態的變化,并雙向綁定刷新標志
@Watch("notifyRefreshingChanged")
@Link refreshing: boolean;
// 通過Link裝飾器雙向綁定,表示是否正在加載更多數據
@Link
loadingMoreIng: boolean;
// 狀態變量,表示列表是否滑動到頂部
@State
isAtTopOfList: boolean = false;
// 通過Link裝飾器雙向綁定,表示列表的數據源
@Link dataSet: Array<Object>;
// 定義回調函數,用于處理刷新和加載更多事件
onRefresh?: () => void;
onLoadMore?: () => 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和State裝飾器監聽并狀態綁定,表示下拉刷新頭部的偏移量
@Watch("notifyOffsetYChanged")
@State offsetY: number = -this.headHeight;
// 狀態變量,刷新頭部圖標資源
@State refreshHeadIcon: Resource = $r("app.media.icon_refresh_down");
// 狀態變量,刷新頭部提示文本
@State refreshHeadText: string = 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
RefreshHead() {
// 使用Row布局來水平排列圖標和文本
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
})
}
/**
* 構建刷新內容組件的邏輯
*/
@Builder
RefreshContent() {
List({ scroller: this.listScroller }) { // 使用List組件創建滾動列表,并傳入滾動器
if (this.dataSet) { // 判斷數據源是否存在
ForEach(this.dataSet, (item: Object, index: number) => { // 遍歷數據源并為每項創建列表項
ListItem() {
if (this.itemLayout) { // 如果提供了列表項布局函數,則使用它
this.itemLayout(item, index)
}
}
.width("100%"); // 設置列表項寬度
}, (item: Object, index: number) => item.toString()) // 為列表項提供唯一的標識符
// 上拉加載更多的UI提示
ListItem() {
if (this.loadingMoreIng === false) {
Text('努力加載中...') // 顯示加載文本
.width('100%') // 設置文本寬度
.height(100) // 設置文本高度
.fontSize(16) // 設置字體大小
.textAlign(TextAlign.Center) // 設置文本居中對齊
.backgroundColor(0xDCDCDC); // 設置背景顏色
}
}
}
}
.width("100%") // 設置列表寬度
.height("100%") // 設置列表高度
.edgeEffect(EdgeEffect.None) // 設置無邊緣效果
.onScrollFrameBegin((offset: number, state: ScrollState) => { // 監聽滾動事件
offset = this.listScrollable ? offset : 0; // 根據滾動狀態決定是否允許滾動
return { offsetRemain: offset }
})
.onReachEnd(() => { // 監聽滾動到列表底部的事件
if (!this.loadingMoreIng) { // 如果不是正在加載更多
this.loadingMoreIng = true // 設置為正在加載狀態
this.onLoadMore() // 調用加載更多的回調函數
this.loadingMoreIng = false // 完成加載,設置為非加載狀態
}
})
.onScrollIndex((start: number, end: number) => { // 監聽滾動索引變化事件
this.logD("onScrollIndex() start = " + start + ",end = " + end) // 打印滾動索引
// 根據滾動索引判斷列表是否滑動到頂部
if (start == 0) {
this.isAtTopOfList = true
} else {
this.isAtTopOfList = false
}
})
}
// 定義組件的構建邏輯,使用 Column 組件作為根布局
build() {
// 創建一個 Column 組件實例,作為整個下拉刷新列表的容器
Column() {
// 調用 RefreshHead 方法,添加下拉刷新的頭部組件
this.RefreshHead()
// 創建另一個 Column 組件實例,作為內容區域的容器
Column() {
// 調用 RefreshContent 方法,添加可滾動的內容區域
// 這個內容區域包含了列表數據的展示
this.RefreshContent()
}
// 為內容區域的 Column 設置屬性
.id("refresh_content") // 設置組件的 ID
.width("100%") // 設置寬度為100%,占滿父容器寬度
.layoutWeight(1) // 設置布局權重,影響在剩余空間中的占比
.backgroundColor(Color.Pink) // 設置背景顏色為粉紅色
.position({ // 設置內容區域的起始位置,實現下拉刷新時的上移效果
x: 0, // 在水平方向上的位置
y: this.offsetY + this.headHeight // 在垂直方向上的位置,根據偏移量和頭部高度計算
})
}
// 為整個下拉刷新列表的 Column 設置屬性
.id("refresh_list") // 設置根容器的 ID
.width("100%") // 設置寬度為100%,占滿父容器寬度
.height("100%") // 設置高度為100%,占滿父容器高度
.enabled(this.touchEnabled) // 設置是否啟用觸摸事件,基于 touchEnabled 狀態變量
.onAreaChange((oldArea, newAre) => { // 監聽容器大小變化事件
console.log("Refresh height: " + newAre.height); // 打印新的高度值
this.refreshContentH = Number(newAre.height); // 更新內容區域的高度狀態變量
})
.clip(true) // 設置是否啟用裁剪,超出部分會被隱藏
.onTouch((event) => { // 監聽觸摸事件
if (event.touches.length != 1) { // 判斷是否為單指觸摸
console.log("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();
})
}
/**
* 設置下拉刷新的狀態,并根據狀態啟用或禁用觸摸事件,以及通知刷新狀態變化。
* @param status 下拉刷新的新狀態。
*/
private setRefreshStatus(status: RefreshStatus) {
// 更新當前的刷新狀態為傳入的參數status
this.refreshStatus = status;
// 根據刷新狀態設置refreshing標志,如果狀態是Refresh,則設置為true,否則為false
this.refreshing = (status == RefreshStatus.Refresh);
// 根據刷新狀態設置touchEnabled,當狀態不是Refresh和Done時,允許觸摸事件
this.touchEnabled = (status != RefreshStatus.Refresh && status != RefreshStatus.Done);
// 通知刷新狀態發生了變化,調用onStatusChanged回調函數(如果已設置)
this.notifyStatusChanged();
}
/**
* 檢查當前滾動狀態是否允許觸發下拉刷新動作。
* @returns {boolean} 如果列表滾動到頂部或已標記為頂部,則返回true,否則返回false。
*/
private canRefresh() {
// 檢查滾動器的當前垂直偏移量的y坐標是否等于-headHeight,或者isAtTopOfList標志為true
// 如果列表已滾動到頂部(yOffset為-headHeight,即刷新頭部的原始隱藏位置),
// 或者isAtTopOfList被設置為true(通過滾動事件監聽器),則可以進行下拉刷新
return this.listScroller.currentOffset().yOffset == -this.headHeight || this.isAtTopOfList;
}
/**
* 處理觸摸按下事件的方法。
* @param event 觸摸事件對象,包含觸摸點的相關信息。
*/
private onTouchDown(event: TouchEvent) {
// 從觸摸事件對象中獲取第一個觸摸點的屏幕坐標
this.lastX = event.touches[0].screenX;
this.lastY = event.touches[0].screenY;
// downY用于記錄初始按下時的Y坐標,用于后續移動事件中的滑動判斷
this.downY = this.lastY;
}
/**
* 處理觸摸移動事件的方法。
* @param event 觸摸事件對象,包含觸摸點的相關信息。
*/
private onTouchMove(event: TouchEvent) {
// 獲取當前觸摸點的屏幕坐標
let currentX = event.touches[0].screenX;
let currentY = event.touches[0].screenY;
// 計算自上次觸摸移動以來的X、Y方向變化量
let deltaX = currentX - this.lastX;
let deltaY = currentY - this.lastY;
// 如果當前正處于拖動狀態
if (this.dragging) {
// 記錄當前偏移量
console.log("offsetY: " + this.offsetY.toFixed(2) + ", head: " + (-this.headHeight));
// 判斷Y方向的滑動方向
if (deltaY < 0) {
// 向上拖動
if (this.offsetY > -this.headHeight) {
// 如果當前偏移量大于-headHeight,表示還在下拉刷新的可拖動范圍內
console.log("手指向上拖動還未到達臨界值,不讓 list 滾動")
// 更新offsetY,并應用flingFactor作為滑動速度的調整因子
this.offsetY = this.offsetY + px2vp(deltaY) * this.flingFactor;
// 此時不允許列表滾動
this.listScrollable = false;
} else {
// 如果當前偏移量小于等于-headHeight,表示已經到達臨界值,可以開始滾動列表
console.log("手指向上拖動到達臨界值了,開始讓 list 滾動")
this.offsetY = -this.headHeight;
// 允許列表滾動
this.listScrollable = true;
// 重置downY為當前的lastY,為下次拖動做準備
this.downY = this.lastY;
}
} else {
// 向下拖動
console.log("手指向下拖動中 this.canRefresh() = " + this.canRefresh())
// 如果可以刷新(即滾動到了列表頂部)
if (this.canRefresh()) {
// 更新offsetY,并應用flingFactor作為滑動速度的調整因子
this.offsetY = this.offsetY + px2vp(deltaY) * this.flingFactor;
// 此時不允許列表滾動
this.listScrollable = false;
} else {
// 如果不是在頂部,則允許滾動
this.listScrollable = true;
}
}
// 更新lastX和lastY為當前坐標,為下次滑動做準備
this.lastX = currentX;
this.lastY = currentY;
} else {
// 如果當前不在拖動狀態
// 判斷是否為向下的滑動,并且滑動的起始點是列表頂部
if (Math.abs(deltaX) < Math.abs(deltaY) && Math.abs(deltaY) > this.touchSlop) {
if (deltaY > 0 && this.canRefresh()) {
// 設置拖動狀態為true
this.dragging = true;
// 不允許列表滾動
this.listScrollable = false;
// 更新lastX和lastY為當前坐標
this.lastX = currentX;
this.lastY = currentY;
console.log("Touch MOVE: 手指向下滑動,達到了拖動條件");
}
}
}
// 如果當前正處于拖動狀態
if (this.dragging) {
// 判斷當前觸摸點的Y坐標是否大于初始按下時的Y坐標
if (currentY >= this.downY) {
// 根據當前的offsetY值判斷刷新頭部的顯示狀態
if (this.offsetY >= 0 || (this.headHeight - Math.abs(this.offsetY)) > this.headHeight * 4 / 5) {
// 如果已經下拉到超過頭部高度的80%,則顯示松開刷新的提示
this.refreshHeadText = Constant.REFRESH_FREE_TO_REFRESH;
this.refreshHeadIcon = $r("app.media.icon_refresh_up");
// 設置當前刷新狀態為OverDrag,即超過拖動區域的狀態
this.setRefreshStatus(RefreshStatus.OverDrag);
} else {
// 如果還在下拉過程中,顯示下拉刷新的提示
this.refreshHeadText = Constant.REFRESH_PULL_TO_REFRESH;
this.refreshHeadIcon = $r("app.media.icon_refresh_down");
// 設置當前刷新狀態為Drag,即拖動狀態
this.setRefreshStatus(RefreshStatus.Drag);
}
}
}
// 可選的控制臺日志,打印當前觸摸點的坐標和偏移量,可以用于調試
// console.log("Touch MOVE: " + event.touches[0].screenX + " x " + event.touches[0].screenY + ", offset: " + this.offsetY);
}
/**
* 處理觸摸抬起事件的方法,根據拖動狀態和偏移量決定是否觸發刷新。
* @param event 觸摸事件對象,包含觸摸點的相關信息。
*/
private onTouchUp(event: TouchEvent) {
// 打印抬起時的X、Y坐標和當前偏移量offsetY
console.log("Touch UP: " + event.touches[0].screenX.toFixed(2) +
" x " + event.touches[0].screenY.toFixed(2) +
", offset: " + this.offsetY);
// 如果當前狀態為拖動狀態,處理釋放后的邏輯
if (this.dragging) {
// 判斷是否滿足下拉刷新的條件:offsetY大于等于0,或者下拉距離已經超過頭部高度的80%
if (this.offsetY >= 0 || (this.headHeight - Math.abs(this.offsetY)) > this.headHeight * 4 / 5) {
// 打印日志,表示用戶已經下拉到足夠的距離,可以觸發刷新
console.log("Touch UP: 觸發下拉刷新條件");
// 更新刷新頭部的圖標和文本,提示用戶正在刷新
this.refreshHeadIcon = $r("app.media.icon_refresh_loading");
this.refreshHeadText = Constant.REFRESH_REFRESHING;
// 設置刷新狀態為Refresh,表示正在刷新中
this.setRefreshStatus(RefreshStatus.Refresh);
// 將列表滾動到頂部,隱藏刷新頭部
this.scrollToTop();
// 通知刷新開始,調用外部提供的onRefresh回調函數
this.notifyRefreshStart();
} else {
// 如果沒有達到刷新條件,打印日志說明
console.log("Touch UP: 未達到下拉刷新條件");
// 更新刷新頭部的圖標和文本,提示用戶下拉以刷新
this.refreshHeadIcon = $r("app.media.icon_refresh_down");
this.refreshHeadText = Constant.REFRESH_PULL_TO_REFRESH;
// 設置刷新狀態為Drag,表示用戶可以繼續拖動
this.setRefreshStatus(RefreshStatus.Drag);
// 滾動回原位,恢復到未拖動狀態
this.scrollByTop();
}
}
}
/**
* 將滾動偏移量設置為0,使得內容區域回到頂部。
*/
private scrollToTop() {
// 設置滾動偏移量為0,滾動到列表頂部
this.offsetY = 0;
}
/**
* 滾動內容區域回到初始位置。
*/
private scrollByTop() {
// 如果當前滾動偏移量不等于-headHeight,即沒有滾動到頂部
if (this.offsetY != -this.headHeight) {
// 記錄開始滾動時的偏移量
this.logD("scrollByTop() start, offsetY: " + this.offsetY.toFixed(2));
// 使用setInterval創建一個滾動動畫,每隔intervalTime毫秒更新一次偏移量
let intervalId = setInterval(() => {
// 如果滾動偏移量小于等于-headHeight,即滾動到了頂部
if (this.offsetY <= -this.headHeight) {
// 重置刷新狀態
this.resetRefreshStatus();
// 清除定時器,停止滾動動畫
clearInterval(intervalId);
// 記錄滾動完成時的偏移量
this.logD("scrollByTop() finish, offsetY: " + this.offsetY.toFixed(2));
} else {
// 如果還沒有滾動到頂部,則更新滾動偏移量
// 每次向上滾動offsetStep像素,直到滾動到頭
this.offsetY = ((this.offsetY - this.offsetStep) < -this.headHeight) ?
(-this.headHeight) : (this.offsetY - this.offsetStep);
}
}, this.intervalTime);
} else {
// 如果已經滾動到頂部,則無需執行滾動操作
this.logD("scrollByTop(): already scrolled to top edge");
}
}
/**
* 重置刷新狀態,將刷新頭部恢復到初始狀態。
*/
private resetRefreshStatus() {
// 設置滾動偏移量為-headHeight,即刷新頭部隱藏的狀態
this.offsetY = -this.headHeight;
// 設置刷新頭部圖標為默認圖標
this.refreshHeadIcon = $r("app.media.icon_refresh_down");
// 設置刷新頭部文本為默認文本
this.refreshHeadText = Constant.REFRESH_PULL_TO_REFRESH;
// 調用setRefreshStatus方法,設置刷新狀態為Inactive,即未激活狀態
this.setRefreshStatus(RefreshStatus.Inactive);
}
/**
* 完成刷新操作后調用的方法,用于更新UI狀態并滾動回列表頂部。
*/
private finishRefresh(): void {
// 更新刷新頭部的文本和圖標,表示刷新成功
this.refreshHeadText = Constant.REFRESH_SUCCESS;
this.refreshHeadIcon = $r("app.media.icon_refresh_success");
// 設置刷新狀態為Done,表示刷新已完成
this.setRefreshStatus(RefreshStatus.Done);
// 延遲1500毫秒后滾動回列表頂部,以便用戶可以看到刷新效果
setTimeout(() => {
this.scrollByTop();
}, 1500);
}
/**
* 組件即將顯示時調用的方法,用于檢查是否需要顯示刷新狀態。
*/
aboutToAppear() {
// 如果當前正在刷新(由refreshing標志控制)
if (this.refreshing) {
// 顯示刷新狀態,更新UI以反映刷新正在進行中
this.showRefreshingStatus();
}
}
/**
* 顯示刷新狀態的方法,用于更新UI以反映刷新正在進行中。
*/
private showRefreshingStatus() {
// 將滾動偏移量設置為0,確保刷新頭部可見
this.offsetY = 0;
// 更新刷新頭部的圖標為加載圖標
this.refreshHeadIcon = $r("app.media.icon_refresh_loading");
// 更新刷新頭部的文本為刷新中文本
this.refreshHeadText = Constant.REFRESH_REFRESHING;
// 設置刷新狀態為Refresh,表示正在刷新
this.setRefreshStatus(RefreshStatus.Refresh);
}
/**
* 通知刷新開始的方法,用于調用外部提供的刷新回調函數。
*/
private notifyRefreshStart() {
// 如果提供了onRefresh回調函數
if (this.onRefresh) {
// 調用onRefresh回調函數,開始執行刷新邏輯
this.onRefresh();
}
}
/**
* 通知刷新狀態變化的方法,用于在刷新狀態變化時調用外部提供的回調函數。
*/
private notifyStatusChanged() {
// 如果提供了onStatusChanged回調函數
if (this.onStatusChanged) {
// 調用onStatusChanged回調函數,通知刷新狀態的變化
this.onStatusChanged(this.refreshStatus);
}
}
private logD(msg: string) {
console.log(msg + ", canRefresh: " + this.canRefresh() + ", dragging: " + this.dragging + ", listScrollable: " + this.listScrollable + ", refreshing: " + this.refreshing);
}
}
調用示例:
import { PullToRefreshList } from '../view/PullToRefreshList';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
// 初始列表數據
@State
dataSet: Array<string> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
@State
refreshing: boolean = false //下拉刷新
@State
loading: boolean = false //上拉加載
build() {
Column() {
PullToRefreshList({
refreshing: $refreshing,
loadingMoreIng:$loading,
dataSet: $dataSet,
itemLayout: (item: Object, index: number) => {
this.item(item);
},
onRefresh: () => {
this.onRefresh();
},
onLoadMore: () => {
this.onLoadMore();
},
onStatusChanged: (status) => {
console.log("current status: " + status);
}
})
}.height('100%')
}
async onRefresh() {
setTimeout(() => {
console.log("finish refresh")
this.dataSet = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
this.refreshing = false;
}, 2500);
}
async onLoadMore() {
setTimeout(() => {
console.log("finish load More")
// 生成10條0到9之間的隨機數并添加到dataSet數組中
for (let i = 0; i < 10; i++) {
// 生成一個0到9之間的隨機數
const randomNumber = Math.floor(this.dataSet.length + 1);
// 將隨機數添加到dataSet數組中
this.dataSet.push(randomNumber.toString());
}
this.loading = false
}, 2500);
}
//實現真正的布局 item
@Builder
item(item: Object) {
Text('' + item) // 顯示列表項文本
.width('100%') // 設置文本寬度
.height(100) // 設置文本高度
.fontSize(24) // 設置字體大小
.textAlign(TextAlign.Center) // 設置文本居中對齊
.borderRadius(10) // 設置邊框圓角
.backgroundColor(0xDCDCDC); // 設置背景顏色
}
}

浙公網安備 33010602011771號