我们如何在颤动中拖动小部件时实现定位线?
Posted
技术标签:
【中文标题】我们如何在颤动中拖动小部件时实现定位线?【英文标题】:How can we achieve the positioned lines while dragging widget in flutter? 【发布时间】:2021-09-07 17:43:58 【问题描述】:我试图在拖动时获取定位线(紫色一条)。请参阅该附件以了解有关此问题的更多信息。
【问题讨论】:
用 DecoratedBox 或 Container 等装饰围绕您的小部件! 你能举个例子吗? 【参考方案1】:在paint()
方法和canvas.drawPoints()
中使用canvas.drawPoints()
绘制线条 - 您可以更改它以绘制虚线
现在,黑色实线看起来像这样:
class FooResizer extends StatefulWidget
@override
_FooResizerState createState() => _FooResizerState();
class _FooResizerState extends State<FooResizer> with TickerProviderStateMixin, ChangeNotifier
Sizer currentSizer;
double angle = 0.0;
AnimationController ctrl;
final Map<String, Sizer> sizers =
'l': Sizer('l', Alignment.centerLeft, 't': 0.5, 'b': 0.5, 'M': 0.5),
't': Sizer('t', Alignment.topCenter, 'l': 0.5, 'r': 0.5, 'M': 0.5),
'r': Sizer('r', Alignment.centerRight, 't': 0.5, 'b': 0.5, 'R': 1.0, 'M': 0.5),
'b': Sizer('b', Alignment.bottomCenter, 'l': 0.5, 'r': 0.5, 'R': 1.0, 'M': 0.5),
'R': Sizer('R', Alignment.bottomRight, ),
'M': Sizer('M', Alignment.center, ),
;
@override
void initState()
super.initState();
ctrl = AnimationController(vsync: this, duration: Duration(milliseconds: 300), value: 1.0);
@override
Widget build(BuildContext context)
return ClipRect(
child: ColoredBox(
color: Colors.black12,
// CustomMultiChildLayoutPainter:
// https://gist.github.com/pskink/0f82724b41d9ebe89604782fbf62fe03#file-multi_layout_painter-dart-L447
child: CustomMultiChildLayoutPainter(
delegate: _FooResizerDelegate(sizers, this),
children: [
LayoutId(
id: 'body',
child: AnimatedBuilder(
animation: this,
builder: (ctx, child)
return Transform.rotate(
angle: angle,
child: child,
);
,
child: Material(color: Colors.grey[300], elevation: 4, child: FlutterLogo()),
),
),
...sizers.values.map(_sizerBuilder),
],
),
),
);
Widget _sizerBuilder(Sizer sizer)
final colors =
'M': Colors.orange,
'R': Colors.teal,
;
return LayoutId(
id: sizer.id,
child: GestureDetector(
onPanStart: (details) => _panStart(sizer),
onPanUpdate: _panUpdate,
onPanEnd: _panEnd,
child: AnimatedBuilder(
animation: ctrl,
builder: (context, child)
final color = colors[sizer.id] ?? Colors.green;
return Opacity(
opacity: currentSizer == sizer? 1.0 : Curves.ease.transform(ctrl.value),
child: Container(
decoration: ShapeDecoration(
shape: CircleBorder(side: BorderSide(width: lerpDouble(0.0, 2.0, ctrl.value), color: Colors.black38)),
color: currentSizer == sizer? Color.lerp(Colors.deepPurple, color, ctrl.value) : color,
shadows: [BoxShadow.lerp(BoxShadow(spreadRadius: 2, blurRadius: 4, offset: Offset(2, 2)), null, ctrl.value)],
),
),
);
),
),
);
_panStart(Sizer sizer)
currentSizer = sizer;
ctrl.reverse();
_panUpdate(DragUpdateDetails details)
assert(currentSizer != null);
if (currentSizer.id == 'M')
// move
sizers.values.forEach((sizer) => sizer.center += details.delta);
else
if (currentSizer.id == 'R')
// rotate
final localCenter = sizers['M'].center;
final globalCenter = (context.findRenderObject() as RenderBox).localToGlobal(localCenter);
final angle0 = (details.globalPosition - details.delta - globalCenter).direction;
final angle1 = (details.globalPosition - globalCenter).direction;
final deltaAngle = angle1 - angle0;
sizers.values
.where((sizer) => sizer.id != 'M')
.forEach((sizer)
final vector = sizer.center - localCenter;
sizer.center = localCenter + Offset.fromDirection(vector.direction + deltaAngle, vector.distance);
);
angle += deltaAngle;
else
// resize
final adjustedAngle = angle + currentSizer.angleAdjustment;
final rotatedDistance = details.delta.distance * math.cos(details.delta.direction - adjustedAngle);
final vector = Offset.fromDirection(adjustedAngle, rotatedDistance);
currentSizer.center += vector;
currentSizer.dependents.forEach((id, factor) => sizers[id].center += vector * factor);
notifyListeners();
_panEnd(DragEndDetails details)
assert(currentSizer != null);
// currentSizer = null;
ctrl.forward();
class _FooResizerDelegate extends MultiChildLayoutPainterDelegate
static const SIZE = 48.0;
final Map<String, Sizer> sizers;
_FooResizerDelegate(this.sizers, Listenable relayout) : super(relayout: relayout);
@override
void performLayout(Size size)
sizers['M'].center ??= init(size);
for (var sizer in sizers.values)
layoutChild(sizer.id, BoxConstraints.tight(Size(SIZE, SIZE)));
positionChild(sizer.id, sizer.center - Offset(SIZE / 2, SIZE / 2));
final w = (sizers['l'].center - sizers['r'].center).distance;
final h = (sizers['t'].center - sizers['b'].center).distance;
layoutChild('body', BoxConstraints.tight(Size(w, h)));
positionChild('body', sizers['M'].center - Offset(w / 2, h / 2));
Offset init(Size size)
final rect = (Offset.zero & size).deflate(24);
print('init rect: $rect');
for (var sizer in sizers.values)
sizer
..center = sizer.alignment.withinRect(rect)
..angleAdjustment = sizer.alignment.x == 0? math.pi / 2 : 0;
return sizers['M'].center;
@override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
@override
void foregroundPaint(Canvas canvas, Size size)
@override
void paint(Canvas canvas, Size size)
final w = (sizers['r'].center - sizers['l'].center).distance;
final h = (sizers['b'].center - sizers['t'].center).distance;
final rect = Rect.fromCenter(center: sizers['M'].center, width: w, height: h);
final angle = (sizers['r'].center - sizers['l'].center).direction;
final matrix = Matrix4.identity()
..translate(rect.center.dx, rect.center.dy)
..rotateZ(angle)
..translate(-rect.center.dx, -rect.center.dy);
final transformedRect = MatrixUtils.transformRect(matrix, rect);
final points = [
Offset(transformedRect.left, 0), Offset(transformedRect.left, size.height),
Offset(0, transformedRect.top), Offset(size.width, transformedRect.top),
Offset(transformedRect.right, 0), Offset(transformedRect.right, size.height),
Offset(0, transformedRect.bottom), Offset(size.width, transformedRect.bottom),
];
canvas.drawPoints(PointMode.lines, points, Paint());
class Sizer
final String id;
final Alignment alignment;
final Map<String, double> dependents;
Offset center;
double angleAdjustment;
Sizer(this.id, this.alignment, this.dependents);
【讨论】:
Sizer class Sizer final String id;最终对齐对齐;最终的 EdgeInsets 插图;最终偏移掩码; Sizer(this.id, this.alignment, this.insets, this.mask); 两个问题: 1. 当widget变得非常小时如何停止调整大小? 2. 如何在从角落拖动时以相同的比例调整高度和小部件的大小? 检查_panUpdate
方法 - 特别是带有 // resize
注释的 else
分支 - 这是 currentSizer.center += vector;
更改 currentSizer
中心位置的地方
我明白了,但我必须检查什么才能让它在某个点停止调整大小?
你必须钳制vector
,以便整个宽度/高度不会低于一些最小值 - 它只是一个矢量数学 - 我不熟悉 that library 但很可能是这里非常有用以上是关于我们如何在颤动中拖动小部件时实现定位线?的主要内容,如果未能解决你的问题,请参考以下文章