HarmonyOS Next 之列表上拉刷新下拉加载及其分页功能

2025-06-18 14:59:50
112次阅读
0个评论

在鸿蒙(HarmonyOSNEXT)应用开发中,上拉加载与下拉刷新是提升用户体验的重要功能。本文将详细介绍如何在鸿蒙应用中实现这一功能,包括设计思路、关键代码以及注意事项。

1. 组件化设计

为了保持代码的灵活性和可维护性,我们将上拉加载与下拉刷新功能封装成一个独立的组件。该组件可以无缝集成到任何需要这些功能的页面上,包括列表(List)、宫格(Grid)等多种布局。

2. 自定义扩展性

PullToRefresh是一款OpenHarmony环境下可用的下拉刷新、上拉加载组件。 支持设置内置动画的各种属性,支持设置自定义动画,支持lazyForEarch的数据作为数据源。。

3.完整源码

1.封装的组件 PullToRefresh.ets 代码如下:

import { PullToRefreshConfigurator } from './PullToRefreshConfigurator'

const IS_FREE = 0;
const IS_PULL_DOWN_1 = 11;
const IS_PULL_DOWN_2 = 12;
const IS_REFRESHING = 2;
const IS_REFRESHED = 3;
const IS_PULL_UP_1 = 41;
const IS_PULL_UP_2 = 42;
const IS_LOADING = 5;

