0


【最新鸿蒙开发api12】——学会瀑布流的实现和应用,这一篇就够了

各位码农朋友们,大家好!今天我们要聊一个既有趣又实用的话题 - 如何实现一个赏心悦目的瀑布流布局。不过,我们不是去欣赏大自然的瀑布,而是要用代码创造一个漂亮小姐姐图片的数字瀑布。准备好你的键盘,让我们开始这段美妙的编码之旅吧!

二话不说,先上图

1. LazyForEach的魔力

首先,让我们来认识一下我们的主角 - LazyForEach。它就像是一个勤劳的搬运工,只在需要的时候才去搬运数据,这样可以大大提高我们应用的性能。在我们的瀑布流中,LazyForEach负责懒洋洋地加载那些漂亮的小姐姐图片。

1.1简单介绍

  • LazyForEach是一种用于数据迭代的组件,它能够根据需要按需创建组件,从而优化性能,特别适用于滚动容器中的数据展示场景。为了为LazyForEach提供数据源,您需要实现IDataSource接口,并将其实例作为LazyForEach的数据源参数。
  • 允许开发者从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当这些组件位于滚动容器中时,LazyForEach会根据可视区域动态加载和卸载组件,以此来优化内存使用和提高应用性能。
  • IDataSource接口包含以下几个方法:- totalCount():返回数据总数。- getData(index):根据给定的索引返回数据项。- registerDataChangeListener(listener):注册数据变化监听器。- unregisterDataChangeListener(listener):注销数据变化监听器。
  • 使用限制- LazyForEach必须在支持数据懒加载的容器组件内使用,如List、Grid、Swiper和WaterFlow组件。- 在每次迭代中,LazyForEach必须创建且只允许创建一个子组件。- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。- 键值生成器必须针对每个数据生成唯一的值,以确保UI组件的正确渲染和更新。- LazyForEach必须使用DataChangeListener对象来进行更新,以触发UI的刷新

1.2接口

  1. LazyForEach(
  2. dataSource: IDataSource, // 需要进行数据迭代的数据源
  3. itemGenerator: (item: any, index: number) => void, // 子组件生成函数
  4. keyGenerator?: (item: any, index: number) => string // 键值生成函数
  5. ): void

1.3案例代码

  1. LazyForEach( this.dataSource, (item: Item, index) => {
  2. FlowItem(){
  3. Column(){
  4. Text('第' + `${this.item.index.toString()}` + '个')
  5. Image(this.item.image)
  6. .objectFit(ImageFit.Fill)
  7. .sharedTransition(`sharedImage${this.item.index}`, {duration: 300, curve:
  8. Curve.Linear,delay:50})
  9. }
  10. .backgroundColor('#ffa4eaac')
  11. .borderRadius(8)
  12. }
  13. // ... 更多代码
  14. })

这段代码就像是在说:"嘿,LazyForEach,帮我从dataSource中拿些数据来,但别太勤快,用到哪个拿哪个就行!"

2. 瀑布流的使用(案例)

2.1 WaterFlow组件

WaterFlow组件是我们瀑布流的核心。它就像是一个调度员,负责安排每张图片的位置,确保它们像瀑布一样流畅地排列。

  1. build() {
  2. Column(){
  3. WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){
  4. LazyForEach( this.dataSource, (item: Item, index) => {
  5. FlowItem(){
  6. //...
  7. }
  8. .onClick(() => {
  9. router.pushUrl(
  10. {
  11. url: 'pages/waterflow/Preview',
  12. params: { item: item, index: index }
  13. })
  14. })
  15. })
  16. }
  17. .columnsTemplate('1fr 1fr 1fr')
  18. .columnsGap(10)
  19. .rowsGap(8)
  20. .margin({left: 15, right:15})
  21. }
  22. .margin({top:20})
  23. }

这里我们设置了三列布局,列间距为10,行间距为8,左右各留15的边距。这样,我们的小姐姐们就能整整齐齐地站好啦!

2.2 IDataSource接口的实现

接下来,我们来看看WaterFlowDataSource类。它就像是一个图片管理员,负责存储和管理我们的小姐姐图片。由于代码过长,我将完整代码放到最后,这里只展示核心部分。

