如何在颤动中调整画布对象/小部件的大小

时间:2021-07-06 06:00:59

标签: flutter canvas flutter-layout flutter-dependencies

我正在尝试在画布小部件中调整小部件的大小。我们可以改变它们的高度-宽度,基于中心点移动并旋转这个小部件但是在从它们的左上角调整大小时会产生问题。我们可以使用其他小部件来做到这一点,但我想使用画布。所以请帮助我或提出建议来存档。谢谢。

我的代码如下,

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;
}

输出:

enter image description here

0 个答案:

没有答案