Flutter堆叠布局插件stack_board的使用
Flutter堆叠布局插件stack_board的使用
stack_board
是一个用于Flutter的自定义堆叠布局插件,允许用户在屏幕上自由拖动、缩放和旋转各种类型的元素。本文将详细介绍如何使用 stack_board
插件,并提供一个完整的示例代码。
1. 使用 StackBoardController
StackBoardController
是 stack_board
的核心控制器,用于管理堆叠板中的所有元素。你可以通过它添加、删除、更新元素,并控制它们的行为。
import 'package:stack_board/stack_board.dart';
// 创建 StackBoardController 实例
final _boardController = StackBoardController();
// 在 StackBoard 中使用控制器
StackBoard(
controller: _boardController,
// 添加背景
background: const ColoredBox(color: Colors.grey),
),
2. 添加自适应文本
你可以通过 AdaptiveText
添加可编辑的文本元素。点击文本后,用户可以编辑其内容。
// 添加自适应文本
_boardController.add(
const AdaptiveText(
'Flutter Candies',
tapToEdit: true,
style: TextStyle(fontWeight: FontWeight.bold),
),
);
3. 添加自适应图片
你还可以通过 Image.network
添加网络图片,并将其作为堆叠板中的一个元素。
// 添加自适应图片
_boardController.add(
StackBoardItem(
child: Image.network('https://avatars.githubusercontent.com/u/47586449?s=200&v=4'),
),
);
4. 添加画板
StackDrawing
允许用户在屏幕上绘制图形。你可以设置画板的样式,例如边框颜色、图标颜色等。
// 添加画板
_boardController.add(
const StackDrawing(
caseStyle: CaseStyle(
borderColor: Colors.grey,
iconColor: Colors.white,
boxAspectRatio: 1,
),
),
);
5. 添加自定义 item
你可以通过继承 StackItemContent
和 StackItem
来创建自定义的堆叠板元素。以下是一个简单的颜色块示例。
5.1 继承自 StackItemContent
和 StackItem
class ColorContent extends StackItemContent {
ColorContent({required this.color});
final Color color;
[@override](/user/override)
Map<String, dynamic> toJson() {
return <String, dynamic>{
'color': color.value,
};
}
}
class ColorStackItem extends StackItem<ColorContent> {
ColorStackItem({
required Size size,
String? id,
Offset? offset,
double? angle,
StackItemStatus? status,
ColorContent? content,
}) : super(
id: id,
size: size,
offset: offset,
angle: angle,
status: status,
content: content,
);
[@override](/user/override)
ColorStackItem copyWith({
Size? size,
Offset? offset,
double? angle,
StackItemStatus? status,
ColorContent? content,
}) {
return ColorStackItem(
id: id, // 必须!
size: size ?? this.size,
offset: offset ?? this.offset,
angle: angle ?? this.angle,
status: status ?? this.status,
content: content ?? this.content,
);
}
}
5.2 使用控制器添加自定义 item
import 'dart:math' as math;
// 添加自定义 item
void _addCustomItem() {
final Color color = Colors.primaries[math.Random().nextInt(Colors.primaries.length)];
_boardController.addItem(
ColorStackItem(
size: const Size.square(100),
content: ColorContent(color: color),
),
);
}
5.3 使用 customBuilder
构建自定义 item
如果你使用了自定义的 StackItem
,你需要通过 customBuilder
来构建这些自定义元素。
StackBoard(
controller: _boardController,
customBuilder: (StackItem<StackItemContent> item) {
if (item is ColorStackItem) {
return Container(
width: item.size.width,
height: item.size.height,
color: item.content?.color,
);
}
return const SizedBox.shink();
},
)
6. 完整示例代码
以下是一个完整的示例代码,展示了如何使用 stack_board
插件来创建一个包含文本、图片、画板和自定义元素的堆叠板。
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:stack_board/flutter_stack_board.dart';
import 'package:stack_board/stack_board_item.dart';
import 'package:stack_board/stack_case.dart';
import 'package:stack_board/stack_items.dart';
class ColorContent extends StackItemContent {
ColorContent({required this.color});
final Color color;
[@override](/user/override)
Map<String, dynamic> toJson() {
return <String, dynamic>{
'color': color.value,
};
}
}
class ColorStackItem extends StackItem<ColorContent> {
ColorStackItem({
required Size size,
String? id,
Offset? offset,
double? angle,
StackItemStatus? status,
ColorContent? content,
}) : super(
id: id,
size: size,
offset: offset,
angle: angle,
status: status,
content: content,
lockZOrder: true,
);
[@override](/user/override)
ColorStackItem copyWith({
Size? size,
Offset? offset,
double? angle,
StackItemStatus? status,
bool? lockZOrder,
ColorContent? content,
}) {
return ColorStackItem(
id: id, // 必须!
size: size ?? this.size,
offset: offset ?? this.offset,
angle: angle ?? this.angle,
status: status ?? this.status,
content: content ?? this.content,
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return const MaterialApp(home: HomePage());
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
[@override](/user/override)
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late StackBoardController _boardController;
[@override](/user/override)
void initState() {
super.initState();
_boardController = StackBoardController();
}
[@override](/user/override)
void dispose() {
_boardController.dispose();
super.dispose();
}
/// 删除拦截
Future<void> _onDel(StackItem<StackItemContent> item) async {
final bool? r = await showDialog<bool>(
context: context,
builder: (_) {
return Center(
child: SizedBox(
width: 400,
child: Material(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(top: 10, bottom: 60),
child: Text('确认删除?'),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
onPressed: () => Navigator.pop(context, true),
icon: const Icon(Icons.check)),
IconButton(
onPressed: () => Navigator.pop(context, false),
icon: const Icon(Icons.clear)),
],
),
],
),
),
),
),
);
},
);
if (r == true) {
_boardController.removeById(item.id);
}
}
/// 添加文本元素
void _addTextItem() {
_boardController.addItem(
StackTextItem(
size: const Size(200, 100),
content: TextItemContent(data: '哈哈哈哈哈'),
),
);
}
/// 添加图片元素
void _addImageItem() {
_boardController.addItem(
StackImageItem(
size: const Size(300, 85),
content: ImageItemContent(
url:
'https://files.flutter-io.cn/images/branding/flutterlogo/flutter-cn-logo.png',
),
),
);
}
/// 添加画板元素
void _addDrawItem() {
_boardController.addItem(StackDrawItem(size: const Size.square(300)));
}
/// 添加自定义元素
void _addCustomItem() {
final Color color =
Colors.primaries[Random().nextInt(Colors.primaries.length)];
_boardController.addItem(
ColorStackItem(
size: const Size.square(100),
content: ColorContent(color: color),
),
);
}
/// 从 JSON 生成元素
Future<void> _generateFromJson() async {
final String jsonString =
(await Clipboard.getData(Clipboard.kTextPlain))?.text ?? '';
if (jsonString.isEmpty) {
_showAlertDialog(
title: '剪贴板为空',
content: '请先将 JSON 字符串复制到剪贴板');
return;
}
try {
final List<dynamic> items = jsonDecode(jsonString) as List<dynamic>;
for (final dynamic item in items) {
if (item['type'] == 'StackTextItem') {
_boardController.addItem(
StackTextItem.fromJson(item),
);
} else if (item['type'] == 'StackImageItem') {
_boardController.addItem(
StackImageItem.fromJson(item),
);
} else if (item['type'] == 'StackDrawItem') {
_boardController.addItem(
StackDrawItem.fromJson(item),
);
}
}
} catch (e) {
_showAlertDialog(title: '错误', content: e.toString());
}
}
/// 获取 JSON
Future<void> _getJson() async {
final String json = jsonEncode(_boardController.getAllData());
Clipboard.setData(ClipboardData(text: json));
showDialog<void>(
context: context,
builder: (_) {
return Center(
child: SizedBox(
width: 400,
child: Material(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(top: 10, bottom: 60),
child: Text('JSON'),
),
Container(
constraints: const BoxConstraints(maxHeight: 500),
child: SingleChildScrollView(
child: Text(json),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.check)),
],
),
],
),
),
),
),
);
},
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('Stack Board Demo'),
elevation: 0,
),
body: StackBoard(
onDel: _onDel,
controller: _boardController,
caseStyle: const CaseStyle(
buttonBorderColor: Colors.grey,
buttonIconColor: Colors.grey,
),
// 背景
background: ColoredBox(color: Colors.grey[100]!),
customBuilder: (StackItem<StackItemContent> item) {
if (item is StackTextItem) {
return StackTextCase(item: item);
} else if (item is StackDrawItem) {
return StackDrawCase(item: item);
} else if (item is StackImageItem) {
return StackImageCase(item: item);
} else if (item is ColorStackItem) {
return Container(
width: item.size.width,
height: item.size.height,
color: item.content?.color,
);
}
return const SizedBox.shrink();
},
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
const SizedBox(width: 25),
FloatingActionButton(
onPressed: _addTextItem,
child: const Icon(Icons.border_color)),
const SizedBox(width: 5),
FloatingActionButton(
onPressed: _addImageItem, child: const Icon(Icons.image)),
const SizedBox(width: 5),
FloatingActionButton(
onPressed: _addDrawItem,
child: const Icon(Icons.color_lens)),
const SizedBox(width: 5),
FloatingActionButton(
onPressed: _addCustomItem,
child: const Icon(Icons.add_box)),
],
),
),
),
Row(
children: <Widget>[
FloatingActionButton(
onPressed: () => _boardController.clear(),
child: const Icon(Icons.delete),
),
const SizedBox(width: 5),
FloatingActionButton(
onPressed: _getJson,
child: const Icon(Icons.file_download),
),
const SizedBox(width: 5),
FloatingActionButton(
onPressed: _generateFromJson,
child: const Icon(Icons.file_upload),
),
],
),
],
),
);
}
void _showAlertDialog({required String title, required String content}) {
showDialog<void>(
context: context,
builder: (_) {
return AlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('确定'),
),
],
);
},
);
}
}
更多关于Flutter堆叠布局插件stack_board的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter堆叠布局插件stack_board的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter中使用stack_board
(假设你指的是Stack
布局,因为Flutter标准库中并没有直接名为stack_board
的插件)的示例代码。Stack
是一个强大的布局小部件,它允许你在Z轴上堆叠子小部件。
示例代码
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Stack Layout Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: StackExample(),
);
}
}
class StackExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stack Layout Example'),
),
body: Center(
child: Stack(
alignment: Alignment.center, // 子部件将居中对齐
children: <Widget>[
// 底层:一个背景图片
Image.network(
'https://via.placeholder.com/300',
fit: BoxFit.cover,
),
// 中间层:一个半透明的覆盖层
Container(
color: Colors.black.withOpacity(0.5),
),
// 上层:文本信息
Text(
'Hello, Flutter!',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
// 另一个上层:一个圆形的按钮
Positioned(
bottom: 20,
right: 20,
child: FloatingActionButton(
onPressed: () {
// 按钮点击事件
print('Button pressed!');
},
tooltip: 'Press me',
child: Icon(Icons.add),
),
),
],
),
),
);
}
}
解释
-
Stack
小部件:Stack
接受一个children
列表,这些子小部件将按照列表中的顺序堆叠。 -
alignment
属性:这个属性决定了如何对齐非定位(non-positioned)的子小部件。在这个例子中,所有子小部件都使用了默认的定位(即没有使用Positioned
包装),但由于我们设置了alignment: Alignment.center
,它们将居中对齐。然而,使用Positioned
的小部件(如按钮)将不受此属性影响。 -
Image.network
:从网络加载并显示一个图片。 -
Container
:创建一个具有半透明黑色的覆盖层。 -
Text
:在覆盖层上显示白色文本。 -
Positioned
:用于在Stack
内精确定位子小部件。在这个例子中,Positioned
用于将一个浮动动作按钮放置在堆叠布局的右下角。
注意
Stack
的子小部件默认会按照添加的顺序堆叠,即后添加的会覆盖先添加的。- 如果需要对某个子小部件进行精确定位,可以使用
Positioned
小部件包装它,并指定top
、bottom
、left
和right
属性。
这个示例展示了如何使用Stack
布局来实现一个包含背景图片、覆盖层、文本和按钮的复杂布局。希望这对你有所帮助!