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

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

前言

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


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


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

https://ohpm.openharmony.cn/#/cn/detail/@abner%2Flithe_refresh


刷新库功能效果一览


1、所有功能



2、各个功能效果



快速使用


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

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

ohpm install @abner/lithe_refresh

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

"dependencies": { "@abner/lithe_refresh": "^1.0.0"}


使用注意


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

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

代码案例


1、简单使用


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使用


@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使用


@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页面。

@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天前
【HarmonyOS Next开发】:ListItemGroup使用
通过使用ListItemGroup和AlphabetIndexer两种类型组件,实现带标题分类和右侧导航栏的页面
79 61
|
1天前
|
API
【HarmonyOS Next开发】Tabs使用封装
在写Tabs时,会使用很多个TabContent来实现不同页面的展示内容,但是如果TabContent数量很多时,会导致Tabs代码量大而且很臃肿,因此想着尝试去封装Tabs的使用,可以让界面整洁和对内容界面的解耦。 主要依托于wrapBuilder:封装全局@Builder的方法使用。需要注意从API 11 才开始支持使用
15 6
|
1天前
|
API 容器
【HarmonyOS Next开发】Navigation使用
Navigation是路由容器组件,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。适用于模块内和跨模块的路由切换。 在页面跳转时,应该使用页面路由router,在页面内的页面跳转时,建议使用Navigation达到更好的转场动效场景。
17 8
|
1天前
|
开发者
【HarmonyOS Next开发】用户文件访问
文件所有者为登录到该终端设备的用户,包括用户私有的图片、视频、音频、文档等。 应用对用户文件的创建、访问、删除等行为,需要提前获取用户授权,或由用户操作完成。
18 10
|
1天前
|
存储 JSON 测试技术
【HarmonyOS Next开发】云开发-云数据库(二)
实现了云侧和端侧的云数据库创建、更新、修改等操作。这篇文章实现调用云函数对云数据库进行增删改查。
19 9
|
1天前
|
存储 IDE JavaScript
【HarmonyOS Next开发】端云一体化初始化项目
端云一体化开发是HarmonyOS对云端开发的支持、实现端云联动。云开发服务提供了云函数、云数据库、云存储等服务,可以使开发者专注于应用的业务逻辑开发,无需关注基础设施,例如:服务器、操作系统等问题。
14 6
|
1天前
|
索引
【HarmonyOS Next开发】日历组件详细日界面组件
原生UI没有提供日历相关的组件,于是手撸了详细页面的日程。一开始打算使用list加tab的方式来实现切换的效果,但是list的切换是没有办法确定当前展示的索引的,所以没有办法实现日历内容动态添加等效果。在业内大佬的指导下,使用了两个swiper组件分别实现周和日的切换,实现了想要的效果
13 6
|
1天前
【HarmonyOS Next开发】实现矩形上下拖动、动态拖拽修改高度
实现一个矩形块上下拖动,并且可以拖动边缘定位点改变矩形块高度。
13 6
|
1天前
【HarmonyOS Next开发】使用两层Scroll实现一天时间轴和事件卡片的层叠显示
实现某一天24小时的时间长度和当天事件的页面。
19 9

热门文章

最新文章