@Component
export struct PullToRefresh {
  @Link data: Object[];
  scroller: Scroller = new Scroller();
  @BuilderParam customList?: () => void;
  refreshConfigurator?: PullToRefreshConfigurator;
  mWidth?: Length = '100%';
  mHeight?: Length = '100%';
  onRefresh?: () => Promise<string> = () => {
    return new Promise<string>((resolve, reject) => {
      setTimeout(() => {
        resolve('刷新失败');
      }, 1000);
    });
  };
  onLoadMore?: () => Promise<string> = () => {
    return new Promise<string>((resolve, reject) => {
      setTimeout(() => {
        resolve('');
      }, 1000);
    });
  };
  // 自定义下拉动画
  @BuilderParam customRefresh?: (() => void) | null;
  //开启自定义下拉动画
  onAnimPullDown?: (value?: number, width?: number, height?: number) => void;
  onAnimRefreshing?: (value?: number, width?: number, height?: number) => void;
  // 自定义上拉动画
  @BuilderParam customLoad?: (() => void) | null;
  onAnimPullUp?: (value?: number, width?: number, height?: number) => void;
  onAnimLoading?: (value?: number, width?: number, height?: number) => void;
  //-----------------------------以下为组件内自用属性-----------------------------//
  @State private mHeightNumber?: number = 0;
  @State private trYTop?: number = 0;
  @State private trYBottom?: number = 0;
  @State private state?: number = IS_FREE;
  @State private refreshText?: string = '';
  @State private loadText?: string = '';
  @State private angle1?: number | string = 0;
  @State private angle2?: number | string = 0;
  private mWidthNumber?: number = 0;
  private touchYOld?: number = 0;
  private touchYNew?: number = 0;
  private listOffsetOld?: number = 0;
  private listOffsetNew?: number = 0;
  private canvasSetting?: RenderingContextSettings = new RenderingContextSettings(true);
  private canvasRefresh?: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.canvasSetting);
  private value?: number = 0;
  private timer?: number;
  private refreshRingOx?: number = 0;
  private refreshRingOy?: number = 0;
  private refreshRingRadius?: number = 0;
  private refreshPoint1x?: number = 0;
  private refreshPoint1y?: number = 0;
  private refreshPoint2x?: number = 0;
  private refreshPoint2y?: number = 0;
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down })

  aboutToAppear() {
    if (!this.refreshConfigurator) {
      this.refreshConfigurator = new PullToRefreshConfigurator();
    }
  }

  private initCanvas(): void {
    if (this.refreshRingOx == 0) {

      if (this.canvasRefresh !== undefined && this.refreshConfigurator !== undefined) {
        this.canvasRefresh.strokeStyle = this.refreshConfigurator.getRefreshColor();
        this.canvasRefresh.fillStyle = this.refreshConfigurator.getRefreshColor();
        this.canvasRefresh.lineWidth = this.refreshConfigurator.getRefreshHeight() / 60 + 1;
      }
      if (this.refreshConfigurator !== undefined) {
        this.refreshRingOx = this.refreshConfigurator.getRefreshWidth() / 2; // 圆心x坐标
        this.refreshRingOy = this.refreshConfigurator.getRefreshHeight() / 2; // 圆心y坐标
        this.refreshRingRadius = this.refreshConfigurator.getRefreshHeight() / 4; // 半径
        this.refreshPoint1x = this.refreshRingOx + this.refreshRingRadius * Math.cos(Math.PI * 150 / 180);
        this.refreshPoint1y = this.refreshRingOy + this.refreshRingRadius * Math.sin(Math.PI * 150 / 180);
        this.refreshPoint2x = this.refreshRingOx + this.refreshRingRadius * Math.cos(Math.PI * -30 / 180);
        this.refreshPoint2y = this.refreshRingOy + this.refreshRingRadius * Math.sin(Math.PI * -30 / 180);
      }
    }
  }

  build() {
    Column() {
      // 下拉刷新动画部分
      Stack() {
        this.headerUI()
      }
      .width('100%')
      .height(this.trYTop !== undefined ? this.trYTop : 0)
      .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshBackgroundColor() : 0)

      // 主体列表部分
      Column() {
        if (this.customList !== undefined) {
          this.customList()
        }
      }
      .width('100%')
      .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0))

      // 上拉加载动画部分
      Stack() {
        this.footerUI()
      }
      .width('100%')
      .height(this.trYBottom !== undefined ? -this.trYBottom : 0)
      .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadBackgroundColor() : 0)
    }
    .width(this.mWidth === undefined ? '100%' : this.mWidth)
    .height(this.mHeight === undefined ? '100%' : this.mHeight)
    .onAreaChange((oldValue: Area, newValue: Area) => {
      this.mWidthNumber = Math.round(newValue.width as number);
      this.mHeightNumber = Math.round(newValue.height as number);
    })
    .parallelGesture(
      PanGesture(this.panOption)
        .onActionStart((event?: GestureEvent) => {
          if (event !== undefined) {
            this.touchYOld = event.offsetY;
          }
        })
        .onActionUpdate((event?: GestureEvent) => {
          if (event !== undefined) {
            this.onActionUpdate(event);
          }
        })
        .onActionEnd(() => {
          this.onActionEnd();
        })
    )
  }

  @Builder
  private headerUI() {
    if (this.customRefresh !== undefined && this.customRefresh !== null) {
      Column() {
        this.customRefresh()
      }
      .width('100%')
      .height('100%')
      .visibility((this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2  || this.state == IS_REFRESHING) ? Visibility.Visible : Visibility.Hidden)
    } else {
      Stack() {
        Text(this.refreshText)
          .textAlign(TextAlign.Center)
          .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshTextColor() : 0)
          .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshTextSize() : 0)
        Stack() {
          Canvas(this.canvasRefresh)
            .width('100%')
            .height('100%')
            .onReady(() => {
              this.initCanvas();
            })
            .visibility(this.state == IS_PULL_DOWN_2 ? Visibility.Visible : Visibility.Hidden)
          LoadingProgress()
            .width(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshHeight() : 0)
            .height(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshHeight() : 0)
            .color(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshColor() : 0)
            .visibility(this.state == IS_REFRESHING ? Visibility.Visible : Visibility.Hidden)
        }
        .width(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshWidth() : 0)
        .height(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshHeight() : 0)
      }
      .width('100%')
      .height('100%')
    }
  }

  @Builder
  private footerUI() {
    if (this.customLoad !== undefined && this.customLoad !== null) {
      Column() {
        this.customLoad()
      }
      .width('100%')
      .height('100%')
      .visibility((this.state == IS_PULL_UP_1  || this.state == IS_PULL_UP_2   || this.state == IS_LOADING ) ? Visibility.Visible : Visibility.Hidden)
    } else {
      Row() {
        Stack() {
          Image($r('app.media.icon_up'))
            .width('100%')
            .height('100%')
            .objectFit(ImageFit.Contain)
            .visibility(this.state == IS_PULL_UP_2 ? Visibility.Visible : Visibility.Hidden)
            .rotate({
              z: 1,
              angle: this.angle1 !== undefined ? this.angle1 : 0
            })
          Image($r('app.media.icon_load'))
            .width('100%')
            .height('100%')
            .objectFit(ImageFit.Contain)
            .visibility(this.state == IS_LOADING ? Visibility.Visible : Visibility.Hidden)
            .rotate({
              z: 1,
              angle: this.angle2 !== undefined ? this.angle2 : 0
            })
        }
        .width(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadImgHeight() : 0)
        .height(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadImgHeight() : 0)

        Text(this.loadText)
          .height('100%')
          .textAlign(TextAlign.Center)
          .margin({ left: 8 })
          .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextColor() : 0)
          .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextSize() : 0)
      }
      .height('100%')
    }
  }

  private onActionUpdate(event: GestureEvent): void {
    if (this.state !== undefined && this.refreshConfigurator !== undefined && this.touchYOld !== undefined) {
      if (this.state == IS_FREE ||
        this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2 ||
        this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) {
        let maxTranslate = this.refreshConfigurator.getMaxTranslate()
        let loadImgHeight = this.refreshConfigurator.getLoadImgHeight()
        let refreshHeight = this.refreshConfigurator.getRefreshHeight()
        if (!this.scroller.currentOffset()) {
          return;
        }
        this.touchYNew = event.offsetY;

        // 当前手势是否下拉
        let distanceY = this.touchYNew - this.touchYOld;
        let isPullAction = distanceY > 0;

        //兼容页面滑动到顶部时,this.scroller.currentOffset().yOffset返回0.000000....的浮点数的情况
        let yOffset: number = this.scroller.currentOffset().yOffset;
        let isTop: boolean = yOffset == 0 ? true : false;
        if (yOffset > -0.0001 && yOffset < 0.001) {
          isTop = true;
        }

        if ((this.state == IS_FREE && isTop && isPullAction) || // 处于自由状态且列表处于顶部位置 并且 当前手势是下拉手势
          this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2) { // 处于下拉状态中
          if (this.refreshConfigurator.getHasRefresh()) {
            if (this.touchYOld !== undefined && refreshHeight !== undefined) {
              // 获取最新位移距离
              let trY = this.touchYNew - this.touchYOld;

              //防止下拉回滑时list组件底层跟着滑动
              if(trY < 0) {
                this.scroller.scrollTo({xOffset: 0, yOffset: 0})
              }

              // 计算当前需要位移的总距离
              this.trYTop = this.getTranslateYOfRefresh(trY);
              if (this.trYTop < refreshHeight) {
                this.state = IS_PULL_DOWN_1;
              } else {
                this.state = IS_PULL_DOWN_2;
              }
              // 如果没有自定义刷新动画,就执行内置动画下拉时的逻辑
              if (!this.customRefresh && maxTranslate !== undefined) {
                this.drawRefreshView(this.trYTop / maxTranslate);
              }
              // 如果有下拉中动画回调,就执行下拉中动画回调
              if (this.onAnimPullDown && maxTranslate !== undefined) {
                this.onAnimPullDown(this.trYTop / maxTranslate, this.mWidthNumber, this.trYTop);
              }
            }
          }
        } else if (this.refreshConfigurator.getHasLoadMore()) {
          this.listOffsetNew = this.scroller.currentOffset().yOffset;
          // 列表处于底部位置且上滑时,2.已上滑时
          try {
            if (this.touchYOld !== undefined) {
              if ((this.state == IS_FREE && this.listOffsetOld == this.listOffsetNew && this.listOffsetOld != 0 && this.touchYNew < this.touchYOld && this.scroller.isAtEnd()) ||
                this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) {
                // 获取最新的位移距离
                let trY = this.touchYNew - this.touchYOld;
                // 计算当前需要位移的总距离
                this.trYBottom = this.getTranslateYOfLoadMore(trY);
                if (loadImgHeight !== undefined && this.trYBottom > -loadImgHeight) {
                  this.state = IS_PULL_UP_1;
                } else {
                  this.state = IS_PULL_UP_2;
                }
                // 如果没有自定义加载更多动画,就执行内置动画上拉时的逻辑
                if (!this.customLoad && maxTranslate !== undefined) {
                  this.drawLoadView(true, -this.trYBottom / maxTranslate);
                }
                // 如果有上拉中动画回调,就执行上拉中动画回调
                if (this.onAnimPullUp) {
                  if (this.trYBottom !== undefined && maxTranslate !== undefined) {
                    this.onAnimPullUp(-this.trYBottom / maxTranslate, this.mWidthNumber, -this.trYBottom);
                  }
                }
              }
            }
          } catch (error) {
            if (this.touchYOld !== undefined) {
              if ((this.state == IS_FREE && this.listOffsetOld == this.listOffsetNew && this.listOffsetOld != 0 && this.touchYNew < this.touchYOld) ||
                this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) {
                if (this.touchYNew !== undefined && this.touchYOld !== undefined && loadImgHeight !== undefined) {
                  // 获取最新的位移距离
                  let trY = this.touchYNew - this.touchYOld;
                  // 计算当前需要位移的总距离
                  this.trYBottom = this.getTranslateYOfLoadMore(trY);
                  if (this.trYBottom > -loadImgHeight) {
                    this.state = IS_PULL_UP_1;
                  } else {
                    this.state = IS_PULL_UP_2;
                  }
                  // 如果没有自定义加载更多动画,就执行内置动画上拉时的逻辑
                  if (!this.customLoad && maxTranslate !== undefined) {
                    this.drawLoadView(true, -this.trYBottom / maxTranslate);
                  }
                  // 如果有上拉中动画回调,就执行上拉中动画回调
                  if (this.onAnimPullUp) {
                    if (this.trYBottom !== undefined && maxTranslate !== undefined) {
                      this.onAnimPullUp(-this.trYBottom / maxTranslate, this.mWidthNumber, -this.trYBottom);
                    }
                  }
                }
              }
            }
          }
          this.listOffsetOld = this.listOffsetNew;
        }
        this.touchYOld = this.touchYNew;
      }
    }
  }

  private onActionEnd(): void {
    if (this.refreshConfigurator !== undefined) {
      let maxTranslate = this.refreshConfigurator.getMaxTranslate()
      let refreshAnimDuration = this.refreshConfigurator.getRefreshAnimDuration();
      if (this.refreshConfigurator.getListIsPlacement()) {
        if (this.state !== undefined) {
          if (this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2) {
            // 让列表归位到顶部
            this.scroller.scrollEdge(Edge.Top);
            // 让列表归位到底部
          } else if (this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) {
            this.scroller.scrollEdge(Edge.Bottom);
          }
        }
      }
      if (this.trYTop !== undefined) {
        if (this.trYTop > 0) { // 下拉结束
          if (this.state !== undefined && maxTranslate !== undefined) {
            if (this.state == IS_FREE || this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2) {
              if (this.trYTop / maxTranslate < 0.75) {
                this.closeRefresh();
              } else {
                this.state = IS_REFRESHING;
                this.trYTop = maxTranslate * 0.75;
                clearInterval(this.timer);
                this.timer = setInterval(() => {
                  if (this.value !== undefined) {
                    if (this.value >= 1) {
                      this.value -= 1;
                    } else {
                      if (refreshAnimDuration !== undefined && refreshAnimDuration !== 0) {
                        this.value += 10 / refreshAnimDuration;
                      }
                    }
                    // 保留3位小数
                    this.value = Math.round(this.value * 1000) / 1000;
                  }
                  // 刷新中动画采用系统组件,因此不用自己去执行动画
                  // 如果有刷新中动画回调,就执行刷新中动画回调
                  if (this.onAnimRefreshing) {
                    this.onAnimRefreshing(this.value, this.mWidthNumber, this.trYTop);
                  }
                }, 10);
                if (this.onRefresh !== undefined) {
                  this.onRefresh().then((refreshText) => {
                    if (refreshText.length == 0) {
                      this.closeRefresh();
                    } else {
                      this.state = IS_REFRESHED;
                      if (!this.customRefresh) {
                        this.refreshText = refreshText;
                      }
                      setTimeout(() => {
                        this.closeRefresh();
                      }, 1000);
                    }
                  });
                }
              }
            }
          }
        } else if (this.trYBottom !== undefined && this.trYBottom < 0) { // 上拉结束
          if (this.state !== undefined && maxTranslate !== undefined) {
            if (this.state == IS_FREE || this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) {
              if (-this.trYBottom / maxTranslate < 0.75) {
                this.closeLoad();
              } else {
                this.state = IS_LOADING;
                this.trYBottom = -maxTranslate * 0.75;
                clearInterval(this.timer);
                this.timer = setInterval(() => {
                  if (this.value !== undefined) {
                    if (this.value >= 1) {
                      this.value -= 1;
                    } else {
                      this.value += 0.01;
                    }
                    // 保留2位小数
                    this.value = Math.round(this.value * 100) / 100;
                    // 如果没有自定义加载中动画,就执行内置加载中动画
                    if (!this.customLoad) {
                      this.drawLoadView(false, this.value);
                    }
                  }
                  // 如果有加载中动画回调,就执行加载中动画回调
                  if (this.onAnimLoading) {
                    if (this.trYBottom !== undefined) {
                      this.onAnimLoading(this.value, this.mWidthNumber, -this.trYBottom);
                    }
                  }
                }, 10);
                if (this.onLoadMore !== undefined) {
                  this.onLoadMore().then((loadText) => {
                    this.closeLoad();
                  });
                }
              }
            }
          }
        } else {
          this.state = IS_FREE;
        }
      }
    }
  }

  private getTranslateYOfRefresh(newTranslateY: number): number {
    if (this.refreshConfigurator !== undefined) {
      let maxTranslateY = this.refreshConfigurator.getMaxTranslate();
      let sensitivity = this.refreshConfigurator.getSensitivity();
      if (maxTranslateY !== undefined && sensitivity !== undefined && this.trYTop !== undefined) {
        // 阻尼值计算
        if (this.trYTop / maxTranslateY < 0.2) {
          newTranslateY = newTranslateY * 1 * sensitivity;
        } else if (this.trYTop / maxTranslateY < 0.4) {
          newTranslateY = newTranslateY * 0.8 * sensitivity;
        } else if (this.trYTop / maxTranslateY < 0.6) {
          newTranslateY = newTranslateY * 0.6 * sensitivity;
        } else if (this.trYTop / maxTranslateY < 0.8) {
          newTranslateY = newTranslateY * 0.4 * sensitivity;
        } else {
          newTranslateY = newTranslateY * 0.2 * sensitivity;
        }
        // 下拉值计算
        if (this.trYTop + newTranslateY > maxTranslateY) {
          return maxTranslateY;
        } else if (this.trYTop + newTranslateY < 0) {
          return 0;
        } else {
          return this.trYTop + newTranslateY;
        }
      }
    }
    return 0;
  }

  private getTranslateYOfLoadMore(newTranslateY: number): number {
    if (this.refreshConfigurator !== undefined) {
      let maxTranslateY = this.refreshConfigurator.getMaxTranslate();
      let sensitivity = this.refreshConfigurator.getSensitivity();
      if (maxTranslateY !== undefined && sensitivity !== undefined && this.trYBottom !== undefined) {
        // 阻尼值计算
        if (this.trYBottom / maxTranslateY > -0.2) {
          newTranslateY = newTranslateY * 1 * sensitivity;
        } else if (this.trYBottom / maxTranslateY > -0.4) {
          newTranslateY = newTranslateY * 0.8 * sensitivity;
        } else if (this.trYBottom / maxTranslateY > -0.6) {
          newTranslateY = newTranslateY * 0.6 * sensitivity;
        } else if (this.trYBottom / maxTranslateY > -0.8) {
          newTranslateY = newTranslateY * 0.4 * sensitivity;
        } else {
          newTranslateY = newTranslateY * 0.2 * sensitivity;
        }
        // 下拉值计算
        if (this.trYBottom + newTranslateY < -maxTranslateY) {
          return -maxTranslateY;
        } else if (this.trYBottom + newTranslateY > 0) {
          return 0;
        } else {
          return this.trYBottom + newTranslateY;
        }
      }
    }
    return 0;
  }

  private drawRefreshView(value: number): void {
    if (this.refreshConfigurator !== undefined && this.trYTop !== undefined) {
      let refreshHeight = this.refreshConfigurator.getRefreshHeight()
      if (refreshHeight !== undefined && this.trYTop >= refreshHeight) {
        if (this.canvasRefresh !== undefined) {
          let refreshWidth = this.refreshConfigurator.getRefreshWidth()
          if (refreshWidth !== undefined) {
            this.canvasRefresh.clearRect(0, 0, refreshWidth, refreshHeight);
          }
          // 绘制圆环
          this.canvasRefresh.beginPath();
          if (this.refreshRingOx !== undefined && this.refreshRingOy !== undefined && this.refreshRingRadius !== undefined) {
            this.canvasRefresh.arc(this.refreshRingOx, this.refreshRingOy, this.refreshRingRadius, 0, Math.PI * 2);
          }
          this.canvasRefresh.stroke();
          // 绘制卫星
          value = value > 0.75 ? 0.75 : value;
          this.canvasRefresh.beginPath();
          if (this.refreshPoint2x !== undefined && this.refreshPoint1x !== undefined
            && this.refreshPoint2y !== undefined && this.refreshPoint1y !== undefined) {
            this.canvasRefresh.arc(
              value * (this.refreshPoint2x - this.refreshPoint1x) + this.refreshPoint1x,
              value * (this.refreshPoint2y - this.refreshPoint1y) + this.refreshPoint1y,
              refreshHeight / 20 + 1, 0, Math.PI * 2);
          }
          this.canvasRefresh.fill();
        }
      }
    }
  }

  private drawLoadView(isPullUp: boolean, value: number): void {
    if (isPullUp) {
      if (this.refreshConfigurator !== undefined) {
        let loadImgHeight = this.refreshConfigurator.getLoadImgHeight()
        if (loadImgHeight !== undefined && this.trYBottom !== undefined) {
          if (this.trYBottom <= -loadImgHeight) {
            if (value < 0.75) {
              this.angle1 = 0;
              if (this.refreshConfigurator !== undefined) {
                this.loadText = this.refreshConfigurator.getLoadTextPullUp1();
              }
            } else {
              this.angle1 = 180;
              if (this.refreshConfigurator !== undefined) {
                this.loadText = this.refreshConfigurator.getLoadTextPullUp2();
              }
            }
          } else {
            this.loadText = '';
          }
        }
      }
    } else {
      this.angle2 = value * 360;
      if (this.refreshConfigurator !== undefined) {
        this.loadText = this.refreshConfigurator.getLoadTextLoading();
      }
    }
  }

  public closeRefresh(): void {
    clearInterval(this.timer);
    if (this.refreshConfigurator !== undefined) {
      animateTo({ duration: this.refreshConfigurator.getAnimDuration() }, () => {
        this.trYTop = 0;
      });
    }
    if (this.refreshConfigurator !== undefined) {
      setTimeout(() => {
        this.state = IS_FREE;
        this.refreshText = '';
      }, this.refreshConfigurator.getAnimDuration());
    }
  }

  public closeLoad(): void {
    clearInterval(this.timer);
    if (this.refreshConfigurator !== undefined) {
      animateTo({ duration: this.refreshConfigurator.getAnimDuration() }, () => {
        this.trYBottom = 0;
      });
    }
    this.state = IS_FREE;
    this.loadText = '';
  }
}

