Skip to content

瀑布流

注意

  1. WaterFlow 只有一个子组件就是 FlowItem

  2. 分成几列 用 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)')