瀑布流
注意
WaterFlow 只有一个子组件就是 FlowItem
分成几列 用 columnsTemplate
效果
参数
js
interface WaterFlowOptions {
// 设置Water 尾部组件
footer: CustomBuilder
// 可滚动组件得控制器,与滚动组件绑定
scroller: Scroller
// 设置FlowItem 分组.实现同一个瀑布流组件内部各分组使用不同列数混合布局(一般用不到)
sections:WaterFlowSection[]
}
属性
js
declare class WaterAttributes {
// 设置当前 瀑布流组件布局列得数量,不设置时候默认1列
// 例如'1fr 1fr 2fr'讲父组件分成3列,其中第一列和第二列各占1份,第三列占2份
columnsTemplate: string;
// 设置当前瀑布流组件布局行的数量,不设置时默认1行。
// 例如'1fr 1fr 2fr'讲父组件分成3行,其中第一行和第二行各占1份,第三行占2份
rowsTemplate: string;
// 列间距
columnsGap:(number)
// 行间距
rowsGap:(number)
// 设置布局得主轴方向,不写默认就是垂直方向
layoutDirection:FlexDirection.Column
// 是否支持滚动手势(不重要)
enableScrollInteraction:boolean
// 摩擦系数
// 设置摩擦系数
friction:number
// 缓存个数
cachedCount:(number)
}
事件
js
// 瀑布流到达起始位置的时候触发
onReachStart(){}
//瀑布流到达末尾的时候触发
onReachEnd(){}
// 瀑布流开始滑动时触发,事件参数传入即将发生的滑动量
// offset 即将发生的滑动量 ScrollState 滑动状态 2 就是惯性 1 就是滚动 0 就是空闲
onScrollFrameBegin((offset:number,state:ScrollState)=>{})
// 当前瀑布流显示的起始位置/终止位置 瀑布流初始化的时候触发一次
onScrollIndex((first:number,last:number)=>{})
示例
基础版
- 第一步准备数据
js
// WaterFlowDataSource.ets
// 实现IDataSource接口的对象,用于瀑布流组件加载数据
export class WaterFlowDataSource implements IDataSource {
private dataArray: number[] = [];
private listeners: DataChangeListener[] = [];
constructor() {
for (let i = 0; i < 100; 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();
}
}
- 第二步实现瀑布流组件
js
// Index.ets
import { WaterFlowDataSource } from './WaterFlowDataSource';
enum FooterState {
Loading = 0,
End = 1
}
@Entry
@Component
struct WaterFlowDemo {
@State minSize: number = 80;
@State maxSize: number = 180;
@State fontSize: number = 24;
@State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
@State footerState: FooterState = FooterState.Loading;
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 < 100; i++) {
this.itemWidthArray.push(this.getSize());
this.itemHeightArray.push(this.getSize());
}
}
aboutToAppear() {
this.setItemSizeArray();
}
@Builder
itemFoot() {
// 不要直接用IfElse节点作为footer的根节点。
Column() {
if (this.footerState == FooterState.Loading) {
Text(`加载中...`)
.fontSize(10)
.backgroundColor(Color.Red)
.width(50)
.height(50)
.align(Alignment.Center)
.margin({ top: 2 })
} else if (this.footerState == FooterState.End) {
Text(`到底啦...`)
.fontSize(10)
.backgroundColor(Color.Red)
.width(50)
.height(50)
.align(Alignment.Center)
.margin({ top: 2 })
} else {
Text(`Footer`)
.fontSize(10)
.backgroundColor(Color.Red)
.width(50)
.height(50)
.align(Alignment.Center)
.margin({ top: 2 })
}
}
}
build() {
Column({ space: 2 }) {
WaterFlow({ footer: this.itemFoot() }) {
LazyForEach(this.dataSource, (item: number) => {
FlowItem() {
Column() {
Text("N" + item).fontSize(12).height('16')
// 存在对应的jpg文件才会显示图片
Image('res/waterFlowTest(' + item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height(this.itemHeightArray[item % 100])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
.columnsTemplate("1fr 1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('100%')
// 触底加载数据
.onReachEnd(() => {
console.info("onReachEnd")
if (this.dataSource.totalCount() > 200) {
this.footerState = FooterState.End;
return;
}
setTimeout(() => {
for (let i = 0; i < 100; i++) {
this.dataSource.addLastItem();
}
}, 1000)
})
.onReachStart(() => {
console.info('waterFlow reach start');
})
.onScrollStart(() => {
console.info('waterFlow scroll start');
})
.onScrollStop(() => {
console.info('waterFlow scroll stop');
})
.onScrollFrameBegin((offset: number, state: ScrollState) => {
console.info('waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString());
return { offsetRemain: offset };
})
.onScrollIndex((first: number, last: number) => {
console.info(first.toString())
console.info(last.toString())
})
}
}
}
自动计算列数
- repeat
注意
可使用 columnsTemplate('repeat(auto-fill,track-size)')
根据给定的列宽 track-size 自动计算列数,其中 repeat、auto-fill 为关键字,
track-size 为可设置的宽度,支持的单位包括 px、vp、%或有效数字,默认单位为 vp,
- 这里就是宽度 80vp 自己划分列数
js
.columnsTemplate('repeat(auto-fill,80)')