配置文件:PullToRefreshConfigurator.ets

export class PullToRefreshConfigurator {
  private hasRefresh?: boolean = true; // 是否具有下拉刷新功能
  private hasLoadMore?: boolean = true; // 是否具有上拉加载功能
  private maxTranslate?: number = 100; // 可下拉上拉的最大距离
  private sensitivity?: number = 0.7; // 下拉上拉灵敏度
  private listIsPlacement?: boolean = true; // 滑动结束后列表是否归位
  private animDuration?: number = 150; // 滑动结束后,回弹动画执行时间
  private refreshHeight?: number = 30; // 下拉动画高度
  private refreshColor?: string = '#999999'; // 下拉动画颜色
  private refreshBackgroundColor?: ResourceColor = 'rgba(0,0,0,0)'; // 下拉动画区域背景色
  private refreshTextColor?: ResourceColor = '#999999'; // 下拉加载完毕后提示文本的字体颜色
  private refreshTextSize?: number | string | Resource = 18; // 下拉加载完毕后提示文本的字体大小
  private refreshAnimDuration?: number = 1000; // 下拉动画执行一次的时间
  private loadImgHeight?: number = 30; // 上拉图片高度
  private loadBackgroundColor?: ResourceColor = 'rgba(0,0,0,0)'; // 上拉动画区域背景色
  private loadTextColor?: ResourceColor = '#999999'; // 上拉文本的字体颜色
  private loadTextSize?: number | string | Resource = 18; // 上拉文本的字体大小
  private loadTextPullUp1?: string = '正在上拉刷新...'; // 上拉1阶段文本
  private loadTextPullUp2?: string = '放开刷新'; // 上拉2阶段文本
  private loadTextLoading?: string = '正在玩命加载中...'; // 上拉加载更多中时的文本

