当前位置:首页 » 《关于电脑》 » 正文

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

10 人参与  2024年10月21日 10:01  分类 : 《关于电脑》  评论

点击全文阅读


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

二话不说,先上图

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接口

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

1.3案例代码 

LazyForEach( this.dataSource, (item: Item, index) => {  FlowItem(){      Column(){        Text('第' + `${this.item.index.toString()}` + '个')        Image(this.item.image)          .objectFit(ImageFit.Fill)          .sharedTransition(`sharedImage${this.item.index}`, {duration: 300, curve:                                      Curve.Linear,delay:50})          }        .backgroundColor('#ffa4eaac')        .borderRadius(8)  }  // ... 更多代码})

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

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

2.1 WaterFlow组件

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

build() {    Column(){      WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){        LazyForEach( this.dataSource, (item: Item, index) => {            FlowItem(){              //...            }             .onClick(() => {              router.pushUrl(                {                  url: 'pages/waterflow/Preview',                  params: { item: item, index: index }                })            })        })      }            .columnsTemplate('1fr 1fr 1fr')      .columnsGap(10)      .rowsGap(8)      .margin({left: 15, right:15})    }    .margin({top:20})  }

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

2.2 IDataSource接口的实现

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

主要讲解实现思路

类的基本结构
export class WaterFlowDataSource implements IDataSource {  private dataArray: Item[] = []  private listeners: DataChangeListener[] = []  // ...}

这里定义了两个私有属性:dataArray用于存储Item对象,listeners用于存储数据变化的监听器。

构造函数
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对象,每个对象包含一个随机选择的图片资源和一个索引。

基本方法实现
public getData(index: number): Item {  return this.dataArray[index]}​totalCount(): number {  return this.dataArray.length}

这两个方法分别用于获取特定索引的Item和获取总数据量。

监听器管理
registerDataChangeListener(listener: DataChangeListener): void {  // ...}​unregisterDataChangeListener(listener: DataChangeListener): void {  // ...}

这两个方法用于添加和移除数据变化的监听器。

数据操作方法
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对象。

通知方法
  // 通知控制器数据增加  notifyDataAdd(index: number): void {    this.listeners.forEach(listener => {      listener.onDataAdd(index)    })  }  // ... 其他通知方法
这些方法用于在数据发生变化时通知所有注册的监听器。
辅助函数
function getRandomInt(min:number, max:number) {  min = Math.ceil(min); // 向上取整  max = Math.floor(max); // 向下取整  return Math.floor(Math.random() * (max - min + 1)) + min; // 包含最大值}

这个函数用于生成指定范围内的随机整数,用于选择随机图片。

实现思路

数据管理: 使用数组存储Item对象,每个对象代表一张图片。

懒加载: 通过getData方法按需获取数据,而不是一次性加载所有数据。

动态更新: 提供添加新项目的方法(addLastItem),以实现无限滚动效果。

事件通知: 使用观察者模式(listeners数组),在数据变化时通知UI更新。

随机性: 使用随机数生成器来选择图片,增加界面的多样性。

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

2.3 无限流

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

WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){    //...}.onReachEnd(() => {        console.info('onReached')        setTimeout( () => {          for( let i = 0; i < 40; i++){            this.dataSource.addLastItem()          }        }, 1000)      })

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

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

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

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

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

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

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

WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){        LazyForEach( this.dataSource, (item: Item, index) => {            FlowItem(){              //...            }            // 用来避免有加载效果造成的卡顿            .onAppear(() => {              // 即将触底时提前增加数据              if (index + 20 == this.dataSource.totalCount()) {                for (let i = 0; i < 40; i++) {                  this.dataSource.addLastItem()                }                console.log('waterFlow ' + '即将触底了')              }            })            .onClick(() => {              router.pushUrl(                {                  url: 'pages/waterflow/Preview',                  params: { item: item, index: index }                })            })        })      }

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

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

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

@Reusable@Componentstruct MyItem{  @State item:Item | null = null  aboutToReuse(params: Record<string, Item>) {    this.item = params.item;    console.info('Reuse item:' + this.item)  }  aboutToAppear() {    console.info('item:' + this.item)  }  build(){    Column(){      if(this.item)      {        Text('第' + `${this.item.index.toString()}` + '个')        Image(this.item.image)          .objectFit(ImageFit.Fill)          .sharedTransition(`sharedImage${this.item.index}`, {duration: 300, curve: Curve.Linear,delay:50})      }    }    .backgroundColor('#ffa4eaac')    .borderRadius(8)  }}

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

3.完整代码

3.1Item

export default class Item{  image: Resource  index: number  constructor(image: Resource,index: number) {    this.image = image    this.index = index  }}

3.2接口实现

import Item  from './Item'export class WaterFlowDataSource implements IDataSource{  private dataArray: Item[] = []  private listeners: DataChangeListener[] = []  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))    }  }  public getData(index: number): Item{    return this.dataArray[index]  }  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 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)  }  // 通知控制器数据重新加载  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 add1stItem(): void {    this.dataArray.splice(0, 0, this.dataArray.length)    this.notifyDataAdd(0)  }  // 在指定索引位置增加一个元素  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()  }*/}function getRandomInt(min:number, max:number) {  min = Math.ceil(min); // 向上取整  max = Math.floor(max); // 向下取整  return Math.floor(Math.random() * (max - min + 1)) + min; // 包含最大值}

3.3主页 

import router from '@ohos.router'import {  WaterFlowDataSource } from './WaterFlowDataSource'import Item from './Item'@Entry@Componentstruct WaterFlowPage {  scroller: Scroller = new Scroller()  dataSource:WaterFlowDataSource = new WaterFlowDataSource()  @Builder itemFoot(){    Column(){      Text('-------到底了--------')        .fontSize(10)        .backgroundColor(Color.White)        .width('100%')        .textAlign(TextAlign.Center)        .height(30)        .margin({top:5})    }  }  build() {    Column(){      WaterFlow({footer: ()=> this.itemFoot() ,scroller: this.scroller}){        LazyForEach( this.dataSource, (item: Item, index) => {            FlowItem(){              MyItem({item:item})            }            // 用来避免有加载效果造成的卡顿            .onAppear(() => {              // 即将触底时提前增加数据              if (index + 20 == this.dataSource.totalCount()) {                for (let i = 0; i < 40; i++) {                  this.dataSource.addLastItem()                }                console.log('waterFlow ' + '即将触底了')              }            })            .onClick(() => {              router.pushUrl(                {                  url: 'pages/waterflow/Preview',                  params: { item: item, index: index }                })            })        })      }      /*.onReachEnd(() => {        console.info('onReached')        setTimeout( () => {          for( let i = 0; i < 40; i++){            this.dataSource.addLastItem()          }        }, 1000)      })*/      .columnsTemplate('1fr 1fr 1fr')      .columnsGap(10)      .rowsGap(8)      .margin({left: 15, right:15})    }    .margin({top:20})  }}@Reusable@Componentstruct MyItem{  @State item:Item | null = null  aboutToReuse(params: Record<string, Item>) {    this.item = params.item;    console.info('Reuse item:' + this.item)  }  aboutToAppear() {    console.info('item:' + this.item)  }  build(){    Column(){      if(this.item)      {        Text('第' + `${this.item.index.toString()}` + '个')        Image(this.item.image)          .objectFit(ImageFit.Fill)          .sharedTransition(`sharedImage${this.item.index}`, {duration: 300, curve: Curve.Linear,delay:50})      }    }    .backgroundColor('#ffa4eaac')    .borderRadius(8)  }}

3.4点击进入的详情页 

import router from '@ohos.router'import Item  from './Item'@Entry@Componentstruct Preview {  @State item: Item | null = null  @State index: number = 0  aboutToAppear(): void {    const params:object = router.getParams()    this.item = params['item']    this.index = params['index']  }  build() {    Row(){      Image(this.item?.image).width('100%')        .sharedTransition(`sharedItem${this.index}`, {duration: 300, curve: Curve.Linear, delay: 50})        .align(Alignment.Center)        .onClick( () => {          router.back()        })    }.width('100%')    .height('100%')    .backgroundColor(Color.Black)    .alignSelf(ItemAlign.Center)  }}

结语

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

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

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

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


点击全文阅读


本文链接:http://zhangshiyu.com/post/174799.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1