Flutter高效网格布局插件efficient_intrinsic_gridview的使用

Flutter高效网格布局插件efficient_intrinsic_gridview的使用

开发状态:

此包仍处于开发阶段,功能完善后将更新并改进其文档及功能。还有许多任务正在推进中。

简介

这是一个允许每个交叉轴具有自身固有大小的GridView,即垂直滚动时为每个交叉轴设置其自身的固有高度,或水平滚动时为其设置自身的固有宽度。

什么是固有?

假设你正在使用带有垂直滚动的GridView,在这种情况下,交叉轴意味着每一行。假设每行的元素有不同的高度,并且在渲染特定行时,你想设置该特定行中最大元素的高度,这就是为GridView的每个交叉轴设置固有尺寸的想法。

默认情况下,GridView不允许这样做。在GridView中,你必须设置一个固定的mainAxisExtent,或者GridView会自动为每个交叉轴计算一个mainAxisExtent。例如,A交叉轴的最大尺寸为X,B交叉轴的最大尺寸为Y。在GridView中,你只能使用Y作为mainAxisExtent,因此即使A交叉轴的尺寸小于A,也会留下不必要的空白空间;如果大于B,则可能会抛出RenderFlex溢出错误。

这就是我产生创建此包想法的原因!在此包中,我们根据每个交叉轴元素的最大尺寸拥有多个mainAxisExtent。

为什么我的包里有“高效”这个词?

因为它高效。

这个Intrinsic名称灵感来自Flutter框架提供的IntrinsicHeight和IntrinsicWidth小部件。为了展示什么是低效的,我还创建了EfficientIntrinsicGridView.shrinkWrap,它是通过组合IntrinsicHeight和IntrinsicWidth来实现的,用于可滚动的行和列以呈现GridView。我将其命名为shrinkWrap,因为大多数新开发者在Flutter抛出滚动项无限大小错误时会学习到shrinkWrap。这个小部件可以防止该错误。

但是,假设GridView中有100个项目,IntrinsicHeight和IntrinsicWidth已经是昂贵的小部件,当你使用EfficientIntrinsicGridView.shrinkWrap时,你会一次性渲染所有100个昂贵的小部件。应用程序将使用大量内存,有时甚至会导致应用程序崩溃,如果你的内存消耗高于可用RAM内存。因此,不要使用EfficientIntrinsicGridView.shrinkWrap,它效率不高。

关于无限大小的错误,我个人讨厌在GridView上设置shrinkWrap为true。所以要么正确设置小部件的大小,要么从IntrinsicController访问EfficientIntrinsicGridView的总计算大小,我最喜欢的方法是使用CustomScrollView。

现在让我们来看看高效的解决方案:

EfficientIntrinsicGridView 和 EfficientIntrinsicGridView.builder

两者都有其自己的高效算法来计算交叉轴项目的固有mainAxisExtent,而无需使用昂贵的IntrinsicHeight和IntrinsicWidth小部件。与EfficientIntrinsicGridView.shrinkWrap不同,它不会一次性渲染所有100个元素。在计算大小之后,此EfficientIntrinsicGridView的自定义SliverGridDelegate仅渲染用户可见的元素,外加一个额外的缓冲区。无论可用项目是10个还是100个,渲染这些元素时的内存消耗将保持不变。

这就是为什么我的包是高效的!

示例代码

以下是完整的示例代码,展示了如何使用EfficientIntrinsicGridView

import 'package:efficient_intrinsic_gridview/efficient_intrinsic_gridview.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: VerticalGridViewExample()));
}

class VerticalGridViewExample extends StatefulWidget {
  const VerticalGridViewExample({super.key});

  [@override](/user/override)
  State<VerticalGridViewExample> createState() => _VerticalGridViewExampleState();
}

class _VerticalGridViewExampleState extends State<VerticalGridViewExample> {
  final controller = IntrinsicController(); // 控制器用于管理GridView的状态

  [@override](/user/override)
  void dispose() {
    controller.dispose(); // 释放资源
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          ValueListenableBuilder(
            valueListenable: controller,
            builder: (context, loading, child) {
              if (loading) {
                return const CircularProgressIndicator(); // 加载指示器
              }
              return IconButton(
                onPressed: () {
                  controller.widgetList =
                      controller.widgetList.map((e) => e).toList()..shuffle(); // 随机打乱列表
                },
                icon: const Icon(Icons.refresh), // 刷新按钮
              );
            },
          ),
        ],
      ),
      body: EfficientIntrinsicGridView(
        preventOverflow: false, // 是否防止溢出
        preventRebuild: true, // 是否防止重新构建
        intrinsicController: controller, // 绑定控制器
        crossAxisCount: 2, // 每行显示的列数
        children: [
          for (int i = 0; i < 100; i++) // 生成100个子项
            Container(
              decoration: BoxDecoration(
                  border: Border.all(
                    color: Colors.blue,
                    width: 5,
                  )),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  for (int j = 0; j < (i % 10) + 1; j++) // 每个容器内嵌套子项
                    _VerticalItem(
                      itemCount: j,
                      index: i,
                    ),
                ],
              ),
            ),
        ],
      ),
    );
  }
}

