1# Refresh
2
3 可以进行页面下拉操作并显示刷新动效的容器组件。 
4
5>  **说明:**
6>
7>  - 该组件从API Version 8开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
8>
9>  - 该组件从API Version 12开始支持与垂直滚动的Swiper和Web的联动。当Swiper设置loop属性为true时,Refresh无法和Swiper产生联动。
10
11## 子组件
12
13支持单个子组件。
14
15从API version 11开始,Refresh子组件会跟随手势下拉而下移。
16
17## 接口
18
19Refresh(value: RefreshOptions)
20
21**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
22
23**系统能力:** SystemCapability.ArkUI.ArkUI.Full
24
25**参数:**
26
27| 参数名 | 类型 | 必填 | 说明 |
28| -------- | -------- | -------- | -------- |
29| value |  [RefreshOptions](#refreshoptions对象说明)| 是 | 刷新组件参数。 |
30
31## RefreshOptions对象说明
32
33**系统能力:** SystemCapability.ArkUI.ArkUI.Full
34
35| 名称         | 类型                                      | 必填   | 说明                                     |
36| ---------- | ---------------------------------------- | ---- | ---------------------------------------- |
37| refreshing | boolean                                  | 是    | 组件当前是否处于刷新中状态。true表示处于刷新中状态,false表示未处于刷新中状态。<br/>默认值:false<br/>该参数支持[$$](../../../quick-start/arkts-two-way-sync.md)双向绑定变量。 <br/>**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。|
38| offset<sup>(deprecated)</sup>    | number&nbsp;\|&nbsp;string   | 否    | 下拉起点距离组件顶部的距离。<br/>默认值:16,单位vp <br/>从API version 11开始废弃,无替代接口<br/>**说明:**<br/>offset取值范围[0vp,64vp]。大于64vp按照64vp处理。不支持百分比,不支持负数 。|
39| friction<sup>(deprecated)</sup>   | number&nbsp;\|&nbsp;string               | 否    | 下拉摩擦系数,取值范围为0到100。<br/>默认值:62<br/>-&nbsp;0表示下拉刷新容器不跟随手势下拉而下拉。<br/>-&nbsp;100表示下拉刷新容器紧紧跟随手势下拉而下拉。<br/>-&nbsp;数值越大,下拉刷新容器跟随手势下拉的反应越灵敏。<br/>从API version 11开始废弃,从API version 12开始,可用[pullDownRatio](#pulldownratio12)属性替代。 |
40| builder<sup>10+</sup>    | [CustomBuilder](ts-types.md#custombuilder8) | 否    | 自定义刷新区域显示内容。<br/>**说明:**<br/>API version 10及之前版本,自定义组件的高度限制在64vp之内。API version 11及以后版本没有此限制。 <br/>自定义组件设置了固定高度时,自定义组件会以固定高度显示在刷新区域下方;自定义组件未设置高度时,自定义组件高度会自适应刷新区域高度,会发生自定义组件高度跟随刷新区域变化至0的现象。建议对自定义组件设置最小高度约束来避免自定义组件高度小于预期的情况发生,具体可参照[示例2](#示例2)。 <br/>从API version 12开始,建议使用refreshingContent参数替代builder参数自定义刷新区域显示内容,以避免刷新过程中因自定义组件销毁重建造成的动画中断问题。<br/>**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。|
41| promptText<sup>12+</sup> | [ResourceStr](ts-types.md#resourcestr) | 否 | 设置刷新区域底部显示的自定义文本。<br/>**说明:**<br/>输入文本的限制参考Text组件,使用builder或refreshingContent参数自定义刷新区域显示内容时,promptText不显示。<br/>promptText设置有效时,[refreshOffset](#refreshoffset12)属性默认值为96vp。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。|
42| refreshingContent<sup>12+</sup>    | [ComponentContent](../js-apis-arkui-ComponentContent.md) | 否    | 自定义刷新区域显示内容。<br/>**说明:**<br/>与builder参数同时设置时builder参数不生效。<br/>自定义组件设置了固定高度时,自定义组件会以固定高度显示在刷新区域下方;自定义组件未设置高度时,自定义组件高度会自适应刷新区域高度,会发生自定义组件高度跟随刷新区域变化至0的现象。建议对自定义组件设置最小高度约束来避免自定义组件高度小于预期的情况发生,具体可参照[示例5](#示例5)。 <br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。|
43
44## 属性
45
46支持[通用属性](ts-universal-attributes-size.md)外,还支持以下属性:
47
48### refreshOffset<sup>12+</sup>
49
50refreshOffset(value: number)
51
52设置触发刷新的下拉偏移量。
53
54**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
55
56**系统能力:** SystemCapability.ArkUI.ArkUI.Full
57
58**参数:** 
59
60| 参数名 | 类型                                        | 必填 | 说明                                                       |
61| ------ | ------------------------------------------- | ---- | ---------------------------------------------------------- |
62| value  | number |  是 | 下拉偏移量,单位vp。<br/>默认值:未设置[promptText](#refreshoptions对象说明)参数时为64vp,设置了[promptText](#refreshoptions对象说明)参数时为96vp。 <br/>如果取值为0或负数的时候此接口采用默认值。|
63
64### pullToRefresh<sup>12+</sup>
65
66pullToRefresh(value: boolean)
67
68设置当下拉距离超过[refreshOffset](#refreshoffset12)时是否能触发刷新。
69
70**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
71
72**系统能力:** SystemCapability.ArkUI.ArkUI.Full
73
74**参数:** 
75
76| 参数名 | 类型                                        | 必填 | 说明                                                       |
77| ------ | ------------------------------------------- | ---- | ---------------------------------------------------------- |
78| value  | boolean |  是 | 当下拉距离超过[refreshOffset](#refreshoffset12)时是否能触发刷新。true表示能触发刷新,false表示不能触发刷新。<br/>默认值:true |
79
80### pullDownRatio<sup>12+</sup>
81
82pullDownRatio(ratio: [Optional](ts-universal-attributes-custom-property.md#optional12)\<number>)
83
84设置下拉跟手系数。
85
86**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
87
88**系统能力:** SystemCapability.ArkUI.ArkUI.Full
89
90**参数:**
91
92| 参数名 | 类型                                        | 必填 | 说明                                                       |
93| ------ | ------------------------------------------- | ---- | ---------------------------------------------------------- |
94| ratio  | [Optional](ts-universal-attributes-custom-property.md#optional12)\<number> |  是 | 下拉跟手系数。数值越大,跟随手势下拉的反应越灵敏。0表示不跟随手势下拉,1表示等比例跟随手势下拉。<br/>没有设置或设置为undefined时,默认使用动态下拉跟手系数,下拉距离越大,跟手系数越小。<br/>有效值为0-1之间的值,小于0的值会被视为0,大于1的值会被视为1。
95
96## 事件
97
98除支持[通用事件](ts-universal-events-click.md)外,还支持以下事件:
99
100### onStateChange
101
102onStateChange(callback: (state: RefreshStatus) => void)
103
104当前刷新状态变更时,触发回调。
105
106**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
107
108**系统能力:** SystemCapability.ArkUI.ArkUI.Full
109
110**参数:** 
111
112| 参数名 | 类型                                    | 必填 | 说明       |
113| ------ | --------------------------------------- | ---- | ---------- |
114| state  | [RefreshStatus](#refreshstatus枚举说明) | 是   | 刷新状态。 |
115
116### onRefreshing
117
118onRefreshing(callback: () => void)
119
120进入刷新状态时触发回调。
121
122**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
123
124**系统能力:** SystemCapability.ArkUI.ArkUI.Full
125
126### onOffsetChange<sup>12+</sup>
127
128onOffsetChange(callback: Callback\<number>)
129
130下拉距离发生变化时触发回调。
131
132**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
133
134**系统能力:** SystemCapability.ArkUI.ArkUI.Full
135
136**参数:** 
137
138| 参数名 | 类型                                    | 必填 | 说明       |
139| ------ | --------------------------------------- | ---- | ---------- |
140| value  | number | 是   | 下拉距离。<br/>单位:vp |
141
142
143## RefreshStatus枚举说明
144
145**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
146
147**系统能力:** SystemCapability.ArkUI.ArkUI.Full
148
149| 名称       | 值       | 说明                 |
150| -------- | -------- | -------------------- |
151| Inactive | 0 | 默认未下拉状态。             |
152| Drag     | 1 | 下拉中,下拉距离小于刷新距离。      |
153| OverDrag | 2 | 下拉中,下拉距离超过刷新距离。      |
154| Refresh  | 3 | 下拉结束,回弹至刷新距离,进入刷新中状态。 |
155| Done     | 4 | 刷新结束,返回初始状态(顶部)。     |
156
157
158## 示例 
159
160### 示例1
161
162刷新区域使用默认样式。
163
164```ts
165// xxx.ets
166@Entry
167@Component
168struct RefreshExample {
169  @State isRefreshing: boolean = false
170  @State arr: String[] = ['0', '1', '2', '3', '4','5','6','7','8','9','10']
171
172  build() {
173    Column() {
174      Refresh({ refreshing: $$this.isRefreshing}) {
175        List() {
176          ForEach(this.arr, (item: string) => {
177            ListItem() {
178              Text('' + item)
179                .width('70%').height(80).fontSize(16).margin(10)
180                .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
181            }
182          }, (item: string) => item)
183        }
184        .onScrollIndex((first: number) => {
185          console.info(first.toString())
186        })
187        .width('100%')
188        .height('100%')
189        .alignListItem(ListItemAlign.Center)
190        .scrollBar(BarState.Off)
191      }
192      .onStateChange((refreshStatus: RefreshStatus) => {
193        console.info('Refresh onStatueChange state is ' + refreshStatus)
194      })
195      .onOffsetChange((value: number) => {
196        console.info('Refresh onOffsetChange offset:' + value)
197      })
198      .onRefreshing(() => {
199        setTimeout(() => {
200          this.isRefreshing = false
201        }, 2000)
202        console.log('onRefreshing test')
203      })
204      .backgroundColor(0x89CFF0)
205      .refreshOffset(64)
206      .pullToRefresh(true)
207    }
208  }
209}
210```
211
212![zh-cn_image_refresh_example1](figures/zh-cn_image_refresh_example1.gif)
213
214### 示例2 
215
216通过builder参数自定义刷新区域显示内容。
217
218```ts
219// xxx.ets
220@Entry
221@Component
222struct RefreshExample {
223  @State isRefreshing: boolean = false
224  @State arr: String[] = ['0', '1', '2', '3', '4','5','6','7','8','9','10']
225  @Builder
226  customRefreshComponent()
227  {
228    Stack()
229    {
230      Row()
231      {
232        LoadingProgress().height(32)
233        Text("Refreshing...").fontSize(16).margin({left:20})
234      }
235      .alignItems(VerticalAlign.Center)
236    }
237    .align(Alignment.Center)
238    .clip(true)
239    .constraintSize({minHeight:32}) // 设置最小高度约束保证自定义组件高度随刷新区域高度变化时自定义组件高度不会低于minHeight
240    .width("100%")
241  }
242
243  build() {
244    Column() {
245      Refresh({ refreshing: $$this.isRefreshing,builder:this.customRefreshComponent()}) {
246        List() {
247          ForEach(this.arr, (item: string) => {
248            ListItem() {
249              Text('' + item)
250                .width('70%').height(80).fontSize(16).margin(10)
251                .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
252            }
253          }, (item: string) => item)
254        }
255        .onScrollIndex((first: number) => {
256          console.info(first.toString())
257        })
258        .width('100%')
259        .height('100%')
260        .alignListItem(ListItemAlign.Center)
261        .scrollBar(BarState.Off)
262      }
263      .backgroundColor(0x89CFF0)
264      .pullToRefresh(true)
265      .refreshOffset(64)
266      .onStateChange((refreshStatus: RefreshStatus) => {
267        console.info('Refresh onStatueChange state is ' + refreshStatus)
268      })
269      .onRefreshing(() => {
270        setTimeout(() => {
271          this.isRefreshing = false
272        }, 2000)
273        console.log('onRefreshing test')
274      })
275    }
276  }
277}
278```
279
280![zh-cn_image_refresh_example2](figures/zh-cn_image_refresh_example2.gif)
281
282### 示例3
283
284边界刷新回弹效果。
285
286```ts
287// xxx.ets
288@Entry
289@Component
290struct ListRefreshLoad {
291  @State arr: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
292  @State refreshing: boolean = false;
293  @State refreshOffset: number = 0;
294  @State refreshState: RefreshStatus = RefreshStatus.Inactive;
295  @State canLoad: boolean = false;
296  @State isLoading: boolean = false;
297
298  @Builder
299  refreshBuilder() {
300    Stack({ alignContent: Alignment.Bottom }) {
301      // 可以通过刷新状态控制是否存在Progress组件
302      // 当刷新状态处于下拉中或刷新中状态时Progress组件才存在
303      if (this.refreshState != RefreshStatus.Inactive && this.refreshState != RefreshStatus.Done) {
304        Progress({ value: this.refreshOffset, total: 64, type: ProgressType.Ring })
305          .width(32).height(32)
306          .style({ status: this.refreshing ? ProgressStatus.LOADING : ProgressStatus.PROGRESSING })
307          .margin(10)
308      }
309    }
310    .clip(true)
311    .height("100%")
312    .width("100%")
313  }
314
315  @Builder
316  footer() {
317    Row() {
318      LoadingProgress().height(32).width(48)
319      Text("加载中")
320    }.width("100%")
321    .height(64)
322    .justifyContent(FlexAlign.Center)
323    // 当不处于加载中状态时隐藏组件
324    .visibility(this.isLoading ? Visibility.Visible : Visibility.Hidden)
325  }
326
327  build() {
328    Refresh({ refreshing: $$this.refreshing, builder: this.refreshBuilder() }) {
329      List() {
330        ForEach(this.arr, (item: number) => {
331          ListItem() {
332            Text('' + item)
333              .width('100%')
334              .height(80)
335              .fontSize(16)
336              .textAlign(TextAlign.Center)
337              .backgroundColor(0xFFFFFF)
338          }.borderWidth(1)
339        }, (item: string) => item)
340
341        ListItem() {
342          this.footer();
343        }
344      }
345      .onScrollIndex((start: number, end: number) => {
346        // 当达到列表末尾时,触发新数据加载
347        if (this.canLoad && end >= this.arr.length - 1) {
348          this.canLoad = false;
349          this.isLoading = true;
350          // 模拟新数据加载
351          setTimeout(() => {
352            for (let i = 0; i < 10; i++) {
353              this.arr.push(this.arr.length);
354              this.isLoading = false;
355            }
356          }, 700)
357        }
358      })
359      .onScrollFrameBegin((offset: number, state: ScrollState) => {
360        // 只有当向上滑动时触发新数据加载
361        if (offset > 5 && !this.isLoading) {
362          this.canLoad = true;
363        }
364        return { offsetRemain: offset };
365      })
366      .scrollBar(BarState.Off)
367      // 开启边缘滑动效果
368      .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
369    }
370    .width('100%')
371    .height('100%')
372    .backgroundColor(0xDCDCDC)
373    .onOffsetChange((offset: number) => {
374      this.refreshOffset = offset;
375    })
376    .onStateChange((state: RefreshStatus) => {
377      this.refreshState = state;
378    })
379    .onRefreshing(() => {
380      // 模拟数据刷新
381      setTimeout(() => {
382        this.refreshing = false;
383      }, 2000)
384    })
385  }
386}
387```
388
389![refresh_boundary_resilience](figures/refresh_boundary_resilience.gif)
390
391### 示例4
392
393通过promptText参数设置刷新区域显示文本。
394
395```ts
396// xxx.ets
397@Entry
398@Component
399struct RefreshExample {
400  @State isRefreshing: boolean = false
401  @State promptText: string = "Refreshing..."
402  @State arr: String[] = ['0', '1', '2', '3', '4','5','6','7','8','9','10']
403
404  build() {
405    Column() {
406      Refresh({ refreshing: $$this.isRefreshing, promptText: this.promptText}) {
407        List() {
408          ForEach(this.arr, (item: string) => {
409            ListItem() {
410              Text('' + item)
411                .width('70%').height(80).fontSize(16).margin(10)
412                .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
413            }
414          }, (item: string) => item)
415        }
416        .onScrollIndex((first: number) => {
417          console.info(first.toString())
418        })
419        .width('100%')
420        .height('100%')
421        .alignListItem(ListItemAlign.Center)
422        .scrollBar(BarState.Off)
423      }
424      .backgroundColor(0x89CFF0)
425      .pullToRefresh(true)
426      .refreshOffset(96)
427      .onStateChange((refreshStatus: RefreshStatus) => {
428        console.info('Refresh onStatueChange state is ' + refreshStatus)
429      })
430      .onOffsetChange((value: number) => {
431        console.info('Refresh onOffsetChange offset:' + value)
432      })
433      .onRefreshing(() => {
434        setTimeout(() => {
435          this.isRefreshing = false
436        }, 2000)
437        console.log('onRefreshing test')
438      })
439    }
440  }
441}
442```
443
444![zh-cn_image_refresh_example4](figures/zh-cn_image_refresh_example4.gif)
445
446### 示例5
447
448通过refreshingContent参数自定义刷新区域显示内容
449
450```ts
451import { ComponentContent } from '@ohos.arkui.node';
452
453class Params {
454  refreshStatus: RefreshStatus = RefreshStatus.Inactive
455
456  constructor(refreshStatus: RefreshStatus) {
457    this.refreshStatus = refreshStatus;
458  }
459}
460
461@Builder
462function customRefreshingContent(params:Params) {
463  Stack() {
464    Row() {
465      LoadingProgress().height(32)
466      Text("refreshStatus: "+params.refreshStatus).fontSize(16).margin({left:20})
467    }
468    .alignItems(VerticalAlign.Center)
469  }
470  .align(Alignment.Center)
471  .clip(true)
472  .constraintSize({minHeight:32}) // 设置最小高度约束保证自定义组件高度随刷新区域高度变化时自定义组件高度不会低于minHeight
473  .width("100%")
474}
475
476@Entry
477@Component
478struct RefreshExample {
479  @State isRefreshing: boolean = false
480  @State arr: String[] = ['0', '1', '2', '3', '4','5','6','7','8','9','10']
481  @State refreshStatus: RefreshStatus = RefreshStatus.Inactive
482  private contentNode?: ComponentContent<Object> = undefined
483
484  aboutToAppear():void {
485    let uiContext = this.getUIContext();
486    this.contentNode = new ComponentContent(uiContext, wrapBuilder(customRefreshingContent), new Params(this.refreshStatus))
487  }
488
489  build() {
490    Column() {
491      Refresh({ refreshing: $$this.isRefreshing, refreshingContent:this.contentNode}) {
492        List() {
493          ForEach(this.arr, (item: string) => {
494            ListItem() {
495              Text('' + item)
496                .width('70%').height(80).fontSize(16).margin(10)
497                .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
498            }
499          }, (item: string) => item)
500        }
501        .onScrollIndex((first: number) => {
502          console.info(first.toString())
503        })
504        .width('100%')
505        .height('100%')
506        .alignListItem(ListItemAlign.Center)
507        .scrollBar(BarState.Off)
508      }
509      .backgroundColor(0x89CFF0)
510      .pullToRefresh(true)
511      .refreshOffset(96)
512      .onStateChange((refreshStatus: RefreshStatus) => {
513        this.refreshStatus = refreshStatus
514        this.contentNode?.update(new Params(this.refreshStatus)) // 更新自定义组件内容
515        console.info('Refresh onStatueChange state is ' + refreshStatus)
516      })
517      .onRefreshing(() => {
518        setTimeout(() => {
519          this.isRefreshing = false
520        }, 2000)
521        console.log('onRefreshing test')
522      })
523    }
524  }
525}
526```
527![zh-cn_image_refresh_example5](figures/zh-cn_image_refresh_example5.gif)
528
529### 示例6
530
531通过[pullDownRatio](#pulldownratio12)属性和[onOffsetChange](#onoffsetchange12)事件实现最大下拉距离
532
533```ts
534import { ComponentContent } from '@ohos.arkui.node';
535
536@Builder
537function customRefreshingContent() {
538  Stack() {
539    Row() {
540      LoadingProgress().height(32)
541    }
542    .alignItems(VerticalAlign.Center)
543  }
544  .align(Alignment.Center)
545  .clip(true)
546  .constraintSize({minHeight:32}) // 设置最小高度约束保证自定义组件高度随刷新区域高度变化时自定义组件高度不会低于minHeight
547  .width("100%")
548}
549
550@Entry
551@Component
552struct RefreshExample {
553  @State isRefreshing: boolean = false
554  @State arr: String[] = ['0', '1', '2', '3', '4','5','6','7','8','9','10']
555  @State maxRefreshingHeight: number = 100.0
556  @State ratio: number = 1
557  private contentNode?: ComponentContent<Object> = undefined
558
559  aboutToAppear():void {
560    let uiContext = this.getUIContext();
561    this.contentNode = new ComponentContent(uiContext, wrapBuilder(customRefreshingContent))
562  }
563
564  build() {
565    Column() {
566      Refresh({ refreshing: $$this.isRefreshing, refreshingContent:this.contentNode}) {
567        List() {
568          ForEach(this.arr, (item: string) => {
569            ListItem() {
570              Text('' + item)
571                .width('70%').height(80).fontSize(16).margin(10)
572                .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
573            }
574          }, (item: string) => item)
575        }
576        .onScrollIndex((first: number) => {
577          console.info(first.toString())
578        })
579        .width('100%')
580        .height('100%')
581        .alignListItem(ListItemAlign.Center)
582        .scrollBar(BarState.Off)
583      }
584      .backgroundColor(0x89CFF0)
585      .pullDownRatio(this.ratio)
586      .pullToRefresh(true)
587      .refreshOffset(64)
588      .onOffsetChange((offset: number)=>{
589          this.ratio = 1 - Math.pow((offset / this.maxRefreshingHeight), 3) // 越接近最大距离,下拉跟手系数越小
590      })
591      .onStateChange((refreshStatus: RefreshStatus) => {
592        console.info('Refresh onStatueChange state is ' + refreshStatus)
593      })
594      .onRefreshing(() => {
595        setTimeout(() => {
596          this.isRefreshing = false
597        }, 2000)
598        console.log('onRefreshing test')
599      })
600    }
601  }
602}
603```
604
605![zh-cn_image_refresh_example6](figures/zh-cn_image_refresh_example6.gif)