Flutter网格布局插件flutter_grid_layout的使用

发布于 1周前 作者 h691938207 来自 Flutter

Flutter网格布局插件flutter_grid_layout的使用

拥抱网格布局对于创建适应性强且用户友好的数字设计至关重要。网格确保视觉一致性,优先展示内容,并能够无缝适应各种屏幕尺寸,从而提升跨设备和显示屏的整体用户体验。

特性

开始使用

flutter pub add flutter_grid_layout

简单示例

GridContainer(
  columns: [0.2, null, 0.2],
  rows: [0.2, null, 0.2],
  children: [
    GridItem(
      start: const Size(1, 1),
      end: const Size(2, 2),
      child: Container(color: Colors.red),
    )
  ],
),

Centered Item

多个具有不同层次的项目

GridContainer(
  columns: [0.2, 0.3, 0.3, 0.2],
  rows: [0.2, 0.3, 0.3, 0.2],
  children: [
    GridItem(
      start: const Size(0, 0),
      end: const Size(4, 1),
      child: Container(color: Colors.red),
    ),
    GridItem(
      start: const Size(1, 0),
      end: const Size(3, 4),
      order: 1,
      child: Container(color: Colors.blue.withOpacity(0.5)),
    ),
    GridItem(
      start: const Size(2, 3),
      end: const Size(4, 4),
      child: Container(color: Colors.green),
    ),
  ],
),

Multiple Items

反向多个具有不同层次的项目

GridContainer(
  alignment: MainAxisAlignment.end,
  columns: [0.2, 0.3, 0.3, 0.2],
  rows: [0.2, 0.3, 0.3, 0.2],
  children: [
    GridItem(
      start: const Size(0, 0),
      end: const Size(4, 1),
      child: Container(color: Colors.red),
    ),
    GridItem(
      start: const Size(1, 0),
      end: const Size(3, 4),
      order: 1,
      child: Container(color: Colors.blue.withOpacity(0.5)),
    ),
    GridItem(
      start: const Size(2, 3),
      end: const Size(4, 4),
      child: Container(color: Colors.green),
    ),
  ],
),

Multiple Items - Reversed

完整示例代码

以下是一个完整的示例代码,展示了如何使用flutter_grid_layout插件来创建一个可拖拽调整大小的网格布局。

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_grid_layout/flutter_grid_layout.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

// 序列化组件状态
typedef ComponentData = Map<String, dynamic>;

// 组件数据属性
abstract class InterfaceComponent {
  static const key = '_component';
  static const order = '_order';
  static const endX = '_end_x';
  static const endY = '_end_y';
  static const startX = '_start_x';
  static const startY = '_start_y';
  static const start = '_start';
  static const end = '_end';
  static const shift = '_shift';
  static const color = 'color';
}

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

  [@override](/user/override)
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.light,
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      themeMode: ThemeMode.system,
      home: const BasicPage(),
    );
  }
}

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

  [@override](/user/override)
  BasicPageState createState() => BasicPageState();
}

class BasicPageState extends State<BasicPage> {
  List<ComponentData> data = [];
  late String key;

  Future<void> _save() async {
    // ... 存储数据
    setState(() {});
  }

  Future<void> save() async {
    // ... 其他与 `data` 的操作
    await _save();
  }

  Future<void> drop() async {
    data.clear();
    // ... 更新存储
  }

  Future<void> add(String key) async {
    List<Color> colors = Colors.primaries;
    Random random = Random();
    data.add({
      InterfaceComponent.key: key,
      InterfaceComponent.startX: 0,
      InterfaceComponent.startY: 0,
      InterfaceComponent.endX: 1 + random.nextInt(3),
      InterfaceComponent.endY: 1 + random.nextInt(2),
      InterfaceComponent.color: colors[random.nextInt(colors.length)],
    });
    await _save();
  }

  Future<void> adjust(int index, ComponentData change) async {
    data[index] = change;
    await _save();
  }

  Future<void> delete(int index) async {
    data.removeAt(index);
    await _save();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        toolbarHeight: 45,
        backgroundColor: Colors.blueGrey,
        title: const Text('布局管理器'),
        centerTitle: true,
        leadingWidth: 120,
        leading: Row(
          children: [
            IconButton(
              hoverColor: Colors.transparent,
              icon: const Icon(
                Icons.save,
                color: Colors.white70,
              ),
              tooltip: '保存',
              onPressed: save,
            ),
            IconButton(
              hoverColor: Colors.transparent,
              icon: const Icon(
                Icons.cancel,
                color: Colors.white70,
              ),
              tooltip: '删除',
              onPressed: drop,
            ),
          ],
        ),
        actions: [
          IconButton(
            hoverColor: Colors.transparent,
            icon: const Icon(
              Icons.add_circle_rounded,
              color: Colors.white70,
            ),
            tooltip: '添加',
            onPressed: () => add(UniqueKey().toString()),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(12),
        child: ComponentsBuilder(data,
            editMode: true, adjust: adjust, delete: delete),
      ),
    );
  }
}

class ComponentsBuilder extends StatelessWidget {
  final List<ComponentData> data;
  final bool editMode;
  final Function? adjust;
  final Function? delete;
  final _shift = InterfaceComponent.shift;
  final _order = InterfaceComponent.order;
  final _start = InterfaceComponent.start;
  final _end = InterfaceComponent.end;

  const ComponentsBuilder(
    this.data, {
    super.key,
    this.editMode = false,
    this.adjust,
    this.delete,
  });

