如何在颤动中创建一个可移动的小部件,使其停留在被拖动到的位置
How to create a movable widget in flutter such that is stays at the position it is dragged to
我如何在 flutter 中创建一个 movable/draggable 小部件,使其保持在被拖动到的位置。我尝试使用可拖动小部件,但被包裹在可拖动 returns 中的小部件回到原来的位置释放拖动后的位置。
您需要使用某种状态管理来管理拖动项的位置。在下面的代码示例中,我使用了 Flutter Hooks useState
.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
const imgData =
'data:image/jpeg;base64,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';
void main() {
runApp(
MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: DragArea(
child: Image.network(imgData),
),
),
),
);
}
class DragArea extends HookWidget {
final Widget child;
const DragArea({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
final position = useState(Offset(100, 100));
return Stack(
children: [
Positioned(
left: position.value.dx,
top: position.value.dy,
child: Draggable(
feedback: child,
childWhenDragging: Opacity(
opacity: .3,
child: child,
),
onDragEnd: (details) => position.value = details.offset,
child: child,
),
)
],
);
}
}
有状态版本
根据 anikait 的要求
class StatefulDragArea extends StatefulWidget {
final Widget child;
const StatefulDragArea({Key key, this.child}) : super(key: key);
@override
_DragAreaStateStateful createState() => _DragAreaStateStateful();
}
class _DragAreaStateStateful extends State<StatefulDragArea> {
Offset position = Offset(100, 100);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
feedback: widget.child,
childWhenDragging: Opacity(
opacity: .3,
child: widget.child,
),
onDragEnd: (details) => setState(() => position = details.offset),
child: widget.child,
),
)
],
);
}
}
除了四处拖动对象之外,您还可以借助 GestureDetector
使其可缩放。我将 GestureDetector
应用于主堆栈,这样您就可以在屏幕上的任何位置捏合缩放 in/out。它使您更容易看到自己在做什么。
HookWidget 版本
class DragArea extends HookWidget {
final Widget child;
const DragArea({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
final position = useState(Offset(100, 100));
final prevScale = useState(1.0);
final scale = useState(1.0);
return GestureDetector(
onScaleUpdate: (details) => scale.value = prevScale.value * details.scale,
onScaleEnd: (_) => prevScale.value = scale.value,
child: Stack(
children: [
Positioned.fill(
child: Container(color: Colors.amber.withOpacity(.4))),
Positioned(
left: position.value.dx,
top: position.value.dy,
child: Draggable(
maxSimultaneousDrags: 1,
feedback: Transform.scale(
scale: scale.value,
child: child,
),
childWhenDragging: Opacity(
opacity: .3,
child: Transform.scale(
scale: scale.value,
child: child,
),
),
onDragEnd: (details) => position.value = details.offset,
child: Transform.scale(
scale: scale.value,
child: child,
),
),
)
],
),
);
}
}
StatefulWidget 版本
class StatefulDragArea extends StatefulWidget {
final Widget child;
const StatefulDragArea({Key key, this.child}) : super(key: key);
@override
_DragAreaStateStateful createState() => _DragAreaStateStateful();
}
class _DragAreaStateStateful extends State<StatefulDragArea> {
Offset position = Offset(100, 100);
double prevScale = 1;
double scale = 1;
void updateScale(double zoom) => setState(() => scale = prevScale * zoom);
void commitScale() => setState(() => prevScale = scale);
void updatePosition(Offset newPosition) =>
setState(() => position = newPosition);
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleUpdate: (details) => updateScale(details.scale),
onScaleEnd: (_) => commitScale(),
child: Stack(
children: [
Positioned.fill(
child: Container(color: Colors.amber.withOpacity(.4))),
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
maxSimultaneousDrags: 1,
feedback: widget.child,
childWhenDragging: Opacity(
opacity: .3,
child: widget.child,
),
onDragEnd: (details) => updatePosition(details.offset),
child: Transform.scale(
scale: scale,
child: widget.child,
),
),
),
],
),
);
}
}
我如何在 flutter 中创建一个 movable/draggable 小部件,使其保持在被拖动到的位置。我尝试使用可拖动小部件,但被包裹在可拖动 returns 中的小部件回到原来的位置释放拖动后的位置。
您需要使用某种状态管理来管理拖动项的位置。在下面的代码示例中,我使用了 Flutter Hooks useState
.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
const imgData =
'data:image/jpeg;base64,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';
void main() {
runApp(
MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: DragArea(
child: Image.network(imgData),
),
),
),
);
}
class DragArea extends HookWidget {
final Widget child;
const DragArea({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
final position = useState(Offset(100, 100));
return Stack(
children: [
Positioned(
left: position.value.dx,
top: position.value.dy,
child: Draggable(
feedback: child,
childWhenDragging: Opacity(
opacity: .3,
child: child,
),
onDragEnd: (details) => position.value = details.offset,
child: child,
),
)
],
);
}
}
有状态版本
根据 anikait 的要求
class StatefulDragArea extends StatefulWidget {
final Widget child;
const StatefulDragArea({Key key, this.child}) : super(key: key);
@override
_DragAreaStateStateful createState() => _DragAreaStateStateful();
}
class _DragAreaStateStateful extends State<StatefulDragArea> {
Offset position = Offset(100, 100);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
feedback: widget.child,
childWhenDragging: Opacity(
opacity: .3,
child: widget.child,
),
onDragEnd: (details) => setState(() => position = details.offset),
child: widget.child,
),
)
],
);
}
}
除了四处拖动对象之外,您还可以借助 GestureDetector
使其可缩放。我将 GestureDetector
应用于主堆栈,这样您就可以在屏幕上的任何位置捏合缩放 in/out。它使您更容易看到自己在做什么。
HookWidget 版本
class DragArea extends HookWidget {
final Widget child;
const DragArea({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
final position = useState(Offset(100, 100));
final prevScale = useState(1.0);
final scale = useState(1.0);
return GestureDetector(
onScaleUpdate: (details) => scale.value = prevScale.value * details.scale,
onScaleEnd: (_) => prevScale.value = scale.value,
child: Stack(
children: [
Positioned.fill(
child: Container(color: Colors.amber.withOpacity(.4))),
Positioned(
left: position.value.dx,
top: position.value.dy,
child: Draggable(
maxSimultaneousDrags: 1,
feedback: Transform.scale(
scale: scale.value,
child: child,
),
childWhenDragging: Opacity(
opacity: .3,
child: Transform.scale(
scale: scale.value,
child: child,
),
),
onDragEnd: (details) => position.value = details.offset,
child: Transform.scale(
scale: scale.value,
child: child,
),
),
)
],
),
);
}
}
StatefulWidget 版本
class StatefulDragArea extends StatefulWidget {
final Widget child;
const StatefulDragArea({Key key, this.child}) : super(key: key);
@override
_DragAreaStateStateful createState() => _DragAreaStateStateful();
}
class _DragAreaStateStateful extends State<StatefulDragArea> {
Offset position = Offset(100, 100);
double prevScale = 1;
double scale = 1;
void updateScale(double zoom) => setState(() => scale = prevScale * zoom);
void commitScale() => setState(() => prevScale = scale);
void updatePosition(Offset newPosition) =>
setState(() => position = newPosition);
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleUpdate: (details) => updateScale(details.scale),
onScaleEnd: (_) => commitScale(),
child: Stack(
children: [
Positioned.fill(
child: Container(color: Colors.amber.withOpacity(.4))),
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
maxSimultaneousDrags: 1,
feedback: widget.child,
childWhenDragging: Opacity(
opacity: .3,
child: widget.child,
),
onDragEnd: (details) => updatePosition(details.offset),
child: Transform.scale(
scale: scale,
child: widget.child,
),
),
),
],
),
);
}
}