在卡中任意定位小部件(基于相对于父级的x-y坐标)

时间:2018-11-26 20:57:37

标签: dart flutter

我要在卡上放置一个点,该点可以在卡内任意移动。

到目前为止,这是我的解决方法。

class RoomCard extends StatefulWidget {
  final Room room;

  RoomCard({
    @required this.room,
  }) : assert(room != null);

  @override
  _RoomCardState createState() => _RoomCardState();
}

class _RoomCardState extends State<RoomCard> {
  double x = 0.0;
  double y = 0.0;
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 400.0,
      width: 400.0,
      child: GestureDetector(
        onPanUpdate: (p) {
          setState(() {
            x += p.delta.dx;
            y += p.delta.dy;
          });
        },
        child: Card(
          child: Stack(
            children: <Widget>[
              Marker(
                x: x,
                y: y,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class Marker extends StatelessWidget {
  final double x;
  final double y;

  Marker({this.x: 0.0, this.y: 0.0});

  @override
  Widget build(BuildContext context) {
    print("x: $x, y: $y");
    return Padding(
      padding: EdgeInsets.only(left: x, top: y),
      child: CircleAvatar(),
    );
  }
}

除使用Padding小部件执行此操作外,我找不到其他方法可将标记根据x,y位置放置在卡中。让我知道是否还有其他更好的方法。

其次,这是第一次工作(第一次移动)。事后移动时出现问题。我在这里错过任何逻辑吗?

我想进一步扩展它,使其在卡中具有多个这样的点,可以很容易地放置和移动它们。

如果您可以建议执行此操作的任何第三方软件包,我都很高兴。

2 个答案:

答案 0 :(得分:1)

您要寻找的可能是CustomSingleChildLayoutCustomMultiChildLayout

使用CustomSingleChildLayout看起来像这样:

class RoomCard extends StatefulWidget {
  @override
  _RoomCardState createState() => _RoomCardState();
}

class _RoomCardState extends State<RoomCard> {
  Offset position = Offset.zero;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 400.0,
      width: 400.0,
      child: GestureDetector(
        onPanUpdate: (p) {
          setState(() => position += p.delta);
        },
        child: CustomSingleChildLayout(
          delegate: MarkerLayoutDelegate(position),
          child: Marker(),
        ),
      ),
    );
  }
}

class CallableNotifier extends ChangeNotifier {
  void notify() {
    this.notifyListeners();
  }
}

class MarkerLayoutDelegate extends SingleChildLayoutDelegate with ChangeNotifier {
  Offset position;

  MarkerLayoutDelegate(this.position);

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return constraints.loosen();
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    return Offset(min(position.dx, size.width - childSize.width), min(position.dy, size.height - childSize.height));
  }

  @override
  bool shouldRelayout(MarkerLayoutDelegate oldDelegate) {
    return position != oldDelegate.position;
  }
}

class Marker extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 30,
      height: 30,
      child: CircleAvatar(),
    );
  }
}

或者您可以使用侦听器来实现,使得每次更改点的位置时主小部件都不需要重建:

class RoomCard extends StatefulWidget {
  @override
  _RoomCardState createState() => _RoomCardState();
}

class _RoomCardState extends State<RoomCard> {
  double x = 0.0;
  double y = 0.0;

  MarkerLayoutDelegate delegate = MarkerLayoutDelegate(relayout: CallableNotifier());

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 400.0,
      width: 400.0,
      child: GestureDetector(
        onPanUpdate: (p) {
          delegate.position += p.delta;
        },
        child: CustomSingleChildLayout(
          delegate: delegate,
          child: Marker(),
        ),
      ),
    );
  }
}

class CallableNotifier extends ChangeNotifier {
  void notify() {
    this.notifyListeners();
  }
}

class MarkerLayoutDelegate extends SingleChildLayoutDelegate with ChangeNotifier {
  Offset _position;

  CallableNotifier _notifier;

  MarkerLayoutDelegate({CallableNotifier relayout, Offset initialPosition = Offset.zero})
      : _position = initialPosition,
        _notifier = relayout,
        super(relayout: relayout);

  set position(Offset position) {
    _position = position;
    _notifier.notifyListeners();
  }

  Offset get position => _position;

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return constraints.loosen();
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    return Offset(min(_position.dx, size.width - childSize.width), min(_position.dy, size.height - childSize.height));
  }

  @override
  bool shouldRelayout(MarkerLayoutDelegate oldDelegate) {
    return _position != oldDelegate._position;
  }
}

class Marker extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 30,
      height: 30,
      child: CircleAvatar(),
    );
  }
}

答案 1 :(得分:1)

您可以使用如下所示的Transform

  class Marker extends StatelessWidget {
    final double x;
    final double y;

    Marker({this.x: 0.0, this.y: 0.0});

    @override
    Widget build(BuildContext context) {
      print("x: $x, y: $y");

      return Transform(
          transform: Matrix4.translationValues(x, y, 0.0), child: CircleAvatar());
    }
  }

您需要检查x,y约束以将变换绑定到特定区域

编辑:

这是一个完整的工作代码,用于说明如何将标记限制在卡片的底部边缘

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(new MaterialApp(
    home: new Scaffold(
      body: RoomCard(room: Room()),
    ),
  ));
}

class Room {}

class RoomCard extends StatefulWidget {
  final Room room;

  RoomCard({
    @required this.room,
  }) : assert(room != null);

  @override
  _RoomCardState createState() => _RoomCardState();
}

class _RoomCardState extends State<RoomCard> {
  double x = 0.0;
  double y = 0.0;

  @override
  Widget build(BuildContext context) {

    //This hight should be known or calculated for the Widget need to be moved
    const double markerHight = 50.0;

    double ymax = context.findRenderObject()?.paintBounds?.bottom ?? markerHight ;


    return SizedBox(
      height: 300.0,
      width: 400.0,
      child: GestureDetector(
        onPanUpdate: (p) {
          setState(() {
            x += p.delta.dx;
            y = (y+p.delta.dy) >ymax - markerHight ? ymax -markerHight : y+p.delta.dy;

          });
        },
        child: Card(
          child: Stack(
            children: <Widget>[

              Marker(
                x: x,
                y: y,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

      class Marker extends StatelessWidget {
        final double x;
        final double y;

        Marker({this.x: 0.0, this.y: 0.0});

        @override
        Widget build(BuildContext context) {
          print("x: $x, y: $y");
          return Transform(
              transform: Matrix4.translationValues(x, y, 0.0), 
              child: CircleAvatar());
        }
      }