返回

渲染瀑布流也容易,Flutter手绘瀑布流第二篇

前端

在上一篇文章中,我们已经实现了Flutter中的一个瀑布流。但是,它存在一个明显的缺陷,那就是它只能支持Cupertino风格的小部件。

这对于一些想要在应用程序中使用Material风格的小部件的用户来说,是一个很大的限制。因此,在本文中,我们将探讨如何使用RenderObject来实现Flutter中的瀑布流。

使用RenderObject来实现瀑布流具有几个优点。首先,它允许我们使用Flutter原生渲染机制来渲染瀑布流的每一行数据。这使得瀑布流更加高效和灵活。

其次,它允许我们使用任何类型的Flutter小部件来填充瀑布流。这使得瀑布流更加通用,可以用于各种各样的应用程序。

渲染瀑布流

为了使用RenderObject来渲染瀑布流,我们需要创建一个新的RenderObject子类。这个子类将负责测量、布局和绘制瀑布流的每一行数据。

在我们的例子中,我们将创建一个名为SliverFlowDelegate的新RenderObject子类。这个子类将负责测量、布局和绘制瀑布流的每一行数据。

import 'package:flutter/rendering.dart';

class SliverFlowDelegate extends RenderSliver {
  SliverFlowDelegate({
    required this.itemBuilder,
    required this.itemCount,
    required this.crossAxisSpacing,
    required this.mainAxisSpacing,
    required this.childSize,
  });

  final IndexedWidgetBuilder itemBuilder;
  final int itemCount;
  final double crossAxisSpacing;
  final double mainAxisSpacing;
  final Size childSize;

  @override
  void performLayout() {
    double contentHeight = 0.0;
    double crossAxisOffset = 0.0;
    double mainAxisOffset = 0.0;

    for (int i = 0; i < itemCount; i++) {
      final BoxConstraints constraints = BoxConstraints.tight(childSize);
      final SliverMultiBoxAdaptorElement child = childManager.createChild(i, constraints);
      child.layout(constraints);

      final double childHeight = child.size.height;
      final double childWidth = child.size.width;

      if (crossAxisOffset + childWidth > constraints.maxWidth) {
        crossAxisOffset = 0.0;
        mainAxisOffset += childHeight + mainAxisSpacing;
      }

      child.geometry = SliverGeometry(
        scrollOffset: mainAxisOffset,
        crossScrollOffset: crossAxisOffset,
        paintExtent: childHeight,
        maxPaintExtent: childHeight,
      );

      crossAxisOffset += childWidth + crossAxisSpacing;
      contentHeight += childHeight + mainAxisSpacing;
    }

    geometry = SliverGeometry(
      scrollExtent: contentHeight,
      maxScrollExtent: contentHeight,
    );
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    for (int i = 0; i < itemCount; i++) {
      final BoxConstraints constraints = BoxConstraints.tight(childSize);
      final SliverMultiBoxAdaptorElement child = childManager.createChild(i, constraints);
      child.paint(context, offset);

      final double childHeight = child.size.height;
      final double childWidth = child.size.width;

      if (crossAxisOffset + childWidth > constraints.maxWidth) {
        crossAxisOffset = 0.0;
        mainAxisOffset += childHeight + mainAxisSpacing;
      }

      offset = offset.translate(0.0, childHeight + mainAxisSpacing);
    }
  }
}

SliverFlowDelegate类具有以下属性:

  • itemBuilder: 一个函数,它返回瀑布流的每一行数据对应的部件。
  • itemCount: 瀑布流中的数据项总数。
  • crossAxisSpacing: 瀑布流中相邻列之间的间距。
  • mainAxisSpacing: 瀑布流中相邻行之间的间距。
  • childSize: 瀑布流中每一行数据对应的部件的大小。

SliverFlowDelegate类还具有以下方法:

  • performLayout(): 该方法测量和布局瀑布流的每一行数据。
  • paint(): 该方法绘制瀑布流的每一行数据。

使用SliverFlowDelegate创建瀑布流

为了使用SliverFlowDelegate类创建瀑布流,我们需要创建一个新的SliverPersistentHeaderDelegate子类。这个子类将负责将SliverFlowDelegate类包装成一个SliverPersistentHeader对象。

import 'package:flutter/material.dart';

class SliverFlowPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  SliverFlowPersistentHeaderDelegate({
    required this.childSize,
    required this.itemBuilder,
    required this.itemCount,
    required this.crossAxisSpacing,
    required this.mainAxisSpacing,
  });

  final Size childSize;
  final IndexedWidgetBuilder itemBuilder;
  final int itemCount;
  final double crossAxisSpacing;
  final double mainAxisSpacing;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return SliverFlowDelegate(
      childSize: childSize,
      itemBuilder: itemBuilder,
      itemCount: itemCount,
      crossAxisSpacing: crossAxisSpacing,
      mainAxisSpacing: mainAxisSpacing,
    );
  }

  @override
  double get maxExtent => 10000.0;

  @override
  double get minExtent => 10000.0;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}

SliverFlowPersistentHeaderDelegate类具有以下属性:

  • childSize: 瀑布流中每一行数据对应的部件的大小。
  • itemBuilder: 一个函数,它返回瀑布流的每一行数据对应的部件。
  • itemCount: 瀑布流中的数据项总数。
  • crossAxisSpacing: 瀑布流中相邻列之间的间距。
  • mainAxisSpacing: 瀑布流中相邻行之间的间距。

SliverFlowPersistentHeaderDelegate类还具有以下方法:

  • build(): 该方法构建瀑布流。
  • maxExtent: 返回瀑布流的最大高度。
  • minExtent: 返回瀑布流的最小高度。
  • shouldRebuild(): 该方法决定瀑布流是否需要重建。

使用SliverFlowPersistentHeaderDelegate创建瀑布流

为了使用SliverFlowPersistentHeaderDelegate类创建瀑布流,我们需要创建一个新的CustomScrollView对象。这个对象将负责滚动瀑布流。

import 'package:flutter/material.dart';

class SliverFlow extends StatelessWidget {
  SliverFlow({
    required this.childSize,
    required this.itemBuilder,
    required this.itemCount,
    required this.crossAxisSpacing,
    required this.mainAxisSpacing,
  });

  final Size childSize;
  final IndexedWidgetBuilder itemBuilder;
  final int itemCount;
  final double crossAxisSpacing;
  final double mainAxisSpacing;

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverPersistentHeader(
          delegate: SliverFlowPersistentHeaderDelegate(
            childSize: childSize,
            itemBuilder: itemBuilder,
            itemCount: itemCount,
            crossAxisSpacing: crossAxisSpacing,
            mainAxisSpacing: mainAxisSpacing,
          ),
        ),
      ],
    );
  }
}

SliverFlow类具有以下属性:

  • childSize: 瀑布流中每一行数据对应的部件的大小。
  • itemBuilder: 一个函数,它返回瀑布流的每一行数据对应的部件。
  • itemCount: 瀑布流中的数据项总数。
  • crossAxisSpacing: 瀑布流中相邻列之间的间距。
  • mainAxisSpacing: 瀑布流中相邻行之间的间距。

SliverFlow类还具有以下方法:

  • build(): 该方法构建瀑布流。

使用SliverFlow创建瀑布流

为了使用SliverFlow类创建瀑布流,我们需要创建一个新的MaterialApp对象。这个对象将负责启动应用程序。

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: SliverFlow(
      childSize: Size(200.0, 200