Flutter:如何为可拖动小部件设置边界?

时间:2020-05-23 08:59:28

标签: flutter draggable

我正在尝试创建一个拖放游戏。我想确保Draggable小部件在四处拖动时不会脱离屏幕。

我找不到这个特定问题的答案。有人问过类似的关于限制可拖动区域Constraining Draggable area的问题,但答案实际上并没有使用Draggable

首先,我尝试在左侧实施限制。

我尝试将监听器与onPointerMove一起使用。我已将此事件与limitBoundaries方法相关联,以检测Draggable何时从屏幕左侧退出。当Offset输出Draggable时,此部分正在工作,因为它确实在控制台中打印了(position.dx < 0)值。我还将setState与此方法相关联,以将可拖动对象的位置设置为Offset(0.0, position.dy),但这是行不通的。

有人可以帮我吗?

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable Test',
      home: GamePlay(),
    );
  }
}

class GamePlay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Row(
            children: [
              Container(
                width: 360,
                height: 400,
                decoration: BoxDecoration(
                  color: Colors.lightGreen,
                  border: Border.all(
                    color: Colors.green,
                    width: 2.0,
                  ),
                ),
              ),
              Container(
                width: 190,
                height: 400,
                decoration: BoxDecoration(
                  color: Colors.white,
                  border: Border.all(
                    color: Colors.purple,
                    width: 2.0,
                  ),
                ),
              ),
            ],
          ),
          DragObject(
              key: GlobalKey(),
              initPos: Offset(365, 0.0),
              id: 'Item 1',
              itmColor: Colors.orange),
          DragObject(
            key: GlobalKey(),
            initPos: Offset(450, 0.0),
            id: 'Item 2',
            itmColor: Colors.pink,
          ),
        ],
      ),
    );
  }
}

class DragObject extends StatefulWidget {
  final String id;
  final Offset initPos;
  final Color itmColor;

  DragObject({Key key, this.id, this.initPos, this.itmColor}) : super(key: key);

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

class _DragObjectState extends State<DragObject> {
  GlobalKey _key;
  Offset position;
  Offset posOffset = Offset(0.0, 0.0);

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    _key = widget.key;
    position = widget.initPos;
    super.initState();
  }

  void _getRenderOffsets() {
    final RenderBox renderBoxWidget = _key.currentContext.findRenderObject();
    final offset = renderBoxWidget.localToGlobal(Offset.zero);

    posOffset = offset - position;
  }

  void _afterLayout(_) {
    _getRenderOffsets();
  }

  void limitBoundaries(PointerEvent details) {
    if (details.position.dx < 0) {
      print(details.position);
      setState(() {
        position = Offset(0.0, position.dy);
      });
    }
  }



@override
  Widget build(BuildContext context) {
    return Positioned(
      left: position.dx,
      top: position.dy,
      child: Listener(
        onPointerMove: limitBoundaries,
        child: Draggable(
          child: Container(
            width: 80,
            height: 80,
            color: widget.itmColor,
          ),
          feedback: Container(
            width: 82,
            height: 82,
            color: widget.itmColor,
          ),
          childWhenDragging: Container(),
          onDragEnd: (drag) {
            setState(() {
              position = drag.offset - posOffset;
            });
          },
        ),
      ),
    );
  }
}

2 个答案:

答案 0 :(得分:1)