  setHasRefresh(hasRefresh: boolean) {
    this.hasRefresh = hasRefresh;
    return this;
  }

  getHasRefresh() {
    return this.hasRefresh;
  }

  setHasLoadMore(hasLoadMore: boolean) {
    this.hasLoadMore = hasLoadMore;
    return this;
  }

  getHasLoadMore() {
    return this.hasLoadMore;
  }

  setMaxTranslate(maxTranslate: number) {
    this.maxTranslate = maxTranslate;
    return this;
  }

  getMaxTranslate() {
    return this.maxTranslate;
  }

  setSensitivity(sensitivity: number) {
    this.sensitivity = sensitivity;
    return this;
  }

  getSensitivity() {
    return this.sensitivity;
  }

  setListIsPlacement(listIsPlacement: boolean) {
    this.listIsPlacement = listIsPlacement;
    return this;
  }

  getListIsPlacement() {
    return this.listIsPlacement;
  }

  setAnimDuration(animDuration: number) {
    this.animDuration = animDuration;
    return this;
  }

  getAnimDuration() {
    return this.animDuration;
  }

  getRefreshWidth(): number {
    if (this.refreshHeight !== undefined) {
      return this.refreshHeight / 3 * 4;
    }
    return 0;
  }

  setRefreshHeight(refreshHeight: number) {
    this.refreshHeight = refreshHeight;
    return this;
  }

