Flutter网格布局插件flutter_grid_layout的使用
Flutter网格布局插件flutter_grid_layout的使用
拥抱网格布局对于创建适应性强且用户友好的数字设计至关重要。网格确保视觉一致性,优先展示内容,并能够无缝适应各种屏幕尺寸,从而提升跨设备和显示屏的整体用户体验。
特性
- 基本实现 CSS Grid。
开始使用
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),
)
],
),
多个具有不同层次的项目
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),
),
],
),
反向多个具有不同层次的项目
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),
),
],
),
完整示例代码
以下是一个完整的示例代码,展示了如何使用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
更多关于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
来实现网格布局。