Flutter可滚动组件(4):GridView(网格布局)

 


在 Flutter 中,GridView 是一个展示数据网格的滚动小部件,类似于表格视图,其中子控件被组织成行和列。它非常适合于展示图像网格、小部件集合等。GridView 同样支持懒加载,这意味着只有当内容进入视口时才会被构建。

一、基本用法

GridView 最基本的用法是包裹一个网格项的集合:

GridView(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  children: <Widget>[
    Container(color: Colors.red,),
    Container(color: Colors.blue,),
    // ... 更多的容器
  ],
)

二、网格委托

GridView使用GridDelegate来控制网格的布局属性,如子控件的大小、间距等。Flutter 提供了几种GridDelegate


(1)SliverGridDelegateWithFixedCrossAxisCount

这个委托创建一个网格,其交叉轴(列)的数量是固定的:

SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 3, // 横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商
  childAspectRatio: 3, // 子元素的宽高比。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度
  mainAxisSpacing = 0.0, // 主轴方向子元素的间距
  crossAxisSpacing = 0.0, // 横轴方向子元素的间距
)

可以发现,子元素的大小是通过crossAxisCountchildAspectRatio两个参数共同决定的。注意,这里的子元素指的是子组件的最大显示空间,注意确保子组件的实际大小不要超出子元素的空间。

下面看一个例子:

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MyHomeBody(),
      ),
    );
  }
}

class MyHomeBody extends StatelessWidget {
  const MyHomeBody({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return GridView(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3, // 横轴三个子元素
            childAspectRatio: 1.0 // 宽高比为1时,子元素的长宽一样
            ),
        children: const <Widget>[
          Icon(Icons.ac_unit),
          Icon(Icons.airport_shuttle),
          Icon(Icons.all_inclusive),
          Icon(Icons.beach_access),
          Icon(Icons.cake),
          Icon(Icons.free_breakfast)
        ]);
  }
}

效果图如下所示:

Flutter_view_L.png


(2)SliverGridDelegateWithMaxCrossAxisExten

这个委托创建一个网格,其交叉轴(列)的最大宽度是固定的:

SliverGridDelegateWithMaxCrossAxisExtent({
  maxCrossAxisExtent: 450.0, // maxCrossAxisExtent为子元素在横轴上的最大长度
  childAspectRatio: 3, // 子元素的宽高比。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度
  mainAxisSpacing = 0.0, // 主轴方向子元素的间距
  crossAxisSpacing = 0.0, // 横轴方向子元素的间距
})

maxCrossAxisExtent为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的,举个例子,如果ViewPort的横轴长度是 450,那么maxCrossAxisExtent的值在区间 [450/4,450/3] 内。其他参数和SliverGridDelegateWithFixedCrossAxisCount相同。

下面我们看一个例子:

GridView(
  padding: EdgeInsets.zero,
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
      maxCrossAxisExtent: 120.0,
      childAspectRatio: 2.0 //宽高比为2
  ),
  children: const <Widget>[
    Icon(Icons.ac_unit),
    Icon(Icons.airport_shuttle),
    Icon(Icons.all_inclusive),
    Icon(Icons.beach_access),
    Icon(Icons.cake),
    Icon(Icons.free_breakfast),
  ],
);

效果图如下所示:

Flutter_view_M.png


三、构建方式

3.1 GridView 构建

只需要构建一个 List<Widget> 传入到 children 和构建一个 gridDelegate 即可:

import 'package:flutter/material.dart';
 
void main() => runApp(const MyApp());
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyGridView(),
    );
  }
}

// 图像数据源
List listData=[
  {
    "url": 'https://www.itying.com/images/flutter/1.png',
  },
  {
    "url": 'https://www.itying.com/images/flutter/2.png',
  },
  {
    "url": 'https://www.itying.com/images/flutter/3.png',
  },
  {
    "url": 'https://www.itying.com/images/flutter/4.png',
  },
  {
    "url": 'https://www.itying.com/images/flutter/5.png',
  },
  {
    "url": 'https://www.itying.com/images/flutter/6.png',
  },      
];

class MyGridView extends StatelessWidget {
  const MyGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView(
      // 次轴固定数量的 GridDelegate
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(        
        crossAxisCount: 3, // 数量设置3        
        mainAxisSpacing: 6, // 主轴间距        
        crossAxisSpacing: 12, // 次轴间距        
        childAspectRatio: 4 / 3, // 子元素宽高比率
      ),
      // 构建子项
      children: List.generate(100, (index) {
        return getItem(index);
      }),
    );
  }

  // 获取子项目
  Widget getItem(int index) {
    var item = listData[index % 6];
    return Image.network(
      item["url"],
      fit: BoxFit.cover,
    );
  }
}

