领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

鸿蒙开发-下拉刷新和上拉滚动加载

nixiaole 2025-05-11 17:34:44 知识剖析 13 ℃

下拉刷新和上拉滚动加载

开源项目地址:找不到了,有缘再见

核心代码分析:【做了一些改动,简化了一些实现】

import { Const, PageState, RefreshState } from "./Const";

@Component
export struct RefreshLayout {

  build() {
    Row() {
      Image('xxxx')
        .margin({
          left: Const.RefreshLayout_TEXT_MARGIN_LEFT,
        })
        .width(30)
        .height(30)
    }
    .padding({bottom: 4})
    .clip(true)
    .width(Const.FULL_WIDTH)
    .justifyContent(FlexAlign.Center)
    .alignItems(VerticalAlign.Bottom)
  }
}

@Component
export struct LoadingLayout {
  build() {
    Row() {
      Text('正在加载...')
        .margin({
          left: Const.RefreshLayout_TEXT_MARGIN_LEFT,
          bottom: Const.RefreshLayout_TEXT_MARGIN_BOTTOM
        })
        .textAlign(TextAlign.Center)
    }
    .padding({top: 6, bottom: 6})
    .clip(true)
    .width(Const.FULL_WIDTH)
    .justifyContent(FlexAlign.Center)
  }
}
@Component
export struct NoMoreLayout {
  build() {
    Row() {
      Text($r('app.string.prompt_message'))
        .margin({ left: Const.NoMoreLayoutConstant_NORMAL_PADDING })
        .fontSize(Const.NoMoreLayoutConstant_TITLE_FONT)
        .textAlign(TextAlign.Center)
    }
    .width(Const.FULL_WIDTH)
    .justifyContent(FlexAlign.Center)
    .height(Const.CUSTOM_LAYOUT_HEIGHT)
  }
}

@Component
export struct ListWrapComponent {
  @Builder doNothingBuilder() {};

  @BuilderParam mainContent : () => void = this.doNothingBuilder
  refreshCallback: Function = () => {}

  // 监听滚动加载
  @Consume('isLoading') isLoading: boolean
  // 记录手指摁下的y坐标,px单位
  @State downY: number = 0

  // 记录手指滑动过程中的最后的y坐标
  @State lastMoveY: number = 0
  // 记录List滚动条可见范围内的起始索引
  @State startIndex: number = 0
  // 记录List滚动条可见范围内的结束索引
  @State endIndex: number = 0
  // 记录手指滑动过程中的偏移量
  @State offsetY: number = 0
  // 触发下拉刷新的最小手指滑动距离
  @State isRefreshing: boolean = false
  // 控制是否执行滚动加载
  @Prop hasMore: boolean = true
  // 记录是下拉刷新还是滚动加载
  @State isPullRefreshOperation: boolean = false
  // 控制是否能执行滚动加载
  @State isCanLoadMore: boolean = false
  // 控制是否能执行下拉刷新
  @State isCanRefresh: boolean = false
  // 记录页面的状态
  @State pageState: PageState = PageState.Success
  // 触发下拉刷新的最小手指滑动距离
  pullDownRefreshHeight: number = Const.CUSTOM_LAYOUT_HEIGHT;

  // list 组件
  @Builder ListLayout() {
    Stack({alignContent: Alignment.Top}) {
      // 下拉刷新显示的组件
      RefreshLayout()
        .height(px2vp(this.offsetY))
      // 列表放三个项,记录滚动的索引
      List() {
        ListItem()
        ListItem(){
          // 内容通过builder全部放里面
          this.mainContent()
        }
        // 滚动加载显示的组件
        ListItem(){
          if (this.hasMore) {
            LoadingLayout()
          } else {
            NoMoreLayout()
          }
        }
      }
      .width(Const.FULL_WIDTH)
      .height(Const.FULL_HEIGHT)
      // Remove the rebound effect.
      .edgeEffect(EdgeEffect.None)
      .offset({ x: 0, y: px2vp(this.offsetY)})
      .onScrollIndex((start: number, end: number) => {
        // Listen to the first index of the current list.
        this.startIndex = start;
        this.endIndex = end;
      })
      .onReachEnd(() => {
        this.isLoading = true
      })
    }
  }

  // 失败显示的组件
  @Builder FailLayout() {
    Image($r('app.media.none'))
      .height(Const.FULL_HEIGHT)
      .width(Const.FULL_WIDTH)
  }