  getRefreshHeight(): number {
    return this.refreshHeight !== undefined ? this.refreshHeight : 0;
  }

  setRefreshColor(refreshColor: string) {
    this.refreshColor = refreshColor;
    return this;
  }

  getRefreshColor(): string {
    return this.refreshColor !== undefined ? this.refreshColor : "0";
  }

  setRefreshBackgroundColor(refreshBackgroundColor: ResourceColor) {
    this.refreshBackgroundColor = refreshBackgroundColor;
    return this;
  }

  getRefreshBackgroundColor(): ResourceColor {
    return this.refreshBackgroundColor !== undefined ? this.refreshBackgroundColor : 0;
  }

  setRefreshTextColor(refreshTextColor: ResourceColor) {
    this.refreshTextColor = refreshTextColor;
    return this;
  }

  getRefreshTextColor(): ResourceColor {
    return this.refreshTextColor !== undefined ? this.refreshTextColor : 0;
  }

  setRefreshTextSize(refreshTextSize: number | string | Resource) {
    this.refreshTextSize = refreshTextSize;
    return this;
  }

  getRefreshTextSize(): number | string | Resource {
    return this.refreshTextSize !== undefined ? this.refreshTextSize : 0;
  }

  setRefreshAnimDuration(refreshAnimDuration: number) {
    this.refreshAnimDuration = refreshAnimDuration;
    return this;
  }