  void resize(ComponentData change, Size start) {
    final scope = data[change[_order]];
    scope[_order] = change[_order];
    if (change[_shift] != null) {
      scope[InterfaceComponent.endX] += start.width - scope[InterfaceComponent.startX];
      scope[InterfaceComponent.endY] += start.height - scope[InterfaceComponent.startY];
      scope[InterfaceComponent.startX] = start.width;
      scope[InterfaceComponent.startY] = start.height;
    } else if (change[_start] != null) {
      scope[InterfaceComponent.startX] = start.width;
      scope[InterfaceComponent.startY] = start.height;
    } else if (change[_end] != null) {
      scope[InterfaceComponent.endX] = start.width + 1.0;
      scope[InterfaceComponent.endY] = start.height + 1.0;
    }
    adjust!(scope[_order], scope);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    const rowsCount = 6;
    const columnsCount = 6;
    return GridContainer(
      rows: List.filled(rowsCount, null),
      columns: List.filled(columnsCount, null),
      children: editMode
          ? [
              ...List.generate(rowsCount * columnsCount, (i) {
                final start = Size(
                  i % rowsCount.toDouble(),
                  (i ~/ rowsCount).toDouble(),
                );
                return GridItem(
                  start: start,
                  end: Size(start.width + 1, start.height + 1),
                  order: 0,
                  child: DragTarget<ComponentData>(
                    onWillAccept: (_) => true,
                    onAccept: (change) => resize(change, start),
                    builder: (context, candidateData, _) => Container(
                      decoration: BoxDecoration(
                        border: Border.all(color: Colors.grey),
                        color: candidateData.isEmpty
                            ? null
                            : Colors.green.shade200,
                      ),
                    ),
                  ),
                );
              }),
              ...List.generate(
                data.length,
                (i) => GridItem(
                  start: Size(data[i][InterfaceComponent.startX] + .0,
                      data[i][InterfaceComponent.startY] + .0),
                  end: Size(data[i][InterfaceComponent.endX] + .0,
                      data[i][InterfaceComponent.endY] + .0),
                  order: i + 1,
                  child: Draggable<ComponentData>(
                    data: {...data[i], _order: i, _shift: true},
                    feedback: Container(
                      color: Colors.amberAccent,
                      width: 32,
                      height: 24,
                    ),
                    child: Container(color: data[i][InterfaceComponent.color]),
                  ),
                ),
              ),
            ]
          : List.generate(
              data.length,
              (i) => GridItem(
                    start: Size(data[i][InterfaceComponent.startX] + .0,
                        data[i][InterfaceComponent.startY] + .0),
                    end: Size(data[i][InterfaceComponent.endX] + .0,
                        data[i][InterfaceComponent.endY] + .0),
                    child: const SizedBox(), // STUB
                  )),
    );
  }
}

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

1 回复

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


当然,以下是一个关于如何使用 flutter_grid_layout 插件在 Flutter 中创建网格布局的示例代码。需要注意的是,flutter_grid_layout 并不是 Flutter 官方的一个核心插件,而是一个第三方库。如果你发现找不到这个具体的包,可以考虑使用 Flutter 自带的 GridView 来实现网格布局。不过,为了符合你的要求,这里假设 flutter_grid_layout 存在并类似于其他网格布局插件。

首先,确保你的 pubspec.yaml 文件中已经添加了 flutter_grid_layout 依赖(注意:这个包名可能需要替换为实际存在的类似包名,因为 flutter_grid_layout 可能不是确切的包名):

dependencies:
  flutter:
    sdk: flutter
  flutter_grid_layout: ^x.y.z  # 替换为实际的版本号

然后运行 flutter pub get 来获取依赖。

接下来是一个使用 flutter_grid_layout(或类似插件)的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_grid_layout/flutter_grid_layout.dart';  // 假设包名正确

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Grid Layout Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Grid Layout Demo'),
      ),
      body: GridLayoutBuilder(
        itemCount: 20,  // 网格中的项目总数
        crossAxisCount: 4,  // 交叉轴上的子项数量(即每行的列数)
        crossAxisSpacing: 4.0,  // 交叉轴间距
        mainAxisSpacing: 4.0,  // 主轴间距
        childAspectRatio: 1.0,  // 子项宽高比
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 4,  // 如果插件支持,可以配置这里的参数
        ),
        itemBuilder: (context, index) {
          return Container(
            color: Colors.primaries[index % Colors.primaries.length],
            child: Center(
              child: Text(
                'Item $index',
                style: TextStyle(color: Colors.white),
              ),
            ),
          );
        },
      ),
    );
  }
}

// 假设 GridLayoutBuilder 是该插件提供的一个构建网格的 Widget
// 如果实际插件没有提供这样的 Widget,你可以使用 Flutter 自带的 GridView
// 下面的代码展示了如何使用 GridView 来实现类似的网格布局
/*
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Grid Layout Demo'),
      ),
      body: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 4,
          crossAxisSpacing: 4.0,
          mainAxisSpacing: 4.0,
        ),
        itemCount: 20,
        itemBuilder: (context, index) {
          return Container(
            color: Colors.primaries[index % Colors.primaries.length],
            child: Center(
              child: Text(
                'Item $index',
                style: TextStyle(color: Colors.white),
              ),
            ),
          );
        },
      ),
    );
  }
}
*/

请注意,上面的 GridLayoutBuilder 是一个假设的 Widget,因为 flutter_grid_layout 并非 Flutter 官方或广泛认知的包。如果实际找不到 flutter_grid_layout,你可以使用 Flutter 自带的 GridView.builder,它提供了非常灵活和强大的网格布局功能,上面的注释部分展示了如何使用 GridView.builder

如果你确实有一个名为 flutter_grid_layout 的包,并且它有特定的 API,请参考该包的文档来调整上面的代码。如果包名有误,推荐使用 Flutter 官方的 GridView 来实现网格布局。

回到顶部