前言
鴻蒙中的通過(guò)ForEach循環(huán)渲染會(huì)一次性創(chuàng)建出所有子組件,即便不在可視區(qū)域外,而LazyForEach則可以進(jìn)行按需迭代數(shù)據(jù),當(dāng)組件滑出可視區(qū)域外時(shí),框架會(huì)進(jìn)行組件銷毀回收以降低內(nèi)存占用。而LazyForEach需要開(kāi)發(fā)者實(shí)現(xiàn)IDataSource數(shù)據(jù)源相關(guān)接口。
封裝通用數(shù)據(jù)源類
我們可以封裝一個(gè)類BasicDataSource,遵循IDataSource并實(shí)現(xiàn)相關(guān)接口,同時(shí)我們可以提供相關(guān)的數(shù)據(jù)操作方法,以便調(diào)用方在操作數(shù)據(jù)后可以通知組件進(jìn)行數(shù)據(jù)重新渲染。
以下為BasicDataSource類的實(shí)現(xiàn):
/**
* LazyForEach 基礎(chǔ)數(shù)據(jù)源
*/
export class BasicDataSource<T> implements IDataSource {
// 監(jiān)聽(tīng)器
private listeners: DataChangeListener[] = [];
// 數(shù)據(jù)數(shù)組
private dataArray: T[]
constructor(dataArray: T[] = []) {
this.dataArray = dataArray
}
// MARK: - IDataSource
// 總條數(shù)
totalCount(): number {
return this.dataArray.length
}
// 獲取某條數(shù)據(jù)
getData(index: number): T {
return this.dataArray[index]
}
// 該方法為框架側(cè)調(diào)用,為L(zhǎng)azyForEach組件向其數(shù)據(jù)源處添加listener監(jiān)聽(tīng)
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.includes(listener)) {
return
}
this.listeners.push(listener)
}
// 該方法為框架側(cè)調(diào)用,為對(duì)應(yīng)的LazyForEach組件在數(shù)據(jù)源處去除listener監(jiān)聽(tīng)
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener)
if (pos >= 0) {
this.listeners.splice(pos, 1)
}
}
// MARK: - Public Methods
/**
* 刷新數(shù)據(jù)
*/
public reloadData(): void {
this.notifyDataReloaded()
}
/**
* 查:獲取所有數(shù)據(jù)
* @returns 數(shù)據(jù)數(shù)組
*/
public getAllArray(): T[] {
return this.dataArray
}
/**
* 增:添加單條數(shù)據(jù)
* @param data 單條數(shù)據(jù)
*/
public addData(data: T): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1);
}
/**
* 增:添加多條數(shù)據(jù)
* @param dataArray 多條數(shù)據(jù)
*/
public addDataArray(dataArray: T[]): void {
dataArray.forEach((data) => {
this.dataArray.push(data)
})
this.notifyDataAdd(this.dataArray.length - 1);
}
/**
* 增:在某個(gè)位置插入數(shù)據(jù)
* @param data 要插入的數(shù)據(jù)
* @param index 插入位置
*/
public insertData(data: T, index: number) {
this.dataArray.splice(index, 0, data)
this.notifyDataAdd(index);
}
/**
* 改:更新某索引下的數(shù)據(jù)
* @param data 數(shù)據(jù)
* @param index 索引
*/
public updateData(data: T, index: number): void {
this.dataArray.splice(index, 1, data)
this.notifyDataChange(index)
}
/**
* 改:交換數(shù)據(jù)位置
* @param from 起始位置
* @param to 目標(biāo)位置
*/
public exchangeData(from: number, to: number): void {
let fromData = this.dataArray[from]
let toData = this.dataArray[to]
this.dataArray[from] = toData
this.dataArray[to] = fromData
this.notifyDataMove(from, to)
}
/**
* 改:重置數(shù)據(jù)
* @param dataArray 新的數(shù)據(jù)數(shù)組
*/
public resetData(dataArray: T[]): void {
this.dataArray = dataArray
this.notifyDataReloaded()
}
/**
* 刪:刪除指定索引的數(shù)據(jù)
* @param index 索引
*/
public deleteData(index: number): void {
this.dataArray.splice(index, 1)
this.notifyDataDelete(index)
}
// MARK: - Private Methods
/**
* 通知組件重新加載所有數(shù)據(jù)
*/
private notifyDataReloaded(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
/**
* 通知組件index的位置有數(shù)據(jù)添加
* @param index 當(dāng)前傳入的索引值
*/
private notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
/**
* 通知組件數(shù)據(jù)有移動(dòng)。將from和to位置的數(shù)據(jù)進(jìn)行交換
* @param from
* @param to
*/
private notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to) // 系統(tǒng) api 命名不嚴(yán)謹(jǐn)
})
}
/**
* 通知組件刪除index位置的數(shù)據(jù)并刷新LazyForEach的展示內(nèi)容
*/
private notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
/**
* 通知組件index的位置有數(shù)據(jù)有變化
*/
private notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
}
使用示例
// 復(fù)用組件
@Reusable
@Component
export struct CardView {
// 被\@State修飾的變量item才能更新,未被\@State修飾的變量不會(huì)更新。
@State item: string = '';
aboutToReuse(params: Record<string, Object>): void {
this.item = params.item as string;
console.info(`>>>>> aboutToReuse: ${this.item}`)
}
aboutToAppear(): void {
console.info(`>>>>> aboutToAppear: ${this.item}`)
}
build() {
Column() {
Text(this.item)
.fontSize(30)
}
.width('100%')
.borderWidth(1)
.height(100)
}
}
import { BasicDataSource } from '../classes/BasicDataSource';
import { CardView } from '../components/CardView';
@Entry
@Component
struct Index {
private dataSource: BasicDataSource<string> = new BasicDataSource<string>()
private deleteIndex: string = ''
aboutToAppear() {
this.reset()
}
build() {
Column() {
this.TopView()
List() {
LazyForEach(this.dataSource, (item: string) => {
ListItem() {
CardView({ item: item })
}
}, (item: string) => item)
}
.width('100%')
.layoutWeight(1)
.borderWidth(1)
.borderColor(Color.Red)
}
}
@Builder
TopView() {
Column() {
Row({ space: 5 }) {
Button('添加').onClick((event: ClickEvent) => {
let date = new Date()
this.dataSource.addData(date.toString())
})
Button('更新').onClick((event: ClickEvent) => {
let date = new Date()
this.dataSource.updateData(`更新:${date.toString()}`, 0)
})
Button('交換 1-3').onClick((event: ClickEvent) => {
this.dataSource.exchangeData(1, 3)
})
}
Row({ space: 5 }) {
Button('重置').onClick((event: ClickEvent) => {
this.reset()
})
Button('刷新').onClick((event: ClickEvent) => {
this.dataSource.reloadData()
})
Button('查詢').onClick((event: ClickEvent) => {
let count = this.dataSource.totalCount()
console.info(`>>>>> 總共${count}條數(shù)據(jù)`)
})
}
Row({ space: 5 }) {
TextInput({
placeholder: '輸入刪除數(shù)據(jù)下標(biāo)'
})
.type(InputType.Number)
.onChange((text) => {
this.deleteIndex = text
})
Button('刪除').onClick((event: ClickEvent) => {
let index = Number(this.deleteIndex)
if (index >= 0 && index < this.dataSource.totalCount()) {
this.dataSource.deleteData(index)
}
})
}
.width(200)
}
}
private reset() {
let dataArray: string[] = []
for (let i = 0; i < 100; i++) {
dataArray.push('原始' + i)
}
this.dataSource.resetData(dataArray)
}
}