class _VerticalItem extends StatefulWidget {
  const _VerticalItem({
    super.key,
    required this.itemCount,
    required this.index,
  });

  final int itemCount;
  final int index;

  [@override](/user/override)
  State<_VerticalItem> createState() => _VerticalItemState();
}

class _VerticalItemState extends State<_VerticalItem> {
  double height = 20; // 初始高度

  [@override](/user/override)
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        height += height; // 点击时增加高度
        setState(() {});
      },
      child: Container(
        color: Colors.red,
        margin: const EdgeInsets.only(bottom: 2),
        height: height, // 动态高度
        child: Text(
          "Item ${widget.itemCount + 1}",
          textAlign: TextAlign.center,
          style: const TextStyle(
            color: Colors.white,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

// 水平方向的GridView示例
class HorizontalGridViewExample extends StatefulWidget {
  const HorizontalGridViewExample({super.key});

  [@override](/user/override)
  State<HorizontalGridViewExample> createState() => _HorizontalGridViewExampleState();
}

class _HorizontalGridViewExampleState extends State<HorizontalGridViewExample> {
  final controller = IntrinsicController();

  [@override](/user/override)
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            onPressed: () {
              controller.widgetList = controller.widgetList.map((e) => e).toList()..shuffle();
            },
            icon: const Icon(Icons.refresh),
          ),
        ],
      ),
      body: EfficientIntrinsicGridView(
        scrollDirection: Axis.horizontal, // 水平滚动
        preventRebuild: false,
        crossAxisCount: 3,
        intrinsicController: controller,
        children: [
          for (int i = 0; i < 20; i++)
            Container(
              decoration: BoxDecoration(
                  border: Border.all(
                    color: Colors.yellow,
                    width: 5,
                  )),
              child: Row(
                children: [
                  for (int j = 0; j < i + 1; j++)
                    _HorizontalItem(itemCount: j, index: i,),
                ],
              ),
            ),
        ],
      ),
    );
  }
}

class _HorizontalItem extends StatefulWidget {
  const _HorizontalItem({
    super.key,
    required this.itemCount,
    required this.index,
  });

  final int itemCount;
  final int index;

  [@override](/user/override)
  State<_HorizontalItem> createState() => _HorizontalItemState();
}

class _HorizontalItemState extends State<_HorizontalItem> {
  double width = 20; // 初始宽度

  [@override](/user/override)
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        width += width; // 点击时增加宽度
        setState(() {});
      },
      child: Container(
        color: Colors.blue,
        margin: const EdgeInsets.only(bottom: 2),
        width: width, // 动态宽度
        child: Text(
          "Item ${widget.itemCount + 1}",
          textAlign: TextAlign.center,
          style: const TextStyle(
            color: Colors.white,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

更多关于Flutter高效网格布局插件efficient_intrinsic_gridview的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter高效网格布局插件efficient_intrinsic_gridview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


efficient_intrinsic_gridview 是一个用于 Flutter 的高效网格布局插件,它提供了类似于 GridView 的功能,但具有更高的性能和更灵活的自定义选项。这个插件特别适用于需要处理大量数据或需要自定义网格项大小的场景。

安装

首先,你需要在 pubspec.yaml 文件中添加依赖:

dependencies:
  efficient_intrinsic_gridview: ^0.0.1

然后运行 flutter pub get 来安装依赖。

基本用法

efficient_intrinsic_gridview 的使用方式与 GridView 类似,但提供了更多的自定义选项。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:efficient_intrinsic_gridview/efficient_intrinsic_gridview.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Efficient Intrinsic GridView Example'),
        ),
        body: EfficientIntrinsicGridView(
          crossAxisCount: 2, // 每行显示的列数
          crossAxisSpacing: 10.0, // 列间距
          mainAxisSpacing: 10.0, // 行间距
          padding: EdgeInsets.all(10.0), // 内边距
          children: List.generate(20, (index) {
            return Container(
              color: Colors.blue,
              child: Center(
                child: Text(
                  'Item $index',
                  style: TextStyle(color: Colors.white, fontSize: 20),
                ),
              ),
            );
          }),
        ),
      ),
    );
  }
}

主要属性

  • crossAxisCount: 每行显示的列数。
  • crossAxisSpacing: 列之间的间距。
  • mainAxisSpacing: 行之间的间距。
  • padding: 网格的内边距。
  • children: 网格中的子部件列表。

自定义网格项大小

efficient_intrinsic_gridview 允许你为每个网格项指定不同的大小。你可以通过 itemBuilderitemSizeBuilder 来实现这一点:

EfficientIntrinsicGridView.builder(
  crossAxisCount: 2,
  crossAxisSpacing: 10.0,
  mainAxisSpacing: 10.0,
  padding: EdgeInsets.all(10.0),
  itemCount: 20,
  itemBuilder: (context, index) {
    return Container(
      color: Colors.blue,
      child: Center(
        child: Text(
          'Item $index',
          style: TextStyle(color: Colors.white, fontSize: 20),
        ),
      ),
    );
  },
  itemSizeBuilder: (index) {
    return Size(100.0, 100.0 + (index % 3) * 50.0); // 自定义每个项目的大小
  },
);
回到顶部