主要讲解实现思路

  1. 类的基本结构export class WaterFlowDataSource implements IDataSource { private dataArray: Item[] = [] private listeners: DataChangeListener[] = [] // ...}这里定义了两个私有属性:dataArray用于存储Item对象,listeners用于存储数据变化的监听器。
  2. 构造函数constructor() { for( let i = 1; i <= 40; i++){ let r = getRandomInt(1,6) this.dataArray.push(new Item($r(`app.media.img_gril_${r}`),i)) }}构造函数初始化了40个Item对象,每个对象包含一个随机选择的图片资源和一个索引。
  3. 基本方法实现public getData(index: number): Item { return this.dataArray[index]}​totalCount(): number { return this.dataArray.length}这两个方法分别用于获取特定索引的Item和获取总数据量。
  4. 监听器管理registerDataChangeListener(listener: DataChangeListener): void { // ...}​unregisterDataChangeListener(listener: DataChangeListener): void { // ...}这两个方法用于添加和移除数据变化的监听器。
  5. 数据操作方法public addLastItem(): void { let newItem = this.AddNewItem(this.dataArray.length) this.dataArray.splice(this.dataArray.length, 0, newItem) this.notifyDataAdd(this.dataArray.length - 1)}​AddNewItem(index: number): Item { let randomNumber = getRandomInt(1,6) return new Item($r(`app.media.img_gril_${randomNumber}`), index+1)}addLastItem方法用于在数组末尾添加新元素,AddNewItem方法用于创建新的Item对象。
  6. 通知方法 // 通知控制器数据增加 notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index) }) } // ... 其他通知方法``````这些方法用于在数据发生变化时通知所有注册的监听器。
  7. 辅助函数function getRandomInt(min:number, max:number) { min = Math.ceil(min); // 向上取整 max = Math.floor(max); // 向下取整 return Math.floor(Math.random() * (max - min + 1)) + min; // 包含最大值}这个函数用于生成指定范围内的随机整数,用于选择随机图片。
实现思路
  1. 数据管理: 使用数组存储Item对象,每个对象代表一张图片。
  2. 懒加载: 通过getData方法按需获取数据,而不是一次性加载所有数据。
  3. 动态更新: 提供添加新项目的方法(addLastItem),以实现无限滚动效果。
  4. 事件通知: 使用观察者模式(listeners数组),在数据变化时通知UI更新。
  5. 随机性: 使用随机数生成器来选择图片,增加界面的多样性。

这个类实现了IDataSource接口,提供了获取数据、计算总数等基本功能。它就像是一个图片仓库的管理员,随时准备为我们提供所需的图片。

2.3 无限流

谁不想拥有无限的美女图片呢?我们来实现一个无限流的功能吧!

  1. WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){
  2. //...
  3. }
  4. .onReachEnd(() => {
  5. console.info('onReached')
  6. setTimeout( () => {
  7. for( let i = 0; i < 40; i++){
  8. this.dataSource.addLastItem()
  9. }
  10. }, 1000)
  11. })

这段代码的意思是:"嘿,当你滑到底的时候,我悄悄地再给你加40张图片,这样你就可以一直欣赏下去啦!"

在WaterFlowDataSource中,我们使用随机数来生成新的图片对象:

  1. AddNewItem(index: number): Item {
  2. let randomNumber = getRandomInt(1,6)
  3. return new Item($r(`app.media.img_gril_${randomNumber}`), index+1)
  4. }

这就像是从帽子里变魔术一样,随机抽出一张新的小姐姐图片!

2.4 解决触底加载卡顿的问题

但不知道你有没有发现,每次滑动到底部都会卡顿一下,该如何解决呢?

为了避免滑动到底部时出现卡顿,我们采用了提前加载的策略:

  1. WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){
  2. LazyForEach( this.dataSource, (item: Item, index) => {
  3. FlowItem(){
  4. //...
  5. }
  6. // 用来避免有加载效果造成的卡顿
  7. .onAppear(() => {
  8. // 即将触底时提前增加数据
  9. if (index + 20 == this.dataSource.totalCount()) {
  10. for (let i = 0; i < 40; i++) {
  11. this.dataSource.addLastItem()
  12. }
  13. console.log('waterFlow ' + '即将触底了')
  14. }
  15. })
  16. .onClick(() => {
  17. router.pushUrl(
  18. {
  19. url: 'pages/waterflow/Preview',
  20. params: { item: item, index: index }
  21. })
  22. })
  23. })
  24. }

这就像是在你快吃完盘子里的食物时,服务员已经准备好了下一盘。这样,你就不会感到任何等待的不适啦!

2.5 优化性能,使用复用组件

最后,为了进一步提升性能,我们使用了@Reusable装饰器来复用MyItem组件:

  1. @Reusable
  2. @Component
  3. struct MyItem{
  4. @State item:Item | null = null
  5. aboutToReuse(params: Record<string, Item>) {
  6. this.item = params.item;
  7. console.info('Reuse item:' + this.item)
  8. }
  9. aboutToAppear() {
  10. console.info('item:' + this.item)
  11. }
  12. build(){
  13. Column(){
  14. if(this.item)
  15. {
  16. Text('第' + `${this.item.index.toString()}` + '个')
  17. Image(this.item.image)
  18. .objectFit(ImageFit.Fill)
  19. .sharedTransition(`sharedImage${this.item.index}`, {duration: 300, curve: Curve.Linear,delay:50})
  20. }
  21. }
  22. .backgroundColor('#ffa4eaac')
  23. .borderRadius(8)
  24. }
  25. }

这就像是循环使用餐具,既环保又高效!

3.完整代码

3.1Item

  1. export default class Item{
  2. image: Resource
  3. index: number
  4. constructor(image: Resource,index: number) {
  5. this.image = image
  6. this.index = index
  7. }
  8. }

3.2接口实现

  1. import Item from './Item'
  2. export class WaterFlowDataSource implements IDataSource{
  3. private dataArray: Item[] = []
  4. private listeners: DataChangeListener[] = []
  5. constructor() {
  6. for( let i = 1; i <= 40; i++){
  7. let r = getRandomInt(1,6)
  8. this.dataArray.push(new Item($r(`app.media.img_gril_${r}`),i))
  9. }
  10. }
  11. public getData(index: number): Item{
  12. return this.dataArray[index]
  13. }
  14. totalCount(): number {
  15. return this.dataArray.length
  16. }
  17. registerDataChangeListener(listener: DataChangeListener): void {
  18. if( this.listeners.indexOf( listener) < 0){
  19. this.listeners.push(listener)
  20. }
  21. }
  22. unregisterDataChangeListener(listener: DataChangeListener): void {
  23. const pos = this.listeners.indexOf(listener)
  24. if( pos >= 0){
  25. this.listeners.splice(pos, 1)
  26. }
  27. }
  28. // 在数据尾部增加一个元素
  29. public addLastItem(): void {
  30. let newItem = this.AddNewItem(this.dataArray.length)
  31. this.dataArray.splice(this.dataArray.length, 0, newItem)
  32. this.notifyDataAdd(this.dataArray.length - 1)
  33. }
  34. AddNewItem(index: number): Item {
  35. let randomNumber = getRandomInt(1,6)
  36. return new Item($r(`app.media.img_gril_${randomNumber}`), index+1)
  37. }
  38. // 通知控制器数据重新加载
  39. notifyDataReload(): void {
  40. this.listeners.forEach(listener => {
  41. listener.onDataReloaded()
  42. })
  43. }
  44. // 通知控制器数据增加
  45. notifyDataAdd(index: number): void {
  46. this.listeners.forEach(listener => {
  47. listener.onDataAdd(index)
  48. })
  49. }
  50. // 通知控制器数据变化
  51. notifyDataChange(index: number): void {
  52. this.listeners.forEach(listener => {
  53. listener.onDataChange(index)
  54. })
  55. }
  56. // 通知控制器数据删除
  57. notifyDataDelete(index: number): void {
  58. this.listeners.forEach(listener => {
  59. listener.onDataDelete(index)
  60. })
  61. }
  62. // 通知控制器数据位置变化
  63. notifyDataMove(from: number, to: number): void {
  64. this.listeners.forEach(listener => {
  65. listener.onDataMove(from, to)
  66. })
  67. }
  68. //通知控制器数据批量修改
  69. notifyDatasetChange(operations: DataOperation[]): void {
  70. this.listeners.forEach(listener => {
  71. listener.onDatasetChange(operations);
  72. })
  73. }
  74. /*// 增加数据
  75. public add1stItem(): void {
  76. this.dataArray.splice(0, 0, this.dataArray.length)
  77. this.notifyDataAdd(0)
  78. }
  79. // 在指定索引位置增加一个元素
  80. public addItem(index: number): void {
  81. this.dataArray.splice(index, 0, this.dataArray.length)
  82. this.notifyDataAdd(index)
  83. }
  84. // 删除第一个元素
  85. public delete1stItem(): void {
  86. this.dataArray.splice(0, 1)
  87. this.notifyDataDelete(0)
  88. }
  89. // 删除第二个元素
  90. public delete2ndItem(): void {
  91. this.dataArray.splice(1, 1)
  92. this.notifyDataDelete(1)
  93. }
  94. // 删除最后一个元素
  95. public deleteLastItem(): void {
  96. this.dataArray.splice(-1, 1)
  97. this.notifyDataDelete(this.dataArray.length)
  98. }
  99. // 在指定索引位置删除一个元素
  100. public deleteItem(index: number): void {
  101. this.dataArray.splice(index, 1)
  102. this.notifyDataDelete(index)
  103. }
  104. // 重新加载数据
  105. public reload(): void {
  106. this.dataArray.splice(1, 1)
  107. this.dataArray.splice(3, 2)
  108. this.notifyDataReload()
  109. }*/
  110. }
  111. function getRandomInt(min:number, max:number) {
  112. min = Math.ceil(min); // 向上取整
  113. max = Math.floor(max); // 向下取整
  114. return Math.floor(Math.random() * (max - min + 1)) + min; // 包含最大值
  115. }

3.3主页

  1. import router from '@ohos.router'
  2. import { WaterFlowDataSource } from './WaterFlowDataSource'
  3. import Item from './Item'
  4. @Entry
  5. @Component
  6. struct WaterFlowPage {
  7. scroller: Scroller = new Scroller()
  8. dataSource:WaterFlowDataSource = new WaterFlowDataSource()
  9. @Builder itemFoot(){
  10. Column(){
  11. Text('-------到底了--------')
  12. .fontSize(10)
  13. .backgroundColor(Color.White)
  14. .width('100%')
  15. .textAlign(TextAlign.Center)
  16. .height(30)
  17. .margin({top:5})
  18. }
  19. }
  20. build() {
  21. Column(){
  22. WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){
  23. LazyForEach( this.dataSource, (item: Item, index) => {
  24. FlowItem(){
  25. MyItem({item:item})
  26. }
  27. // 用来避免有加载效果造成的卡顿
  28. .onAppear(() => {
  29. // 即将触底时提前增加数据
  30. if (index + 20 == this.dataSource.totalCount()) {
  31. for (let i = 0; i < 40; i++) {
  32. this.dataSource.addLastItem()
  33. }
  34. console.log('waterFlow ' + '即将触底了')
  35. }
  36. })
  37. .onClick(() => {
  38. router.pushUrl(
  39. {
  40. url: 'pages/waterflow/Preview',
  41. params: { item: item, index: index }
  42. })
  43. })
  44. })
  45. }
  46. /*.onReachEnd(() => {
  47. console.info('onReached')
  48. setTimeout( () => {
  49. for( let i = 0; i < 40; i++){
  50. this.dataSource.addLastItem()
  51. }
  52. }, 1000)
  53. })*/
  54. .columnsTemplate('1fr 1fr 1fr')
  55. .columnsGap(10)
  56. .rowsGap(8)
  57. .margin({left: 15, right:15})
  58. }
  59. .margin({top:20})
  60. }
  61. }
  62. @Reusable
  63. @Component
  64. struct MyItem{
  65. @State item:Item | null = null
  66. aboutToReuse(params: Record<string, Item>) {
  67. this.item = params.item;
  68. console.info('Reuse item:' + this.item)
  69. }
  70. aboutToAppear() {
  71. console.info('item:' + this.item)
  72. }
  73. build(){
  74. Column(){
  75. if(this.item)
  76. {
  77. Text('第' + `${this.item.index.toString()}` + '个')
  78. Image(this.item.image)
  79. .objectFit(ImageFit.Fill)
  80. .sharedTransition(`sharedImage${this.item.index}`, {duration: 300, curve: Curve.Linear,delay:50})
  81. }
  82. }
  83. .backgroundColor('#ffa4eaac')
  84. .borderRadius(8)
  85. }
  86. }