  getRefreshAnimDuration() {
    return this.refreshAnimDuration;
  }

  setLoadImgHeight(loadImgHeight: number) {
    this.loadImgHeight = loadImgHeight;
    return this;
  }

  getLoadImgHeight(): number {
    return this.loadImgHeight !== undefined ? this.loadImgHeight : 0;
  }

  setLoadBackgroundColor(loadBackgroundColor: ResourceColor) {
    this.loadBackgroundColor = loadBackgroundColor;
    return this;
  }

  getLoadBackgroundColor(): ResourceColor {
    if (this.loadBackgroundColor !== undefined) {
      return this.loadBackgroundColor;
    }
    return 0;
  }

  setLoadTextColor(loadTextColor: ResourceColor) {
    this.loadTextColor = loadTextColor;
    return this;
  }

  getLoadTextColor(): ResourceColor {
    if (this.loadTextColor !== undefined) {
      return this.loadTextColor;
    }
    return 0;
  }

  setLoadTextSize(loadTextSize: number | string | Resource) {
    this.loadTextSize = loadTextSize;
    return this;
  }

  getLoadTextSize(): number | string | Resource {
    if (this.loadTextSize !== undefined) {
      return this.loadTextSize;
    }
    return 0;
  }

  setLoadTextPullUp1(loadTextPullUp1: string) {
    this.loadTextPullUp1 = loadTextPullUp1;
    return this;
  }

