HarmonyOS 鸿蒙Next中List组件在滚动到顶部时如何实现“下拉刷新”动画?

HarmonyOS 鸿蒙Next中List组件在滚动到顶部时如何实现“下拉刷新”动画? 想实现类似微信的下拉刷新,带箭头旋转和文字提示。ArkUI 有内置支持吗?

9 回复

【背景知识】

【解决方案】

开发者可以直接使用Refresh实现刷新,也可以通过设置自定义刷新区域显示内容,实现带箭头旋转和文字提示:

import { ComponentContent } from '@ohos.arkui.node';

const PULL_REFRESHING = '刷新中...';

@Entry
@Component
struct MainTab {
  @Builder
  barBars() {
    Text('BAR').margin({ top: 20, bottom: 20 }).fontSize(18).fontWeight('bold');
  }

  @Builder
  barContents() {
    Tabs() {
      TabContent() {
        RefreshExample();
      };
    }
    .layoutWeight(1)
    .width('100%')
    .barHeight(0)
    .barMode(BarMode.Scrollable)
    .barOverlap(false)
    .fadingEdge(false);
  }

  build() {
    Column() {
      TopSvBar({
        bars: this.barBars,
        content: this.barContents
      });
    }.width('100%').height('100%');
  }
}

@Component
struct TopSvBar {
  @BuilderParam bars: () => void = this.barContents;
  @BuilderParam content: () => void = this.barContents;

  @Builder
  barContents() {
  }

  build() {
    Column() {
      Text('标题').margin({ top: 20, bottom: 20 }).fontSize(18).fontWeight('bold');
      Column() {
        this.bars();
      }.height(50);

      Column() {
        this.content();
      }.layoutWeight(1);
    };
  }
}

class Params {
  angle: number = 0;

  constructor(angle: number) {
    this.angle = angle;
  }
}


@Builder
function customRefreshingContent(params: Params) {
  Row() {
    Text(){
      SymbolSpan($r('sys.symbol.arrow_clockwise'))
        .fontWeight(FontWeight.Lighter)
        .fontSize(20)
    }
      .rotate({ angle: params.angle })
      .width(20)
      .height(20);
    Text('刷新中').fontSize(16).margin({ left: 20 });
  }
  .alignItems(VerticalAlign.Center)
  .width('100%')
  .justifyContent(FlexAlign.Center)
  .constraintSize({ minHeight: 32 });
}


@Component
struct RefreshExample {
  @State isRefreshing: boolean = false;
  @State refreshString: string = '下拉刷新';
  private contentNode?: ComponentContent<Object> = undefined;
  private params: Params = new Params(0);
  maxRefreshingHeight: number = 200.0;
  @State ratio: number = 1;
  @State arr: String[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
  colors: [ResourceColor | LinearGradient, number][] = []; // 组件进度条颜色配置


  // 设置进度条颜色
  setColors(percentage: number) {
    this.colors = [['#FF585E70', percentage], ['#ffffff', 1 - percentage]];
  }

  @State @Watch('valueChange') currentValue: number = 0; // 当前值

  // 计算当前值占总的百分比
  getPercentage(): number {
    return Math.min(this.currentValue / 100, 1);
  }

  // 当前值改变监听
  valueChange() {
    this.setColors(this.getPercentage());
  }

  aboutToAppear(): void {
    let uiContext = this.getUIContext();
    this.contentNode = new ComponentContent(uiContext, wrapBuilder(customRefreshingContent), this.params);
  }

  build() {
    Column() {
      Refresh({ refreshing: $$this.isRefreshing, refreshingContent: this.contentNode }) {
        List() {
          ForEach(this.arr, (item: string) => {
            ListItem() {
              Text('' + item)
                .width('70%')
                .height(80)
                .fontSize(16)
                .margin(10)
                .textAlign(TextAlign.Center)
                .borderRadius(10)
                .backgroundColor(0xFFFFFF);
            };
          }, (item: string) => item);
        }
        .onScrollIndex((first: number) => {
          console.info(first.toString());
        })
        .width('100%')
        .height('100%')
        .alignListItem(ListItemAlign.Center)
        .scrollBar(BarState.Off);
      }
      .pullDownRatio(this.ratio)
      .backgroundColor(0x89CFF0)
      .pullToRefresh(true)
      .refreshOffset(64)
      .onOffsetChange((offset: number) => {
        // 越接近最大距离,下拉跟手系数越小
        this.ratio = 1 - Math.pow((offset / this.maxRefreshingHeight), 3);
      })
      .onStateChange((refreshStatus: RefreshStatus) => {
        if (refreshStatus === 3) {
          this.refreshString = PULL_REFRESHING;
          this.getUIContext().animateTo({ curve: Curve.Linear, iterations: 5, duration: 1000 }, () => {
            this.params.angle = 360;
            // 更新自定义组件内容
            this.contentNode?.update(this.params);
          });
        }
      })
      .onRefreshing(() => {
        setTimeout(() => {
          this.isRefreshing = false;
          this.contentNode?.update(this.params.angle = 0);
        }, 5000);
        console.info('onRefreshing test');
      });
    };
  }
}

更多关于HarmonyOS 鸿蒙Next中List组件在滚动到顶部时如何实现“下拉刷新”动画?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这种下拉刷新没必要自己实现,重复造轮子了。

OpenHarmony三方库中心仓 可以从三分库里面直接搜。

参考代码如下:

// xxx.ets
@Entry
@Component
struct RefreshExample {
  @State isRefreshing: boolean = false;
  @State promptText: string = "Refreshing...";
  @State arr: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];