3.4点击进入的详情页

  1. import router from '@ohos.router'
  2. import Item from './Item'
  3. @Entry
  4. @Component
  5. struct Preview {
  6. @State item: Item | null = null
  7. @State index: number = 0
  8. aboutToAppear(): void {
  9. const params:object = router.getParams()
  10. this.item = params['item']
  11. this.index = params['index']
  12. }
  13. build() {
  14. Row(){
  15. Image(this.item?.image).width('100%')
  16. .sharedTransition(`sharedItem${this.index}`, {duration: 300, curve: Curve.Linear, delay: 50})
  17. .align(Alignment.Center)
  18. .onClick( () => {
  19. router.back()
  20. })
  21. }.width('100%')
  22. .height('100%')
  23. .backgroundColor(Color.Black)
  24. .alignSelf(ItemAlign.Center)
  25. }
  26. }

结语

好啦,我们的瀑布流小姐姐相册就这样完成啦!通过LazyForEach的懒加载、WaterFlow的灵活布局、无限流的持续加载,以及性能优化,我们创造了一个流畅美观的图片浏览体验。

记住,编程就像是魔法,而你就是那个魔法师。现在,你已经掌握了创建美丽瀑布流的魔法,去创造你自己的数字世界吧!

最后,请记住:欣赏美女图片虽然愉快,但要适度哦。毕竟,我们的目标是成为优秀的程序员,而不是走火入魔的LSP(老色批)!

祝你编码愉快,瀑布流畅!

标签: 鸿蒙 前端 harmonyos

本文转载自: https://blog.csdn.net/m0_66825548/article/details/140775551
版权归原作者 拥有一个学徒的心 所有, 如有侵权,请联系我们删除。

“【最新鸿蒙开发api12】——学会瀑布流的实现和应用,这一篇就够了”的评论:

还没有评论