Flutter 系列 - 拖动元素本教程,我们将从零开始,实现一个拖动元素的功能,并限制元素拖动活动的区间。 开始项目
本教程,我们将从零开始,实现一个拖动元素的功能,并限制元素拖动活动的区间。
开始项目
$ flutter create drag_demo
$ cd drag_demo
$ flutter run
启动项目之后,我们选择在 macOS
上运行桌面端应用,初始效果如下👇
实现拖动
我们将会修改 main.dart
文件,实现拖动的效果。
我们先来实现基本的 UI
👇
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
left: MediaQuery.of(context).size.width / 2 - 50.0,
top: MediaQuery.of(context).size.height / 2 - 50.0,
child: Container(
width: 100.0,
height: 100.0,
color: Colors.red,
),
),
],
),
);
}
}
这里很简单,我们在窗口上新建一个简单的 Container
的挂件,将其设定在屏幕的中央。MediaQuery.of(context).size.width
即是屏幕的宽度。
认识 Draggable
我们将会使用 Draggable,官网的实现效果如下👇
嗯,效果不是我们需要的。那么,我们先来看看 Draggable
这个挂件能干啥:
const Draggable({
super.key,
required this.child, // 拖动的子挂件,必须
required this.feedback, // 当拖动正在进行时,在指针下方显示的窗口小部件,必须
this.data, // 该挂件带的数据(传递给其他挂件,比如上面案例 data: 10, 每次拖动增加一个 10)
this.axis,
this.childWhenDragging, // 当拖动的时候(在原位置)展示的挂件
this.feedbackOffset,
this.dragAnchorStrategy = childDragAnchorStrategy,
this.affinity,
this.maxSimultaneousDrags,
this.onDragStarted, // 挂件拖动开始
this.onDragUpdate,
this.onDraggableCanceled,
this.onDragEnd, // 关键拖动结束
this.onDragCompleted,
this.ignoringFeedbackSemantics = true,
this.ignoringFeedbackPointer = true,
this.rootOverlay = false,
this.hitTestBehavior = HitTestBehavior.deferToChild,
this.allowedButtonsFilter,
})
上面是 Draggble
挂件的所有属性和方法,我们在重点的那几个上标注了说明,我们也是对这几个用得多,其他的说明,感兴趣的读者可以查看 Draggable class。
下面,我们针对这 feedback
, childWhenDragging
, onDragStarted
和 onDragEnd
来编写一个 Demo
,方便理解,我们在 _MyHomePageState
类上进行改写:
class _MyHomePageState extends State<MyHomePage> {
Offset position = const Offset(0.0, 0.0);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
feedback: Container(
width: 100.0,
height: 100.0,
color: Colors.black,
child: const Center(
child: Text(
'Dragging...',
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
),
),
),
),
childWhenDragging: Container(
width: 100.0,
height: 100.0,
color: Colors.green,
child: const Center(
child: Text('Child When Dragging'),
),
),
onDragStarted: () {
print('drag start');
},
onDragEnd: (dragDetails) {
setState(() {
position =
Offset(dragDetails.offset.dx, dragDetails.offset.dy);
});
},
child: Container(
width: 100.0,
height: 100.0,
color: Colors.red,
),
),
),
],
)
);
}
}
上面,我们设定了一个偏移的位置 position
,并赋予其初始值 Offset(0.0, 0.0)
,然后为 feedback
设置一个 Container
的拖动反馈提示;为 childWhenDragging
设置一个正在拖动的替换 Container
挂件;当我们 onDragStarted
开始拖动的时候需要做什么,这么就简单打印个文案 drag start
;当我们拖动完成的时候,更改该挂件的偏移位置,结合下面的 GIF
图更好理解👇
应用 Draggable
认识完 Draggable
之后,我们实现拖动的需求。我们还是改写 _MyHomePageState
类:
class _MyHomePageState extends State<MyHomePage> {
Offset position = const Offset(0.0, 0.0);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
feedback: _buildDragWidget(),
childWhenDragging: Container(),
onDragEnd: (dragDetails) {
setState(() {
position =
Offset(dragDetails.offset.dx, dragDetails.offset.dy);
});
},
child: _buildDragWidget(),
),
),
],
)
);
}
Widget _buildDragWidget() {
return Container(
width: 100.0,
height: 100.0,
color: Colors.red,
);
}
}
上面 feeback
和 child
我们应该使用同一个 Widget
,所以,我们这里共同调用了 _buildGragWidget
方法。
我们能够很顺畅地拖动挂件了,但是,当我们拖动出去视窗的时候,回不来了,尴尬😅。
下面,我们在方法 onDragEnd
方法上做下限制:
onDragEnd: (dragDetails) {
double _dx = dragDetails.offset.dx;
if(dragDetails.offset.dx < 0.0) {
_dx = 0.0;
}
if(dragDetails.offset.dx >= MediaQuery.of(context).size.width - 100.0) {
_dx = MediaQuery.of(context).size.width - 100.0;
}
double _dy =dragDetails.offset.dy;
if(dragDetails.offset.dy < 0.0) {
_dy = 0.0;
}
if(dragDetails.offset.dy >= MediaQuery.of(context).size.height - 100.0) {
_dy = MediaQuery.of(context).size.height - 100.0;
}
setState(() {
position =
Offset(_dx, _dy);
});
},
我们限制了拖动的挂件只能够在视窗上进行展示👇
我们留个问题:如何使用 Draggable
实现拖动指定的区域,带动整个父 Widget
拖动呢?
/// main.dart - 更改其类 _MyHomePageState
class _MyHomePageState extends State {
Offset position = const Offset(0.0, 0.0);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: Stack(
children: [
Container(
width: 100.0,
height: 100.0,
color: Colors.red,
),
Positioned(
top: 0.0,
right: 0.0,
child: Draggable(
feedback: _buildDragWidget(),
childWhenDragging: Container(),
onDragUpdate: (details) {
double _dx = position.dx + details.delta.dx;
double _dy = position.dy + details.delta.dy;
setState(() {
position = Offset(_dx, _dy);
});
},
child: _buildDragWidget(),
),
)
],
),
)
],
),
);
}
Widget _buildDragWidget() {
return Container(
width: 30.0,
height: 30.0,
color: Colors.blue,
child: const Center(
child: Icon(
Icons.drag_indicator,
color: Colors.white
),
),
);
}
}
【完✅】感谢捧场🌹
转载自:https://juejin.cn/post/7410624782418935862