  build() {
    Column() {
      Refresh({ refreshing: $$this.isRefreshing, promptText: this.promptText }) {
        List() {
          ForEach(this.arr, (item: string) => {
            ListItem() {
              Text(item)
                .width('70%')
                .height(80)
                .fontSize(16)
                .margin(10)
                .textAlign(TextAlign.Center)
                .borderRadius(10)
                .backgroundColor(0xFFFFFF)
            }
          }, (item: string) => item)
        }
        .onScrollIndex((first: number) => {
          console.info(first.toString());
        })
        .width('100%')
        .height('100%')
        .alignListItem(ListItemAlign.Center)
        .scrollBar(BarState.Off)
      }
      .backgroundColor(0x89CFF0)
      .pullToRefresh(true)
      .refreshOffset(96)
      .onStateChange((refreshStatus: RefreshStatus) => {
        console.info('Refresh onStatueChange state is ' + refreshStatus);
      })
      .onOffsetChange((value: number) => {
        console.info('Refresh onOffsetChange offset:' + value);
      })
      .onRefreshing(() => {
        setTimeout(() => {
          this.isRefreshing = false;
        }, 2000)
        console.info('onRefreshing test');
      })
    }
  }
}

效果如图: previewableImage

可以参考文档 List组件 - 下拉刷新与上拉加载

建议根据 Codelabs 中的 实现新闻数据加载功能 - 6.下拉刷新 作为示例代码参考依据。

Refresh 可以进行页面下拉操作并显示刷新动效的容器组件。

通过builder参数自定义刷新区域显示内容。可根据onStateChange回调参数RefreshStatus,在下拉过程中改变箭头方向和文字提示,以及刷新中动效。

// xxx.ets
@Entry
@Component
struct RefreshExample {
  @State isRefreshing: boolean = false;
  @State arr: String[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];

  @Builder
  customRefreshComponent() {
    Stack() {
      Row() {
        LoadingProgress().height(32)
        Text("Refreshing...").fontSize(16).margin({ left: 20 })
      }
      .alignItems(VerticalAlign.Center)
    }
    .align(Alignment.Center)
    .clip(true)
    // 设置最小高度约束保证自定义组件高度随刷新区域高度变化时自定义组件高度不会低于minHeight。
    .constraintSize({ minHeight: 32 })
    .width("100%")
  }