  pullRefreshState(state: number) {
    let that = this
    switch (state) {
      case RefreshState.DropDown:
        that.isCanRefresh = false;
        that.isRefreshing = false;
        break;
      case RefreshState.Release:
        that.isCanRefresh = true;
        that.isRefreshing = false;
        break;
      case RefreshState.Refreshing:
        that.offsetY = vp2px(that.pullDownRefreshHeight);
        that.isCanRefresh = true;
        that.isRefreshing = true;
        break;
      case RefreshState.Success:
        that.isCanRefresh = true;
        that.isRefreshing = true;
        break;
      case RefreshState.Fail:
        that.isCanRefresh = true;
        that.isRefreshing = true;
        break;
      default:
        break;
    }
  }
  touchMoveLoadMore(event: TouchEvent) {
    let that = this
    if (that.isLoading) {
    // if (that.endIndex === that.newsData.length - 1 || that.endIndex === that.newsData.length) {
      that.offsetY = Math.min(event.touches[0].y - that.downY, vp2px(that.pullDownRefreshHeight));
      if (Math.abs(that.offsetY) > vp2px(that.pullDownRefreshHeight)) {
        that.isCanLoadMore = true;
        that.offsetY = -vp2px(that.pullDownRefreshHeight) + that.offsetY * Const.Y_OFF_SET_COEFFICIENT;
      }
    }
  }
  touchMovePullRefresh(event: TouchEvent) {
    let that = this
    // 滚动条在list的第一项时,才执行下拉刷新
    if (that.startIndex === 0) {
      that.isPullRefreshOperation = true;
      let height = vp2px(that.pullDownRefreshHeight);
      // 计算下拉的距离
      that.offsetY = Math.min(event.touches[0].y - that.downY, vp2px(that.pullDownRefreshHeight));
      // 判断下拉记录大于配置的下拉距离时更新页面状态
      if (that.offsetY >= height) {
        this.pullRefreshState(RefreshState.Release);
        // that.offsetY = height + that.offsetY * Const.Y_OFF_SET_COEFFICIENT;
      } else {
        this.pullRefreshState(RefreshState.DropDown);
      }
      if (that.offsetY < 0) {
        that.offsetY = 0;
        that.isPullRefreshOperation = false;
      }
    }
  }
  // 刷新结束时,动画
  closeRefresh(isRefreshSuccess: boolean) {
    let that = this;
    if (that.isCanRefresh === true) {
      this.pullRefreshState(isRefreshSuccess ? RefreshState.Success : RefreshState.Fail);
    }
    // 显示动画,动画效果相关的配置
    animateTo({
      duration: Const.RefreshConstant_CLOSE_PULL_DOWN_REFRESH_TIME,
      onFinish: () => {
        this.pullRefreshState(RefreshState.DropDown);
        that.isPullRefreshOperation = false;
      }
    }, () => {
      // offsetY 的改变执行会插入动画
      that.offsetY = 0;
    })
  }

  async touchUpPullRefresh() {
    let that = this
    if (that.isCanRefresh === true) {
      that.offsetY = vp2px(that.pullDownRefreshHeight);
      this.pullRefreshState(RefreshState.Refreshing);
      await this.refreshCallback()
      this.closeRefresh(true)
    } else {
      this.closeRefresh(false);
    }
  }

  // 滚动加载
  touchUpLoadMore() {
    let that = this;
    animateTo({
      duration: Const.ANIMATION_DURATION,
      // onFinish: () => {
      //   this.pullRefreshState(RefreshState.DropDown);
      //   that.isPullRefreshOperation = false;
      // }
    }, () => {
      that.offsetY = 0;
    })
    if ((that.isCanLoadMore === true) && (that.hasMore === true)) {
      // 执行异步操作,滚动加载调用后端接口
      that.isLoading = true;
    } else {
      this.closeLoadMore();
    }
  }
  closeLoadMore() {
    let that = this
    that.isCanLoadMore = false;
    that.isLoading = false;
  }

  listTouchEvent(event: TouchEvent) {
    let that = this
    switch (event.type) {
      case TouchType.Down:
        that.downY = event.touches[0].y;
        that.lastMoveY = event.touches[0].y;
        break;
      case TouchType.Move:
        if ((that.isRefreshing === true) || (that.isLoading === true)) {
          return;
        }
        let isDownPull = event.touches[0].y - that.lastMoveY > 0;
        // 判断下拉还是下拉
        if (((isDownPull === true) || (that.isPullRefreshOperation === true)) && (that.isCanLoadMore === false)) {
          // Finger movement, processing pull-down refresh.
          this.touchMovePullRefresh(event);
        } else {
          // Finger movement, processing load more.
          this.touchMoveLoadMore(event);
        }
        that.lastMoveY = event.touches[0].y;
        break;
      case TouchType.Cancel:
        break;
      case TouchType.Up:
        if ((that.isRefreshing === true) || (that.isLoading === true)) {
          return;
        }
        if ((that.isPullRefreshOperation === true)) {
          // Lift your finger and pull down to refresh.
          this.touchUpPullRefresh();
        } else {
          // Fingers up, handle loading more.
          this.touchUpLoadMore();
        }
        break;
      default:
        break;
    }
  }

  build() {
    Row() {
      Column() {
        if (this.pageState === PageState.Success) {
          this.ListLayout()
        } else if (this.pageState === PageState.Loading) {
          LoadingLayout()
        } else {
          this.FailLayout()
        }
      }
      .width(Const.FULL_WIDTH)
      .height(Const.FULL_HEIGHT)
      .justifyContent(FlexAlign.Center)
      .onTouch((event: TouchEvent | undefined) => {
        if (event) {
          if (this.pageState === PageState.Success) {
            this.listTouchEvent(event);
          }
        }
      })
    }
    .height(Const.FULL_HEIGHT)
    .width(Const.FULL_WIDTH)
  }

}



最近发表
标签列表