Flutter拖拽容器插件draggable_container的使用
Flutter拖拽容器插件draggable_container的使用
可拖动子部件,可删除子部件,可以固定子部件位置
每个子部件都可以拖动、删除和固定位置。
截图 / Screenshots
模式 / Mode
正常模式 / Normal Mode:
- 不拦截子部件的手势事件。
- 不能拖动和删除子部件。
编辑模式 / Edit mode:
- 长按子部件进入编辑模式。
- 进入编辑模式后,不再需要长按来拖动子部件,直接拖动就可以了。
- 在可删除子部件上显示删除按钮。
- 拦截所有子部件的手势事件。
- 可以拖动和删除子部件。
DraggableContainer的构造函数参数
required List<T extends DraggableItem> items
: 必须传入子部件列表。required Widget? NullableItemBuilder<T extends DraggableItem>(BuildContext context, T? item) itemBuilder
: 子项widget的构建器。required SliverGridDelegate gridDelegate
: 布局依赖于gridDelegate。bool? shrinkWrap
: 紧缩水平宽度大小,方便水平居中,默认为false。Widget? NullableItemBuilder<T extends DraggableItem>(BuildContext context, T? item) deleteButtonBuilder
: 子项删除按钮的构建器。Widget? NullableItemBuilder<T extends DraggableItem>(BuildContext context, T? item) slotBuilder
: 槽位组件的构建器。BoxDecoration? draggingDecoration, default is a shadow style.
: 当拖动子项时,包裹在子项外部的样式,默认为阴影效果。Duration? animationDuration, default 200ms.
: 子项widget位移的动画时间。bool? tapOutSideExitEditMode, default true.
: 当点击了DraggableContainer外部后,退出编辑模式。onChanged(List<T extends DraggableItem> items)
: 当子项目改变时触发(拖动过后,删除后)。onEditModeChanged(bool mode)
: mode为true则进入了编辑模式,为false则退出了编辑模式.Future<bool> beforeRemove(T? item, int slotIndex)
: 删除item的确认事件,返回true删除,返回false不删除。Future<bool> beforeDrop({T? fromItem, int fromSlotIndex, T? toItem, int toSlotIndex})
: 将一个item从A点移到B点后的确认事件,返回true为允许放下,返回false不允许放下,会覆盖toItem.fixed属性。
DraggableContainerState的方法
getter / setter bool editMode
: 读取或设置编辑模式。List<T extends DraggableItem> items
: 项目列表。Future<void> addSlot(<T extends DraggableItem>? item)
: 添加一个新的槽。Future<T extends DraggableItem> removeSlot(int index)
: 删除一个槽位,返回item。removeItem(<T extends DraggableItem> item)
: 删除item。removeItemAt(int index)
: 删除item。replaceItem(int index, <T extends DraggableItem>? item)
: 替换item。
完整示例代码
import 'package:draggable_container/draggable_container.dart';
import 'package:flutter/material.dart';
import 'data.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'DraggableContainer In ListView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyItem extends DraggableItem {
final Color color;
final int index;
bool deletable;
bool fixed;
MyItem({
required this.index,
this.deletable = true,
this.fixed = false,
Color color,
}) : color = color ?? randomColor();
[@override](/user/override)
String toString() {
return '<MyItem> {index:$index, fixed:$fixed, deletable:$deletable}';
}
}
class AddItem extends DraggableItem {
[@override](/user/override)
bool get deletable => false;
[@override](/user/override)
bool get fixed => true;
[@override](/user/override)
String toString() {
return '<AddItem> {fixed:$fixed, deletable:$deletable}';
}
}
class MyHomePage extends StatefulWidget {
[@override](/user/override)
_MyHomePage createState() => _MyHomePage();
}
class _MyHomePage extends State<MyHomePage> {
final data = <DraggableItem>[
MyItem(index: 0),
MyItem(index: 1),
MyItem(index: 2),
MyItem(index: 3),
AddItem(),
];
final key = GlobalKey<DraggableContainerState<DraggableItem>>();
String items = '';
String delegate = '';
String settings = '';
updateText() {
if (key.currentState != null) {
Map<String, dynamic> text = {};
var widget = key.currentWidget as DraggableContainer;
var _delegate = widget.gridDelegate;
if (_delegate is SliverGridDelegateWithFixedCrossAxisCount) {
text['SliverGridDelegateWithFixedCrossAxisCount'] = '';
text['crossAxisCount'] = _delegate.crossAxisCount;
text['crossAxisSpacing'] = _delegate.crossAxisSpacing;
text['mainAxisSpacing'] = _delegate.mainAxisSpacing;
} else if (_delegate is SliverGridDelegateWithMaxCrossAxisExtent) {
text['SliverGridDelegateWithMaxCrossAxisExtent'] = '';
text['maxCrossAxisExtent'] = _delegate.maxCrossAxisExtent;
text['crossAxisSpacing'] = _delegate.crossAxisSpacing;
text['mainAxisSpacing'] = _delegate.mainAxisSpacing;
}
delegate = mapToString(text);
text.clear();
text['tapOutSideExitEditMode'] = key.currentState.tapOutSideExitEditMode;
text['onChange listener'] = widget.onChanged == null ? 'unset' : 'set';
text['beforeRemove listener'] =
widget.beforeRemove == null ? 'unset' : 'set';
text['beforeDrop listener'] = widget.beforeDrop == null ? 'unset' : 'set';
settings = mapToString(text);
items = key.currentState?.items?.join('\n');
}
}
Future<bool> beforeRemove(item, int slotIndex) async {
item = item as MyItem;
final res = await showDialog<bool>(
context: context,
builder: (_) =>
AlertDialog(
title: Text('Remove item ${item.index}?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('No')),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: Text('Yes')),
],
));
if (res == true) {
key.currentState.removeSlot(slotIndex);
}
return false;
}
String mapToString(Map map) {
return map.keys.map((key) => '$key: ${map[key]}').join('\n');
}
[@override](/user/override)
Widget build(BuildContext context) {
updateText();
return Scaffold(
appBar: AppBar(
title: Text('DraggableContainer In ListView'),
),
body: ListView(
children: [
ListTile(
title: Text('DraggableContainer settings'),
subtitle: Text(settings),
),
Divider(height: 1),
ListTile(
title: Text('Current SliverGridDelegate'),
subtitle: Text(delegate),
),
Divider(height: 1),
Align(
alignment: Alignment.center,
child: DraggableContainer<DraggableItem>(
key: key,
items: data,
shrinkWrap: true,
beforeRemove: beforeRemove,
draggingDecoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 5,
offset: Offset(0, 5),
),
]),
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 3,
// crossAxisSpacing: 10,
// mainAxisSpacing: 10,
// ),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
padding: EdgeInsets.all(10),
onChanged: (List<DraggableItem> items) {
final addItem = items.firstWhere(
(item) => item is AddItem,
orElse: () => null,
);
// print('has AddItem ${addItem != null}');
if (items.length < 9 && addItem == null)
key.currentState.addSlot(AddItem());
setState(() {});
},
beforeDrop: ({fromItem, fromSlotIndex, toItem, toSlotIndex}) {
// print('beforeDrop from $fromSlotIndex to $toSlotIndex');
/// will override the toItem.fixed property
return Future.value(true);
},
itemBuilder: (_, DraggableItem item) {
if (item is AddItem) {
return ElevatedButton(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Add',
style: TextStyle(color: Colors.white),
),
Icon(
Icons.add,
color: Colors.white,
),
],
),
onPressed: () {
if (key.currentState.slots.length < 9) {
key.currentState.insertSlot(
0,
MyItem(
index: key.currentState.slots.length,
));
} else {
print('replace 8');
key.currentState.replaceItem(8, MyItem(index: 99));
}
},
);
} else if (item is MyItem) {
return Material(
elevation: 0,
borderOnForeground: false,
child: Container(
color: item.color,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.index.toString(),
style: TextStyle(
fontSize: 22,
color: Colors.white,
shadows: [
BoxShadow(color: Colors.black, blurRadius: 5),
],
),
),
SizedBox(height: 5),
ElevatedButton.icon(
icon: Icon(item.fixed
? Icons.lock_outline
: Icons.lock_open),
label: Text(item.fixed ? 'Unlock' : 'Lock'),
onPressed: () {
item.fixed = !item.fixed;
setState(() {});
},
),
],
),
),
);
}
return null;
},
),
),
Divider(height: 1),
ListTile(
title: Text('Current Items'),
subtitle: Text(items),
),
],
),
);
}
}
更多关于Flutter拖拽容器插件draggable_container的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复
更多关于Flutter拖拽容器插件draggable_container的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用draggable_container
插件来实现拖拽功能的代码案例。draggable_container
插件允许你创建一个可以拖拽的容器。
首先,确保你的pubspec.yaml
文件中已经添加了draggable_container
依赖:
dependencies:
flutter:
sdk: flutter
draggable_container: ^x.y.z # 替换为最新的版本号
然后,运行flutter pub get
来安装依赖。
以下是一个简单的示例代码,展示如何使用draggable_container
插件:
import 'package:flutter/material.dart';
import 'package:draggable_container/draggable_container.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Container Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DraggableContainerDemo(),
);
}
}
class DraggableContainerDemo extends StatefulWidget {
@override
_DraggableContainerDemoState createState() => _DraggableContainerDemoState();
}
class _DraggableContainerDemoState extends State<DraggableContainerDemo> {
Offset? _initialOffset;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable Container Demo'),
),
body: Center(
child: DraggableContainer(
onDragStarted: (details) {
// 当拖拽开始时记录初始位置
_initialOffset = details.globalPosition;
},
onDragEnded: (details) {
// 当拖拽结束时,可以在这里处理逻辑,比如更新状态
print('Drag ended at: ${details.globalPosition}');
},
child: Container(
width: 200,
height: 200,
color: Colors.amber,
child: Center(
child: Text(
'Drag me!',
style: TextStyle(fontSize: 24, color: Colors.black),
),
),
),
// 可选参数,设置拖拽的边界
constraints: BoxConstraints(
minWidth: 100,
minHeight: 100,
maxWidth: 300,
maxHeight: 300,
),
// 可选参数,设置拖拽的反馈效果
feedback: Container(
width: 210,
height: 210,
color: Colors.amber.withOpacity(0.8),
child: Center(
child: Text(
'Dragging...',
style: TextStyle(fontSize: 24, color: Colors.black),
),
),
),
// 可选参数,设置拖拽时的子widget的阴影
childDecoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3), // changes position of shadow
),
],
),
),
),
);
}
}
在这个示例中:
DraggableContainer
是主要的拖拽容器。onDragStarted
回调在拖拽开始时触发,可以用来记录初始位置。onDragEnded
回调在拖拽结束时触发,可以用来处理逻辑。constraints
参数用于设置拖拽容器的边界。feedback
参数用于定义拖拽时的反馈效果,比如放大或改变颜色。childDecoration
参数用于设置拖拽时子widget的装饰,比如阴影效果。
这个代码示例展示了如何使用draggable_container
插件来实现基本的拖拽功能,你可以根据需求进一步自定义和扩展。