Flutter墙面布局插件flutter_wall_layout的使用
Flutter墙面布局插件flutter_wall_layout的使用
本库提供了一个独特的组件WallLayout,可以将不同形状的子部件布局为垂直或水平列表。
该库主要有两个类:WallLayout 和 Stone。
- 墙由一组石头组成,这些石头按照特定的方式排列。
- 一块石头是一个定义其宽度和高度的组件。
墙布局组件
该组件受到ListView的启发,你可以选择滚动方向和方式。此外,你还可以定义墙有多少行或列,以创建响应式设计。
WallLayout(
stones: _buildStonesList(),
layersCount: 3,
scrollDirection: Axis.vertical,
reverse: false,
);
墙布局组件属性
参数名称 | 描述 | 默认值 |
---|---|---|
stones | 石头列表,表示墙布局的子部件。 | - |
layersCount | 定义墙的层数。必须大于等于2。当方向为Axis.vertical时,它定义墙有多少列。当方向为Axis.horizontal时,它定义墙有多少行。 | - |
wallBuilder | 定义如何构建墙:每个石头在墙内的位置。允许使用比提供的更少的石头,并且可以创建新的石头(详情见示例)。 | WallBuilder.standard() |
stonePadding | 石头之间的间距。 | 16.0 |
scrollController | 同ListView.scrollController,控制滚动视图滚动到的位置。 | - |
physics | 同ListView.physics,定义滚动视图如何响应用户输入。 | - |
restorationId | 同ListView.restorationId,用于保存和恢复滚动视图的滚动偏移量。 | - |
dragStartBehavior | 同ListView.dragStartBehavior,确定拖动开始行为的处理方式。 | DragStartBehavior.start |
clipBehavior | 同ListView.clipBehavior,定义如何裁剪小部件的内容。 | Clip.hardEdge |
primary | 同ListView.primary,是否此为与父PrimaryScrollController关联的主要滚动视图。 | false |
scrollDirection | 同ListView.scrollDirection,定义滚动视图沿哪个轴滚动。 | Axis.vertical |
reverse | 同ListView.reverse属性,定义滚动视图是否从阅读方向反向滚动。 | false |
石头组件
一个石头组件是一个定义石头大小(宽度和高度)的小部件代理。通过其id属性优化渲染对象。
Stone(
id: 7,
width: 2,
height: 2,
child: Container(
color: Colors.red,
child: Center(child: Text("2x2")),
),
);
石头组件属性
参数名称 | 描述 | 默认值 |
---|---|---|
width | 石头的宽度(相对于墙的分割数)。必须大于等于1。 | - |
height | 石头的高度(相对于墙的分割数)。必须大于等于1。 | - |
id | 代理布局标识符:必须唯一。 | - |
child | 渲染的小部件,其尺寸将受石头宽度和高度的约束。 | - |
完整示例代码
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_wall_layout/flutter_wall_layout.dart';
import 'package:flutter_wall_layout/wall_builder.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wall Layout Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
backgroundColor: Color(0xFFF5F5F5),
),
home: MyHomePage(title: 'Wall Layout Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
[@override](/user/override)
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late final AnimationController _controller;
late bool _reversed;
late Axis _direction;
late int _nbLayers;
late bool _wrapedOptions;
bool _random = false;
late List<Stone> _stones;
[@override](/user/override)
void initState() {
super.initState();
_controller =
AnimationController(duration: Duration(milliseconds: 500), vsync: this);
_reversed = false;
_direction = Axis.vertical;
_nbLayers = 3;
_controller.forward(from: 0);
_wrapedOptions = true;
_stones = _buildStonesList();
}
[@override](/user/override)
Widget build(BuildContext context) {
final backgroundColor = Theme.of(context).backgroundColor;
return Scaffold(
backgroundColor: backgroundColor,
appBar: AppBar(
title: Text(this.widget.title),
actions: [
IconButton(
onPressed: _onRandomClicked,
icon: Icon(
_random ? Icons.sync : Icons.sync_disabled,
),
tooltip: "Random stone sizes + custom WallHandler",
)
],
),
body: buildWallLayout(),
floatingActionButton: _buildOptions(context),
);
}
void _onRandomClicked() {
setState(() {
_random = !_random;
if (_random) {
_stones = _buildRandomStonesList(_nbLayers);
} else {
_stones = _buildStonesList();
}
});
}
Widget _buildOptions(BuildContext context) {
return AnimatedSize(
duration: Duration(milliseconds: 200),
reverseDuration: Duration(milliseconds: 200),
alignment: Alignment.bottomRight,
vsync: this,
child: Container(
margin: EdgeInsets.only(left: 32),
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 6.0),
],
borderRadius: BorderRadius.circular(30.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (!_wrapedOptions)
Padding(
padding: EdgeInsets.symmetric(vertical: 20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
__buildDivisionsOption(),
__buildDirectionOption(),
__buildReverseOption(),
],
),
),
FloatingActionButton(
elevation: 0.0,
highlightElevation: 0.0,
onPressed: () => setState(() => _wrapedOptions = !_wrapedOptions),
child: Icon(Icons.build),
),
],
),
),
);
}
Widget __buildDivisionsOption() {
return _buildOption(
"Layers",
CupertinoSegmentedControl<int>(
groupValue: _nbLayers,
children: {2: Text("2"), 3: Text("3"), 4: Text("4")},
onValueChanged: (value) => setState(() {
_controller.forward(from: 0.0);
_nbLayers = value;
if (_random) {
_stones = _buildRandomStonesList(_nbLayers);
}
}),
),
);
}
Widget __buildReverseOption() {
return _buildOption(
"Reverse",
CupertinoSegmentedControl<bool>(
groupValue: _reversed,
children: {false: Text("no"), true: Text("yes")},
onValueChanged: (value) => setState(() {
_controller.forward(from: 0.0);
_reversed = value;
}),
),
);
}
Widget __buildDirectionOption() {
return _buildOption(
"Direction",
CupertinoSegmentedControl<Axis>(
groupValue: _direction,
children: {
Axis.vertical: Text("vertical"),
Axis.horizontal: Text("horizontal")
},
onValueChanged: (value) => setState(() {
_controller.forward(from: 0.0);
_direction = value;
}),
),
);
}
Widget _buildOption(String text, Widget child) {
return Padding(
padding: const EdgeInsets.only(top: 4.0, left: 8.0, bottom: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: Text(text),
flex: 1,
),
Expanded(
child: child,
flex: 2,
),
],
),
);
}
Widget buildWallLayout() {
return WallLayout(
wallBuilder: _random
? FillingHolesWallBuildHandler(
context: context,
childBuilder: (_) => DecoratedBox(
decoration: BoxDecoration(
color: Color(0xffececee),
borderRadius: BorderRadius.circular(12)),
),
)
: WallBuilder.standard(),
scrollDirection: _direction,
stones: _stones,
reverse: _reversed,
layersCount: _nbLayers,
);
}
List<Stone> _buildRandomStonesList(int maxLayer) {
Random r = Random();
final next = () => r.nextInt(maxLayer) + 1;
final colors = [
Colors.red,
Colors.greenAccent,
Colors.lightBlue,
Colors.purple,
Colors.yellow,
Colors.cyanAccent,
Colors.orange,
Colors.green,
Colors.pink,
Colors.blueAccent,
Colors.amber,
Colors.teal,
Colors.lightGreenAccent,
Colors.deepOrange,
Colors.deepPurpleAccent,
Colors.lightBlueAccent,
Colors.limeAccent,
];
return colors.map((color) {
int width = next();
int height = next();
return Stone(
id: colors.indexOf(color),
width: width,
height: height,
child: __buildStoneChild(
background: color,
text: "${width}x$height",
surface: (width * height).toDouble(),
),
);
}).toList();
}
List<Stone> _buildStonesList() {
final data = [
{"color": Colors.red, "width": 2, "height": 2},
{"color": Colors.greenAccent, "width": 1, "height": 1},
{"color": Colors.lightBlue, "width": 1, "height": 2},
{"color": Colors.purple, "width": 2, "height": 1},
{"color": Colors.yellow, "width": 1, "height": 1},
{"color": Colors.cyanAccent, "width": 1, "height": 1},
{"color": Colors.orange, "width": 2, "height": 2},
{"color": Colors.green, "width": 1, "height": 1},
{"color": Colors.pink, "width": 2, "height": 1},
{"color": Colors.blueAccent, "width": 1, "height": 1},
{"color": Colors.amber, "width": 1, "height": 2},
{"color": Colors.teal, "width": 2, "height": 1},
{"color": Colors.lightGreenAccent, "width": 1, "height": 1},
{"color": Colors.deepOrange, "width": 1, "height": 1},
{"color": Colors.deepPurpleAccent, "width": 2, "height": 2},
{"color": Colors.lightBlueAccent, "width": 1, "height": 1},
{"color": Colors.limeAccent, "width": 1, "height": 1},
];
return data.map((d) {
int width = d["width"] as int;
int height = d["height"] as int;
return Stone(
id: data.indexOf(d),
width: width,
height: height,
child: __buildStoneChild(
background: d["color"] as Color,
text: "${width}x$height",
surface: (width * height).toDouble(),
),
);
}).toList();
}
Widget __buildStoneChild(
{required Color background,
required String text,
required double surface}) {
return ScaleTransition(
scale: CurveTween(curve: Interval(0.0, min(1.0, 0.25 + surface / 6.0)))
.animate(_controller),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
background,
Color.alphaBlend(background.withOpacity(0.6), Colors.black)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
),
child:
Text(text, style: TextStyle(color: Colors.white, fontSize: 32.0)),
),
);
}
}
// 填充空位用1x1大小的石头
class FillingHolesWallBuildHandler extends WallBuilder {
final WallBuilder proxy = WallBuilder.standard();
final BuildContext context;
final WidgetBuilder childBuilder;
FillingHolesWallBuildHandler(
{required this.childBuilder, required this.context})
: super();
WallBlueprint _buildBlueprint(List<Stone> stones) {
return proxy.build(
mainAxisSeparations: mainAxisSeparations,
reverse: reverse,
direction: direction,
stones: stones);
}
void _findHoles(WallBlueprint blueprint, Function(int x, int y) onHoleFound) {
List<Rect> bounds = blueprint.stonesPosition
.map((key, value) => MapEntry(
key,
Rect.fromLTWH(value.x.toDouble(), value.y.toDouble(),
key.width.toDouble(), key.height.toDouble())))
.values
.toList();
for (int x = 0; x < blueprint.size.width; x++) {
for (int y = 0; y < blueprint.size.height; y++) {
Rect area = Rect.fromLTWH(x.toDouble(), y.toDouble(), 1.0, 1.0);
bounds.firstWhere(
(element) => area.overlaps(element),
orElse: () {
onHoleFound(x, y);
return area;
},
);
bounds.add(area);
}
}
}
[@override](/user/override)
Map<Stone, StoneStartPosition> computeStonePositions(List<Stone> stones) {
final blueprint = _buildBlueprint(stones);
Map<Stone, StoneStartPosition> positions = blueprint.stonesPosition;
int idStart = 10000;
_findHoles(blueprint, (x, y) {
final stone = Stone(
height: 1,
width: 1,
id: idStart++,
child: childBuilder.call(context),
);
positions[stone] = StoneStartPosition(x: x, y: y);
});
return positions;
}
}
更多关于Flutter墙面布局插件flutter_wall_layout的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复