我正在尝试在画布小部件中调整小部件的大小。我们可以改变它们的高度-宽度,基于中心点移动并旋转这个小部件但是在从它们的左上角调整大小时会产生问题。我们可以使用其他小部件来做到这一点,但我想使用画布。所以请帮助我或提出建议来存档。谢谢。
我的代码如下,
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FooResizer(),
);
}
}
class FooResizer extends StatefulWidget {
@override
_FooResizerState createState() => _FooResizerState();
}
class _FooResizerState extends State<FooResizer> {
Sizer currentSizer;
double angle = 0.0;
ValueNotifier<int> relayout = ValueNotifier<int>(0);
// left-top-right-bottom-rotate-move
final Map<String, Sizer> sizers = {
'tl': Sizer('tl', Alignment.topLeft,
{'t': 0.5, 'b': 0.5, 'l': 1.0, 'r': 1.0, 'M': 0.5}),
'l': Sizer(
'l', Alignment.centerLeft, {'t': 0.5, 'tl': 1.0, 'b': 0.5, 'M': 0.5}),
't': Sizer(
't', Alignment.topCenter, {'l': 0.5, 'r': 0.5, 'M': 0.5, 'tl': 1.0}),
'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, {}),
// 'olo': Sizer('olo', Alignment.bottomLeft, {}),
};
@override
Widget build(BuildContext context) {
return ClipRect(
child: ColoredBox(
color: Colors.black12,
child: CustomMultiChildLayoutPainter(
delegate: _FooResizerDelegate(sizers, relayout),
children: [
LayoutId(
id: 'body',
child: AnimatedBuilder(
animation: relayout,
builder: (ctx, child) {
return Transform.rotate(
angle: angle,
child: Material(
color: Colors.grey[300],
elevation: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FlutterLogo(
size: 120,
),
Text('Some data to display'),
],
),
),
);
}),
),
...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: Container(
decoration: ShapeDecoration(
shape:
CircleBorder(side: BorderSide(width: 2, color: Colors.black26)),
color: colors[sizer.id] ?? Colors.green,
),
),
),
);
}
_panStart(Sizer sizer) {
currentSizer = sizer;
}
_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;
var rotatedDistance;
if (currentSizer.id == "tl") {
rotatedDistance = details.delta.distance *
cos(details.delta.direction - adjustedAngle);
} else {
rotatedDistance = details.delta.distance *
cos(details.delta.direction - adjustedAngle);
}
final vector = Offset.fromDirection(adjustedAngle, rotatedDistance);
print(
"direction ${details.delta.direction} vector $vector adjustedAngle $adjustedAngle rotatedDistance $rotatedDistance");
var finalVector;
if (currentSizer.id == "tl") {
finalVector = Offset(vector.dx, vector.dy + (vector.dx));
} else {
finalVector = vector;
}
currentSizer.center += finalVector;
final w = (sizers['r'].center - sizers['l'].center).distance;
final h = (sizers['b'].center - sizers['t'].center).distance;
if (w < 100 || h < 100) {
currentSizer.center -= finalVector;
return;
}
currentSizer.dependents.forEach(
(id, factor) => sizers[id].center += finalVector * factor,
);
}
relayout.value++;
}
_panEnd(DragEndDetails details) {
assert(currentSizer != null);
currentSizer = null;
}
}
class _FooResizerDelegate extends MultiChildLayoutPainterDelegate {
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(48, 48)));
positionChild(sizer.id, sizer.center - Offset(24, 24));
}
final w = (sizers['l'].center - sizers['r'].center).distance;
final h = (sizers['tl'].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 ? 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()..style = PaintingStyle.stroke);
}
}
class Sizer {
final String id;
final Alignment alignment;
final Map<String, double> dependents;
Offset center;
double angleAdjustment;
Sizer(this.id, this.alignment, this.dependents);
}
extension LayoutIdExtension on Iterable<Widget> {
List wrapIds(List ids) {
int i = 0;
return this.map((e) => LayoutId(id: ids[i++], child: e)).toList();
}
}
class CustomMultiChildLayoutPainter extends StatelessWidget {
CustomMultiChildLayoutPainter({
Key key,
@required this.delegate,
this.children = const <Widget>[],
this.layoutIds,
}) : super(key: key);
final MultiChildLayoutPainterDelegate delegate;
final List<Widget> children;
final List layoutIds;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _PainterDelegate(delegate.paint, delegate.repaint),
foregroundPainter:
_PainterDelegate(delegate.foregroundPaint, delegate.repaint),
child: CustomMultiChildLayout(
delegate: delegate,
children: layoutIds != null ? children.wrapIds(layoutIds) : children,
),
);
}
}
abstract class MultiChildLayoutPainterDelegate
extends MultiChildLayoutDelegate {
Listenable repaint;
MultiChildLayoutPainterDelegate({
Listenable relayout,
this.repaint,
}) : super(relayout: relayout);
void paint(Canvas canvas, Size size);
void foregroundPaint(Canvas canvas, Size size);
}
abstract class SingleChildLayoutPainterDelegate
extends SingleChildLayoutDelegate {
Listenable repaint;
SingleChildLayoutPainterDelegate({
Listenable relayout,
this.repaint,
}) : super(relayout: relayout);
void paint(Canvas canvas, Size size);
void foregroundPaint(Canvas canvas, Size size);
}
class _PainterDelegate extends CustomPainter {
final void Function(Canvas, Size) paintDelegate;
_PainterDelegate(this.paintDelegate, Listenable repaint)
: super(repaint: repaint);
@override
void paint(Canvas canvas, Size size) => paintDelegate(canvas, size);
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
输出: