鸿蒙开发:一个轻盈的上拉下拉刷新组件

前言

老早之前开源了一个刷新组件,提供了很多常见的功能,也封装了List,Grid,WaterFlow,虽然功能多,但也冗余比较多,随着时间的前去,暴露的问题就慢慢增多,虽然我也提供了通用的RefrshLayout,奈何很多人仍然有许多问题,但大部分都是相关属性以及用法的问题,对于我来说也比较苦恼,既然如此,那就只封装一个刷新加载,其它的自己实现好了,于是针对refresh的轻盈组件就剥离出来了。

因为它只是一个刷新组件,也仅仅是提供刷新能力,并不提供数据加载服务,这是和refrsh组件的不同之处,当然了,也是灵活之处,毕竟列表的组件是自己写的,需要什么样式更加灵活,但是在代码层次上也稍显冗余,不过有舍就有得。

目前已上传至了中心仓库,地址是:

ohpm.openharmony.cn/#/cn/detail...

刷新库功能效果一览

1、所有功能

2、各个功能效果

快速使用

方式一:在Terminal窗口中,执行如下命令安装三方包,DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。

建议:在使用的模块路径下进行执行命令。

text 复制代码
ohpm install @abner/lithe_refresh

方式二:在工程的oh-package.json5中设置三方包依赖,配置示例如下:

json 复制代码
"dependencies": { "@abner/lithe_refresh": "^1.0.0"}

使用注意

可以使用LitheRefresh组件,包裹想刷新的任意组件,相对比较灵活,如果您想实现懒加载数据模式,建议结合提供的RefreshDataSource,可以让您实现更加方便。

有一点需要知道,如果是包裹的是可滑动组件,比如List,Grid,WaterFlow等,需要配合nestedScroll属性,来解决滑动之间的冲突。

代码案例

1、简单使用

TypeScript 复制代码
controller: RefreshController = new RefreshController()

//任意组件,可以是List、Grid,WaterFlow ......
@Builder
  itemLayout() {
    Column() {

    }.width("100%")
      .height("100%")
      .backgroundColor(Color.Pink)
      .justifyContent(FlexAlign.Center)
  }

LitheRefresh({
  itemLayout: this.itemLayout,
  controller: this.controller,
  onRefresh: () => {
    //下拉刷新
    this.controller.finishRefresh()
  },
  onLoadMore: () => {
    //加载更多
    this.controller.finishLoadMore()
  }
})

2、List使用

TypeScript 复制代码
@Entry
@Component
struct ListUpAndDownPage {
  @State testArray: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  scroller: Scroller = new Scroller()
  controller: RefreshController = new RefreshController()

  @Builder
  itemLayout(_this: ListUpAndDownPage) {
    List({ scroller: _this.scroller, space: 20 }) {
      ForEach(_this.testArray, (item: number) => {
        ListItem() {
          Text('' + item)
            .width('100%')
            .height(80)
            .fontSize(16)
            .textAlign(TextAlign.Center)
            .backgroundColor(0xFFFFFF)
            .border({ width: 2, color: Color.Pink })
        }
      }, (item: string, index: number) => JSON.stringify(item) + "_" + index)
    }
    .scrollBar(BarState.Off)
    .edgeEffect(EdgeEffect.None)
    .width("100%")
    .height("100%")
    .padding({ left: 20, right: 20 })
    .nestedScroll({
      scrollForward: NestedScrollMode.PARENT_FIRST,
      scrollBackward: NestedScrollMode.PARENT_FIRST
    })
  }

  build() {
    Column() {
      
      LitheRefresh({
        scroller: this.scroller,
        controller: this.controller,
        itemLayout: () => {
          this.itemLayout(this)
        },
        onRefresh: () => {
          //下拉刷新
          setTimeout(() => {
            this.controller.finishRefresh()
          }, 2000)
        },
        onLoadMore: () => {
          //上拉加载
          setTimeout(() => {
            this.testArray.push(13)
            this.testArray.push(14)
            this.controller.finishLoadMore()
          }, 2000)
        }
      })
    }
  }
}

3、Grid使用

TypeScript 复制代码
@Entry
@Component
struct GridUpAndDownPage {
  @State testArray: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  scroller: Scroller = new Scroller()
  controller: RefreshController = new RefreshController()

  @Builder
  itemLayout(_this: GridUpAndDownPage) {
    Grid(_this.scroller) {
      ForEach(_this.testArray, (item: number) => {
        GridItem() {
          Text('' + item)
            .width('100%')
            .height(80)
            .fontSize(16)
            .textAlign(TextAlign.Center)
            .backgroundColor(0xFFFFFF)
            .border({ width: 2, color: Color.Pink })
        }
      }, (item: string, index: number) => JSON.stringify(item) + "_" + index)
    }
    .columnsTemplate("1fr 1fr")
    .columnsGap(10)
    .rowsGap(10)
    .scrollBar(BarState.Off)
    .edgeEffect(EdgeEffect.None)
    .width("100%")
    .height("100%")
    .padding({ left: 20, right: 20 })
    .nestedScroll({
      scrollForward: NestedScrollMode.PARENT_FIRST,
      scrollBackward: NestedScrollMode.PARENT_FIRST
    })
  }

  build() {
    Column() {

      ActionBar({
        title: "Grid组件刷新",
        barBackgroundColor: Color.Red,
        leftText: "返回",
        onLeftClick: () => {
          router.back()
        },
      })

      LitheRefresh({
        scroller: this.scroller,
        controller: this.controller,
        itemLayout: () => {
          this.itemLayout(this)
        },
        onRefresh: () => {
          //下拉刷新
          setTimeout(() => {
            this.controller.finishRefresh()
          }, 2000)
        },
        onLoadMore: () => {
          //上拉加载
          setTimeout(() => {
            this.testArray.push(13)
            this.testArray.push(14)
            this.controller.finishLoadMore()
          }, 2000)
        }
      })
    }
  }
}

3、WaterFlow使用

这里没有使用提供RefreshDataSource,所以懒加载方式比较冗余,为了简洁代码,建议使用我提供的RefreshDataSource,可以让您的效率极大提升。

具体RefreshDataSource使用方式,可以查看Demo中LazyDataOperationPage页面。

TypeScript 复制代码
@Entry
  @Component
  struct WaterFlowUpAndDownPage {
    scroller: Scroller = new Scroller()
    controller: RefreshController = new RefreshController()

    @Builder
    itemLayout(_this: WaterFlowUpAndDownPage) {
      WaterFlowView({
        scroller: _this.scroller
      })
    }

    build() {
      Column() {

        LitheRefresh({
          scroller: this.scroller,
          controller: this.controller,
          itemLayout: () => {
            this.itemLayout(this)
          },
          onRefresh: () => {
            //下拉刷新
            setTimeout(() => {
              this.controller.finishRefresh()
            }, 2000)
          },
          onLoadMore: () => {
            //上拉加载
            setTimeout(() => {
              this.controller.finishLoadMore()
            }, 2000)
          }
        })
      }
    }
  }


@Component
  struct WaterFlowView {
    @State minSize: number = 80
    @State maxSize: number = 180
    @State fontSize: number = 24
    @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
    scroller: Scroller = new Scroller()
    dataSource: WaterFlowDataSource = new WaterFlowDataSource()
    private itemWidthArray: number[] = []
    private itemHeightArray: number[] = []

    // 计算FlowItem宽/高
    getSize() {
      let ret = Math.floor(Math.random() * this.maxSize)
      return (ret > this.minSize ? ret : this.minSize)
    }

    // 设置FlowItem的宽/高数组
    setItemSizeArray() {
      for (let i = 0; i < 30; i++) {
        this.itemWidthArray.push(this.getSize())
        this.itemHeightArray.push(this.getSize())
      }
    }

    aboutToAppear() {
      this.setItemSizeArray()
    }

    build() {
      WaterFlow({ scroller: this.scroller }) {
        LazyForEach(this.dataSource, (item: number) => {
          FlowItem() {
            Column() {
              Text("N" + item).fontSize(12).height('16')
            }
          }
          .width('100%')
                    .height(this.itemHeightArray[item % 30])
                    .backgroundColor(this.colors[item % 5])
                    }, (item: string) => item)
      }
      .columnsTemplate("1fr 1fr")
        .columnsGap(10)
        .rowsGap(5)
        .backgroundColor(0xFAEEE0)
        .width('100%')
        .height('100%')
        .nestedScroll({
          scrollForward: NestedScrollMode.PARENT_FIRST,
          scrollBackward: NestedScrollMode.PARENT_FIRST
        })
    }
  }

// 实现IDataSource接口的对象,用于瀑布流组件加载数据
export class WaterFlowDataSource implements IDataSource {
  private dataArray: number[] = []
  private listeners: DataChangeListener[] = []

  constructor() {
    for (let i = 0; i < 30; i++) {
      this.dataArray.push(i)
    }
  }

  // 获取索引对应的数据
  public getData(index: number): number {
    return this.dataArray[index]
  }

  // 通知控制器数据重新加载
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded()
    })
  }

  // 通知控制器数据增加
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index)
    })
  }

  // 通知控制器数据变化
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index)
    })
  }

  // 通知控制器数据删除
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index)
    })
  }

  // 通知控制器数据位置变化
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to)
    })
  }

  //通知控制器数据批量修改
  notifyDatasetChange(operations: DataOperation[]): void {
    this.listeners.forEach(listener => {
      listener.onDatasetChange(operations);
    })
  }

  // 获取数据总数
  public totalCount(): number {
    return this.dataArray.length
  }

  // 注册改变数据的控制器
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener)
    }
  }

  // 注销改变数据的控制器
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener)
    if (pos >= 0) {
      this.listeners.splice(pos, 1)
    }
  }

  // 增加数据
  public add1stItem(): void {
    this.dataArray.splice(0, 0, this.dataArray.length)
    this.notifyDataAdd(0)
  }

  // 在数据尾部增加一个元素
  public addLastItem(): void {
    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
    this.notifyDataAdd(this.dataArray.length - 1)
  }

  // 在指定索引位置增加一个元素
  public addItem(index: number): void {
    this.dataArray.splice(index, 0, this.dataArray.length)
    this.notifyDataAdd(index)
  }

  // 删除第一个元素
  public delete1stItem(): void {
    this.dataArray.splice(0, 1)
    this.notifyDataDelete(0)
  }

  // 删除第二个元素
  public delete2ndItem(): void {
    this.dataArray.splice(1, 1)
    this.notifyDataDelete(1)
  }

  // 删除最后一个元素
  public deleteLastItem(): void {
    this.dataArray.splice(-1, 1)
    this.notifyDataDelete(this.dataArray.length)
  }

  // 在指定索引位置删除一个元素
  public deleteItem(index: number): void {
    this.dataArray.splice(index, 1)
    this.notifyDataDelete(index)
  }

  // 重新加载数据
  public reload(): void {
    this.dataArray.splice(1, 1)
    this.dataArray.splice(3, 2)
    this.notifyDataReload()
  }
}

使用总结

在和可滑动组件使用的时候,记得一定要和nestedScroll属性配合使用,用于解决滑动冲突,除此之外,还需要传递滑动组件的scroller属性,用于手势操作。

相关推荐
1登峰造极34 分钟前
scroll-view 实现滑动显示,确保超出正常显示,小程序app,h5兼容
android·小程序
刘争Stanley35 分钟前
Android 15(V)新功能适配,雕琢移动细节之美
android·kotlin·android 15·android v
小狗爱世界40 分钟前
深入解析Binder源码
android·binder
qq_214670351 小时前
android 聊天界面键盘、表情切换丝滑
android·gitee
韩家老大1 小时前
MTK Android12 隐藏顶部状态栏
android
小杨互联网2 小时前
Fiddler 配置安卓和ios抓包和pc抓包
ios·fiddler·抓包·安卓抓包·fiddler抓包配置安卓·浏览器抓包
JhonKI3 小时前
【MySQL】复合查询
android·数据库·mysql
Yawesh_best3 小时前
MySQL(9)【内置函数】
android·数据库·mysql
m0_748254666 小时前
iOS中使用WKWebView加载和显示PDF文档实战
ios·pdf·cocoa
奇风7 小时前
鸿蒙技术分享:鸿蒙页面里更新卡片也太麻烦了吧!!!
harmonyos·鸿蒙应用开发