  getLoadTextPullUp1() {
    return this.loadTextPullUp1;
  }

  setLoadTextPullUp2(loadTextPullUp2: string) {
    this.loadTextPullUp2 = loadTextPullUp2;
    return this;
  }

  getLoadTextPullUp2() {
    return this.loadTextPullUp2;
  }

  setLoadTextLoading(loadTextLoading: string) {
    this.loadTextLoading = loadTextLoading;
    return this;
  }

  getLoadTextLoading(): string {
    return this.loadTextLoading !== undefined ? this.loadTextLoading : "";
  }
}

使用案例如下:

import { PullToRefresh } from '../pulltorefresh';

build() {
        // 上拉加载  下拉刷新组件
// 需绑定列表或宫格组件
private scroller: Scroller = new Scroller();
  
PullToRefresh({
// 必传项,列表组件所绑定的数据
data: $data,
// 必传项,需绑定传入主体布局内的列表或宫格组件
scroller: this.scroller,
// 必传项,自定义主体布局,内部有列表或宫格组件
customList: () => {
  // 一个用@Builder修饰过的UI方法
  this.getListView();
},
// 可选项,下拉刷新回调
onRefresh: () => {
  return new Promise<string>((resolve, reject) => {
    // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
    setTimeout(() => {
      resolve('刷新成功');
      this.data = this.dataNumbers;
    }, 2000);
  });
},
// 可选项,上拉加载更多回调
onLoadMore: () => {
  return new Promise<string>((resolve, reject) => {
    // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
    setTimeout(() => {
      resolve('');
      this.data.push("增加的条目" + this.data.length);
    }, 2000);
  });
},
customLoad: null,
customRefresh: null,
})
    
}
// 自定义列表里面的组件内容
@Builder
  private getListView() {
    Stack({ alignContent: Alignment.BottomEnd }) {
      List({ space: CommonConstants.LIST_SPACE, scroller: this.scroller }) {
        LazyForEach(this.listData, (item: listData) => {
          ListItem() {
            listItem({
              //listTitle: item.listTitle,
              //listContent: item.listContent,
              //listTime: item.listTime,
              //listImage: item.listImage,
              //listParams: new listParamsClass(item.listParams?.parameter, item.listParams?.router as string, item.listParams?.marker as string, item.listParams?.location as string)
            })
          }
          .backgroundColor($r('app.color.listViewColor'))
          .margin({
            bottom: $r('app.string.list_list_margin_bottom'),
          })
          .borderRadius($r('app.integer.list_list_border_radius'))
        }, (item: listData, index?: number) => JSON.stringify(item) + index);
      }
      .onScrollIndex((first: number) => {
        this.firstIndex = first;
      })
      .width($r('app.string.list_List_width'))
      .backgroundColor($r('app.color.listColor'))
      .edgeEffect(EdgeEffect.None)

      Row() {
        Image($r('app.media.ic_public_backtotop'))
          .width($r('app.integer.back_top_img_size'))
          .height($r('app.integer.back_top_img_size'))
          .opacity($r('app.float.jump_button_opacity'))
      }
      .onClick(() => {
        // if (this.firstIndex >= this.SWITCH_BUTTON) {
        //   this.scroller.scrollTo({
        //     xOffset: CommonConstants.ZERO,
        //     yOffset: CommonConstants.ZERO,
        //     animation: { duration: this.ANIMATION_DURATION, curve: Curve.LinearOutSlowIn }
        //   });
        // }
      })
      .visibility(this.firstIndex >= this.SWITCH_BUTTON ? Visibility.Visible : Visibility.None)
      .justifyContent(FlexAlign.Center)
      .width($r('app.integer.back_top_img_background_size'))
      .height($r('app.integer.back_top_img_background_size'))
      .backgroundColor($r("app.color.tab_sel_color"))
      .borderRadius($r('app.integer.back_top_bag_radius'))
      .margin({
        right: new BreakpointType($r('app.float.page_col_padding_sm'), $r('app.float.page_col_padding_md'),
          $r('app.float.page_col_padding_lg')).getValue(this.currentBreakpoint),
        bottom: $r('app.integer.back_top_margin')
      })
    }
  }
收藏00

登录 后评论。没有帐号? 注册 一个。