CustomPullToRefresh.ets 8.82 KB
import lottie, { AnimationItem } from '@ohos/lottie';
import { PullToRefresh, PullToRefreshConfigurator } from '@ohos/pulltorefresh';
import { LazyDataSource, Logger } from 'wdKit/Index';

@Component
export struct CustomPullToRefresh {
  @Link alldata: Object[] | LazyDataSource<Object>;
  scroller: Scroller = new Scroller();
  @BuilderParam customList: () => void;
  onRefresh: (resolve?: (value: string | PromiseLike<string>) => void) => void = () => {
  }
  onLoadMore: (resolve?: (value: string | PromiseLike<string>) => void) => void = () => {
  }
  ///是否存在上拉更多
  @Prop @Watch('hasMoreChange') hasMore: boolean = true
  refreshConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator()
    .setHasLoadMore(this.hasMore)
    .setAnimDuration(500);
  private refreshSettings: RenderingContextSettings = new RenderingContextSettings(true)
  private refreshContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.refreshSettings)
  private loadMoreSettings: RenderingContextSettings = new RenderingContextSettings(true)
  private loadMoreContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.loadMoreSettings)
  private refreshAnimation: AnimationItem | null = null;
  private loadMoreAnimation: AnimationItem | null = null;
  private refreshAnimName: string = "refresh";
  private refreshingAnimName: string = "refreshing";
  private loadMoreAnimName: string = "loadMore";
  private refreshAnimationDestroy = true
  // refresh-1,refreshing-2,refreshed-3,idle-4
  @State @Watch('stateChange') private refreshState: number = 4;

  build() {
    Column() {
      PullToRefresh({
        data: $alldata,
        scroller: this.scroller,
        refreshConfigurator: this.refreshConfigurator,
        customList: () => {
          this.customList();
        },
        onRefresh: () => {
          return new Promise<string>((success, error) => {
            new Promise<string>((resolve) => {
              this.onRefresh(resolve)
            }).then((text) => {
              setTimeout(()=>{
                // 延时500,展示第二段动画 TODO 是否去掉?
                this.refreshState = 3
                this.refreshDestroy()
                setTimeout(() => {
                  success(text)
                }, 500)
                setTimeout(() => {
                  this.refreshState = 4
                  // 延时将状态改为空闲,组件closeRefresh做了延时,不能配置,这里配合延时
                }, 1200)
              },500)
            })
          });
        },
        onLoadMore: () => {
          return new Promise<string>((resolve, reject) => {
            this.onLoadMore(resolve)// TODO 加层promise处理,延长动画时间
          });
        },
        customLoad: () => {
          this.customLoadMoreView()
        },
        // 可选项,自定义下拉刷新动画布局
        customRefresh: () => {
          this.customRefreshView()
        },
        // 可选项,下拉中回调
        onAnimPullDown: (value, width, height) => {
          this.refreshState = 1
          // Logger.error('zzzz', 'onAnimPullDown, ' + value + ', ' + width + ', ' + height)
          this.refreshAnim(value)
        },
        // 可选项,刷新中回调
        onAnimRefreshing: (value, width, height) => {
          if (this.refreshState < 2) {
            this.refreshState = 2
          }
          // Logger.error('zzzz', 'onAnimRefreshing, ' + value + ', ' + width + ', ' + height)
          this.refreshingAnim()
        },
      })
    }
  }

  /**
   * 销毁所有动画
   */
  private refreshDestroy() {
    lottie.destroy(this.refreshAnimName);
    lottie.destroy(this.refreshingAnimName);
    this.refreshAnimation?.removeEventListener('DOMLoaded')
    this.refreshAnimation?.removeEventListener('destroy')
    this.refreshAnimation = null
  }

  @Builder
  private customRefreshView() {
    Stack({ alignContent: Alignment.Center }) {
      Canvas(this.refreshContext)
        .width(60)
        .height(60)
        .backgroundColor(Color.Transparent)
        .onReady(() => {
          // 可在此生命回调周期中加载动画,可以保证动画尺寸正确
          //抗锯齿的设置
          this.refreshContext.imageSmoothingEnabled = true;
          this.refreshContext.imageSmoothingQuality = 'medium'
        })
        .onDisAppear(() => {
          lottie.destroy(this.refreshAnimName);
          lottie.destroy(this.refreshingAnimName);
        })
        .visibility((this.refreshState == 1 || this.refreshState == 2) ? Visibility.Visible : Visibility.Hidden)

      Text('已更新至最新')
        .fontSize(14)
        .textAlign(TextAlign.Center)
        .fontColor('#676767')
        .backgroundColor('#f6f6f6')
        .borderRadius(20)
        .padding({
          left: 19,
          right: 19,
          top: 10,
          bottom: 10
        })
        .visibility(this.refreshState == 3 ? Visibility.Visible : Visibility.Hidden)
    }.padding({ top: 20 })
  }

  @Builder
  private customLoadMoreView() {
    Row() {
      Canvas(this.loadMoreContext)
        .width(14)
        .height(14)
        .backgroundColor(Color.Transparent)
        .onReady(() => {
          // 可在此生命回调周期中加载动画,可以保证动画尺寸正确
          //抗锯齿的设置
          this.loadMoreContext.imageSmoothingEnabled = true;
          this.loadMoreContext.imageSmoothingQuality = 'medium'
          this.loadMoreAnimate()
        })
        .onDisAppear(() => {
          // Logger.error('zzzz', 'CustomLoadMoreLayout onDisAppear')
          lottie.destroy(this.loadMoreAnimName);
        })

      Text('努力加载中')
        .margin({
          left: 7,
          bottom: 1
        })
        .fontSize(14)
        .fontColor('#888888')
        .textAlign(TextAlign.Center)
    }
    .clip(true)
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .height(60)
  }

  private refreshAnim(percent: number) {
    if (this.refreshAnimation == null || this.refreshAnimation.name != this.refreshAnimName ||
    this.refreshAnimationDestroy) {
      this.refreshAnimation?.destroy(this.refreshAnimName)
      this.refreshAnimation?.destroy(this.refreshingAnimName)
      this.refreshAnimation = lottie.loadAnimation({
        container: this.refreshContext,
        renderer: 'canvas', // canvas 渲染模式
        loop: 1,
        autoplay: true,
        name: this.refreshAnimName,
        path: "lottie/refresh_step1.json", // 路径加载动画只支持entry/src/main/ets 文件夹下的相对路径
      })
      this.refreshAnimation?.goToAndStop(1)
      this.addRefreshAnimListener()
    }
    this.refreshAnimation?.goToAndStop(this.getFramesByProgress(percent), true);
  }

  /**
   * 获取动画帧
   *
   * @param percent 百分比,如0.76
   * @returns
   */
  private getFramesByProgress(percent: number): number {
    if (this.refreshAnimation == null) {
      return 1;
    }
    let total = this.refreshAnimation.totalFrames;
    let frame = Math.floor(total * percent);
    if (frame >= total - 1) {
      frame = total - 1
    }
    return frame;
  }

  private refreshingAnim() {
    // Logger.error('zzzz', 'animate2, 1')
    // 先销毁之前的动画
    if (this.refreshAnimation == null || this.refreshAnimation.name != this.refreshingAnimName ||
    this.refreshAnimationDestroy) {
      this.refreshAnimation?.destroy(this.refreshAnimName)
      this.refreshAnimation?.destroy(this.refreshingAnimName)
      this.refreshAnimation = lottie.loadAnimation({
        container: this.refreshContext,
        renderer: 'canvas', // canvas 渲染模式
        loop: 10,
        autoplay: true,
        name: this.refreshingAnimName,
        path: "lottie/refresh_step2.json", // 路径加载动画只支持entry/src/main/ets 文件夹下的相对路径
      })
      this.addRefreshAnimListener()
    }
    // Logger.error('zzzz', 'animate2, 2')
  }

  private addRefreshAnimListener() {
    this.refreshAnimation?.addEventListener('DOMLoaded', (args: Object): void => {
      // Logger.error('zzzz', "lottie DOMLoaded");
      this.refreshAnimationDestroy = false
    }); //动画加载完成,播放之前触发
    this.refreshAnimation?.addEventListener('destroy', (args: Object): void => {
      // Logger.error('zzzz', "lottie destroy");
      this.refreshAnimationDestroy = true
    });
  }

  loadMoreAnimate() {
    if (this.loadMoreAnimation == null) {
      this.loadMoreAnimation = lottie.loadAnimation({
        container: this.loadMoreContext,
        renderer: 'canvas', // canvas 渲染模式
        loop: 50,
        autoplay: true,
        name: this.loadMoreAnimName,
        path: "lottie/loading_more.json", // 路径加载动画只支持entry/src/main/ets 文件夹下的相对路径
      })
    }
  }

  hasMoreChange() {
    this.refreshConfigurator.setHasLoadMore(this.hasMore)
  }

  stateChange() {
    // Logger.error('zzzz', 'stateChange ' + this.refreshState)
  }
}