尝试一下。我从Constraining Draggable area进行了调整。

  ValueNotifier<List<double>> posValueListener = ValueNotifier([0.0, 0.0]);
  ValueChanged<List<double>> posValueChanged;
  double _horizontalPos = 0.0;
  double _verticalPos = 0.0;


  @override
  void initState() {
    super.initState();
  
    posValueListener.addListener(() {
      if (posValueChanged != null) {
        posValueChanged(posValueListener.value);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
   return Scaffold(
      body: Stack(
        children: <Widget>[
           _buildDraggable(),
        ]));
  }
  
  _buildDraggable() {
    return SafeArea(
      child: Container(
        margin: EdgeInsets.only(bottom: 100),
        color: Colors.green,
        child: Builder(
          builder: (context) {
            final handle = GestureDetector(
                onPanUpdate: (details) {
                  _verticalPos =
                      (_verticalPos + details.delta.dy / (context.size.height))
                          .clamp(.0, 1.0);
                  _horizontalPos =
                      (_horizontalPos + details.delta.dx / (context.size.width))
                          .clamp(.0, 1.0);
                  posValueListener.value = [_horizontalPos, _verticalPos];
                },
                child: Container(
                  child: Container(
                    margin: EdgeInsets.all(12),
                    width: 110.0,
                    height: 170.0,
                    child: Container(
                      color: Colors.black87,
                    ),
                    decoration: BoxDecoration(color: Colors.black54),
                  ),
                ));

            return ValueListenableBuilder<List<double>>(
              valueListenable: posValueListener,
              builder:
                  (BuildContext context, List<double> value, Widget child) {
                return Align(
                  alignment: Alignment(value[0] * 2 - 1, value[1] * 2 - 1),
                  child: handle,
                );
              },
            );
          },
        ),
      ),
    );
  }

答案 1 :(得分:0)

我找到了解决此问题的方法。这不是我一直在寻找的输出,但是我认为这可能对其他人有用。

我没有尝试在拖动过程中控制拖动对象,而是让它移出屏幕之外,然后将其放回其原始位置,以防它移出屏幕外。

如果有人尝试使用我的代码,请快速注意一下,我忘了提到我正在尝试为网络开发游戏。移动设备上的输出可能有点奇怪!

import 'package:flutter/material.dart';

void main() {
 runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable Test',
      home: GamePlay(),
    );
  }
}

class GamePlay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Row(
            children: [
              Container(
                width: 360,
                height: 400,
                decoration: BoxDecoration(
                  color: Colors.lightGreen,
                  border: Border.all(
                    color: Colors.green,
                    width: 2.0,
                  ),
                ),
              ),
              Container(
                width: 190,
                height: 400,
                decoration: BoxDecoration(
                  color: Colors.white,
                  border: Border.all(
                    color: Colors.purple,
                    width: 2.0,
                  ),
                ),
              ),
            ],
          ),
          DragObject(
              key: GlobalKey(),
              initPos: Offset(365, 0.0),
              id: 'Item 1',
              itmColor: Colors.orange),
          DragObject(
            key: GlobalKey(),
            initPos: Offset(450, 0.0),
            id: 'Item 2',
            itmColor: Colors.pink,
          ),
        ],
      ),
    );
  }
}

class DragObject extends StatefulWidget {
  final String id;
  final Offset initPos;
  final Color itmColor;

  DragObject({Key key, this.id, this.initPos, this.itmColor}) : super(key: key);

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

class _DragObjectState extends State<DragObject> {
  GlobalKey _key;
  Offset position;
  Offset posOffset = Offset(0.0, 0.0);

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    _key = widget.key;
    position = widget.initPos;
    super.initState();
  }

  void _getRenderOffsets() {
    final RenderBox renderBoxWidget = _key.currentContext.findRenderObject();
    final offset = renderBoxWidget.localToGlobal(Offset.zero);

    posOffset = offset - position;
  }

  void _afterLayout(_) {
    _getRenderOffsets();
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      left: position.dx,
      top: position.dy,
      child: Listener(
        child: Draggable(
          child: Container(
            width: 80,
            height: 80,
            color: widget.itmColor,
          ),
          feedback: Container(
            width: 82,
            height: 82,
            color: widget.itmColor,
          ),
          childWhenDragging: Container(),
          onDragEnd: (drag) {
            setState(() {
              if (drag.offset.dx > 0) {
                position = drag.offset - posOffset;
              } else {
                position = widget.initPos;
              }
            });
          },
        ),
      ),
    );
  }
}

如果有人能找到解决最初问题的合适方法,我仍然很感兴趣:-)