RefreshView.ets 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { ToolsHelper } from '../../utils/ToolsHelper'
  2. import { inspector } from '@kit.ArkUI'
  3. import { RefreshController } from './RefreshController'
  4. @Component
  5. export struct RefreshView {
  6. @Link isLoading: boolean
  7. @Prop @Watch('change') data: Array<ESObject>
  8. @Require keyGenerator?: (item: ESObject, index: number) => string
  9. //是否自动调用`onRefresh`方法,默认为`true`,加载组件会自动调用一次`onRefresh`
  10. init: boolean = true
  11. // 第一次加载完成,是否定位到底部,默认`false`,定位到顶部
  12. positioningToBottom: boolean = false
  13. pageSize: number = 10
  14. onLoadMore?: (pageNum: number) => void
  15. onRefresh?: () => void
  16. controller: RefreshController | null = null
  17. private startY: number = 0
  18. private endY: number = 0
  19. private lastNum: number = 0
  20. private _openMore: boolean = false
  21. private _oTime: number = 0
  22. private _listener: inspector.ComponentObserver = inspector.createComponentObserver('Refresh_View_List');
  23. private _scroller: Scroller = new Scroller()
  24. private onDrawComplete = () => {
  25. this._isLoad = false
  26. this.isLoading = false
  27. if (!this._firstFinish && this.positioningToBottom) {
  28. this._firstFinish = true
  29. this.toBottom()
  30. } else {
  31. if (this._toTop) {
  32. this.toTop()
  33. this._toTop = false
  34. } else if (this._toBottom) {
  35. this.toBottom()
  36. this._toBottom = false
  37. } else if (this._toIndex) {
  38. this.toIndex()
  39. this._toIndex = false
  40. }
  41. }
  42. };
  43. private _firstFinish = false
  44. private _isLoad = false
  45. private _toTop = false
  46. private _toIndex = false
  47. private _toBottom = false
  48. change() {
  49. this._isLoad = true
  50. }
  51. aboutToAppear(): void {
  52. if (this.init) {
  53. this.onRefresh && this.onRefresh()
  54. }
  55. if (this.controller) {
  56. this.controller.toTop = (smooth?: boolean) => {
  57. this.toTop(smooth)
  58. }
  59. this.controller.toBottom = (smooth?: boolean) => {
  60. this.toBottom(smooth)
  61. }
  62. this.controller.toIndex = (value: number, smooth?: boolean, align?: ScrollAlign) => {
  63. this.toIndex(value, smooth, align)
  64. }
  65. this.controller.isAtEnd = () => this._scroller.isAtEnd()
  66. }
  67. this._listener.on('draw', this.onDrawComplete)
  68. }
  69. @State value: number = -1
  70. @State smooth?: boolean = false
  71. @State aligns?: ScrollAlign = undefined
  72. toIndex(value?: number, smooth?: boolean, align?: ScrollAlign) {
  73. if (!this._isLoad) {
  74. if (value || this.value != -1 || value === 0) {
  75. this._scroller.scrollToIndex(value ?? this.value, smooth, align)
  76. }
  77. } else {
  78. this.value = value ?? this.value
  79. this.smooth = smooth
  80. this.aligns = align
  81. this._toIndex = true
  82. }
  83. }
  84. toTop(smooth?: boolean) {
  85. if (!this._isLoad) {
  86. this._scroller.scrollToIndex(0, smooth, ScrollAlign.START)
  87. } else {
  88. this._toTop = true
  89. }
  90. }
  91. toBottom(smooth?: boolean) {
  92. if (!this._isLoad) {
  93. this._scroller.scrollToIndex(this.data.length, smooth, ScrollAlign.END)
  94. } else {
  95. this._toBottom = true
  96. }
  97. }
  98. aboutToDisappear(): void {
  99. this._listener.off('draw', this.onDrawComplete);
  100. }
  101. @Builder
  102. doNothingBuilder() {
  103. };
  104. // 使用父组件@Builder装饰的方法初始化子组件@BuilderParam
  105. @BuilderParam customBuilderParam: (item: ESObject, index: number) => void
  106. @BuilderParam headerBuilderParam: () => void = this.doNothingBuilder;
  107. @BuilderParam footerBuilderParam: () => void = this.doNothingBuilder;
  108. build() {
  109. Refresh({ refreshing: $$this.isLoading }) {
  110. if (this.data && this.data.length > 0) {
  111. List({
  112. scroller: this._scroller,
  113. }) {
  114. ListItem() {
  115. this.headerBuilderParam()
  116. }
  117. ForEach(this.data ?? [], (item: ESObject, index: number) => {
  118. ListItem() {
  119. this.customBuilderParam(item, index)
  120. }
  121. }, (item: ESObject, index: number) => this.keyGenerator!(item, index))
  122. ListItem() {
  123. this.footerBuilderParam()
  124. }
  125. }
  126. .onTouch((event: TouchEvent) => {
  127. const e1 = event.touches[0]
  128. switch (event.type) {
  129. case TouchType.Down:
  130. this._openMore = this.data && this.lastNum >= this.data.length && !this.isLoading
  131. this.startY = e1.y
  132. break
  133. case TouchType.Up:
  134. this.endY = e1.y
  135. if (this.endY - this.startY < -100 && this._openMore) {
  136. const v = this.data && (this.data.length % this.pageSize) === 0
  137. if (v) {
  138. const cTime = new Date().getTime()
  139. // 2024.11.21 测试觉得刷新太多了,暂时改为1秒5间隔
  140. if (cTime - this._oTime > 1500) {
  141. this.onLoadMore && this.onLoadMore(Math.floor(this.data.length / this.pageSize))
  142. this._oTime = cTime
  143. }
  144. } else {
  145. this.onLoadMore && ToolsHelper.showMessage('没有更多数据了')
  146. }
  147. }
  148. break
  149. }
  150. })
  151. .onScrollIndex((first: number, last: number) => {
  152. this.lastNum = last + 1
  153. })
  154. .id('Refresh_View_List')
  155. .width('100%')
  156. .height('100%')
  157. .alignListItem(ListItemAlign.Center)
  158. .scrollBar(BarState.Off)
  159. } else {
  160. Text('暂无数据')
  161. .width('100%')
  162. .height('100%')
  163. .textAlign(TextAlign.Center)
  164. }
  165. }
  166. .width('100%')
  167. .layoutWeight(1)
  168. .onStateChange((refreshStatus: RefreshStatus) => {
  169. // ToolsHelper.log('Refresh onStatueChange state is ' + refreshStatus)
  170. })
  171. .onRefreshing(() => {
  172. this.onRefresh && this.onRefresh()
  173. })
  174. }
  175. }