效果图如下所示:

Flutter_gridViw_A.png


3.2 GridView.builder 构建

GridView.builder是一种按需构建的GridView,它只会构建当前可见区域的项,并且会在滚动过程中重复使用已构建的项,这种方式适用于大型网格布局。

class MyGridView extends StatelessWidget {
  const MyGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      // 最大次轴「范围」的 GridDelegate
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(        
        maxCrossAxisExtent: 140, // 最大次轴宽度(或高度)        
        mainAxisSpacing: 6, // 主轴间距        
        crossAxisSpacing: 12, // 次轴间距        
        childAspectRatio: 16 / 9, // 子项宽高比率
      ),
      itemBuilder: (context, index) {
        return getItem(index);
      },      
      itemCount: 100, // 子项数量
    );
  }

  // 获取子项目
  Widget getItem(int index) {
    var item = listData[index % 6];
    return Image.network(
      item["url"],
      fit: BoxFit.cover,
    );
  }
}

效果图如下所示:

Flutter_gridViw_B.png


3.3 GridView.count 构建

GridView.count则是一种固定数量的构建方式,你可以指定每行(列)显示的项的数量,然后GridView会根据这个数量自动调整布局。

class MyGridView extends StatelessWidget {
  const MyGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 6, // 数量设置3 
      mainAxisSpacing: 6, // 主轴间距 
      crossAxisSpacing: 12, // 次轴间距 
      childAspectRatio: 4 / 3, // 子项宽高比率
      children: List.generate(100, (index) {
        return getItem(index);
      }),
    );
  }

  // 获取子项目
  Widget getItem(int index) {
    var item = listData[index % 6];
    return Image.network(
      item["url"],
      fit: BoxFit.cover,
    );
  }
}

效果图如下所示:

Flutter_gridViw_C.png


3.4 GridView.extent 构建

GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建横轴子元素为固定最大长度的的GridView

class MyGridView extends StatelessWidget {
  const MyGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.extent(
      maxCrossAxisExtent: 140, // 最大次轴范围 
      mainAxisSpacing: 6, // 主轴间距 
      crossAxisSpacing: 12, // 次轴间距 
      childAspectRatio: 16 / 9, // 子项宽高比率
      children: List.generate(100, (index) {
        return getItem(index);
      }),
    );
  }

  // 获取子项目
  Widget getItem(int index) {
    var item = listData[index % 6];
    return Image.network(
      item["url"],
      fit: BoxFit.cover,
    );
  }
}

效果图如下所示:

Flutter_gridViw_D.png


3.5 GridView.custom 构建

GridView.custom构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建横轴子元素为固定最大长度的的GridView

class MyGridView extends StatelessWidget {
  const MyGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.custom(
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 140, // 最大次轴范围 
        mainAxisSpacing: 6, // 主轴间距
        crossAxisSpacing: 12, // 次轴间距 
        childAspectRatio: 16 / 9, // 子项宽高比率
      ),
      childrenDelegate: SliverChildListDelegate(
        List.generate(100, (index) {
          return getItem(index);
        }),
      ),
    );
  }

  // 获取子项目
  Widget getItem(int index) {
    var item = listData[index % 6];
    return Image.network(
      item["url"],
      fit: BoxFit.cover,
    );
  }
}

效果图如下所示:

Flutter_gridViw_E.png


四、性能优化

当使用 GridView 显示大量数据时,你可以考虑以下性能优化方式:

  • 使用GridView.builderGridView.count来按需构建网格项,避免一次性构建所有的项。
  • 如果网格项有固定尺寸,请使用childAspectRatio属性来指定网格项的宽高比例,以避免动态计算尺寸带来的性能开销。
  • 使用ScrollController来控制滚动,并使用addPostFrameCallback在构建完成后延迟加载数据。

五、常见问题和解决方法

在使用 GridView 时,你可能会遇到一些常见的问题。以下是其中一些问题和相应的解决方法:

  • GridView 无法滚动:与 ListView 类似,确保包含 GridView 的容器具有足够的空间来进行滚动,或者尝试将 GridView 的父级包装在一个可滚动的容器中。
  • GridView 不显示或空白:检查是否正确设置了itemCount属性,并且itemBuilder方法返回了有效的项。
  • GridView 滚动不流畅:可能是由于网格项过多或构建过程中的性能问题导致的。尝试使用按需构建的GridView.builderGridView.count,并使用性能优化技术来提高滚动性能。

参考:

Flutter 中的 GridView 小部件:全面指南_flutter gridview-CSDN博客

Flutter速来系列23-1、ListView和GridView,躲不掉避不开的列表一、引言 在Flutter中,Li - 掘金


posted @   fengMisaka  阅读(488)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示