  build() {
    Column() {
      Refresh({ refreshing: $$this.isRefreshing, builder: this.customRefreshComponent() }) {
        List() {
          ForEach(this.arr, (item: string) => {
            ListItem() {
              Text('' + item)
                .width('70%')
                .height(80)
                .fontSize(16)
                .margin(10)
                .textAlign(TextAlign.Center)
                .borderRadius(10)
                .backgroundColor(0xFFFFFF)
            }
          }, (item: string) => item)
        }
        .onScrollIndex((first: number) => {
          console.info(first.toString());
        })
        .width('100%')
        .height('100%')
        .alignListItem(ListItemAlign.Center)
        .scrollBar(BarState.Off)
      }
      .backgroundColor(0x89CFF0)
      .pullToRefresh(true)
      .refreshOffset(64)
      .onStateChange((refreshStatus: RefreshStatus) => {
        console.info('Refresh onStatueChange state is ' + refreshStatus);
      })
      .onRefreshing(() => {
        setTimeout(() => {
          this.isRefreshing = false;
        }, 2000)
        console.info('onRefreshing test');
      })
    }
  }
}

你想找的是这个:Refresh,里面有实例可以直接用

在HarmonyOS Next中,List组件实现下拉刷新动画需使用@State装饰器管理刷新状态,结合onTouch事件监听手势。通过if条件渲染自定义刷新组件(如Refresh),利用scrollToIndex方法滚动到顶部时触发动画。动画效果可使用animateTo方法实现平滑过渡。

在HarmonyOS Next的ArkUI中,可以通过Refresh组件实现下拉刷新功能,它内置了拉动提示、刷新动画等能力。

核心实现步骤:

  1. 使用Refresh组件包裹列表:将List组件作为Refresh的子组件。
  2. 配置刷新参数
    • onRefresh:触发刷新时的回调函数。
    • offset:刷新触发距离(默认值16vp)。
    • refreshType:设置刷新组件类型,PullDown(默认)即为下拉刷新。
  3. 自定义刷新UI:通过builder属性自定义刷新过程中的提示内容(如箭头图标、文字)。

示例代码:

@Entry
@Component
struct RefreshExample {
  @State isRefreshing: boolean = false
  @State dataArray: string[] = ['Item 1', 'Item 2', 'Item 3']

  build() {
    Column() {
      Refresh({
        onRefresh: () => {
          this.isRefreshing = true
          // 模拟网络请求
          setTimeout(() => {
            this.dataArray.unshift('New Item')
            this.isRefreshing = false
          }, 2000)
        },
        offset: 120,
        builder: (refreshState: RefreshStatus, percent: number) => {
          this.CustomRefreshUI(refreshState, percent)
        }
      }) {
        List() {
          ForEach(this.dataArray, (item: string) => {
            ListItem() {
              Text(item).padding(20)
            }
          })
        }
      }
    }
  }

  @Builder
  CustomRefreshUI(refreshState: RefreshStatus, percent: number) {
    Column() {
      if (refreshState === RefreshStatus.Inactive) {
        Image($r('app.media.arrow_down'))
          .rotate({ angle: 0 })
        Text('下拉刷新')
      } else if (refreshState === RefreshStatus.Drag) {
        Image($r('app.media.arrow_down'))
          .rotate({ angle: percent >= 1.0 ? 180 : 0 }) // 到达阈值时箭头翻转
        Text(percent >= 1.0 ? '释放立即刷新' : '继续下拉')
      } else if (refreshState === RefreshStatus.Refreshing) {
        LoadingProgress() // 内置加载动画
        Text('刷新中...')
      } else {
        Text('刷新完成')
      }
    }
    .padding(20)
  }
}

关键说明:

  • RefreshStatus枚举提供Inactive(未触发)、Drag(拖动中)、Refreshing(刷新中)、Success(成功)四种状态。
  • percent参数表示当前下拉距离与触发距离的比值(≥1.0时触发刷新)。
  • 箭头旋转通过rotate属性根据percent值控制,达到阈值时翻转180度。
  • 刷新动画可使用内置LoadingProgress组件或自定义Lottie动画。

注意事项:

  • 列表滚动到顶部时继续下拉才会触发。
  • 刷新完成后需手动将isRefreshing设为false以结束动画。
  • 可结合refreshStatepercent实现更精细的动画效果。

此方案直接使用ArkUI原生组件,无需第三方库即可实现标准的下拉刷新交互。

回到顶部