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

8
我正在尝试创建一个拖放游戏。我希望确保当拖动Draggable小部件时,它们不会离开屏幕。
我找不到对这个特定问题的答案。有人问了类似的约束可拖动区域的问题Constraining Draggable area,但该答案实际上没有使用Draggable
首先,我尝试在左侧实现限制。我试图使用带有onPointerMove的Listener。我将此事件与limitBoundaries方法关联起来,以便检测Draggable何时从屏幕左侧退出。这部分正在工作,因为当Draggable即将退出(position.dx < 0)时,它确实在控制台中打印出Offset值。我还将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;
            });
          },
        ),
      ),
    );
  }
}
3个回答

3

试试这个。我是从这里修改的:约束可拖拽区域

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

0

你可以使用小部件Draggable的属性onDragEnd:,并在设置新位置之前使用MediaQuery比较设备的高度或宽度,并仅在未超出屏幕限制时更新位置,否则将新位置设置为初始位置。

以下是示例:

Positioned(
                      left: position.dx,
                      top: position.dy,
                      child: Draggable(
                        maxSimultaneousDrags: 1,
                        childWhenDragging:
                            Opacity(opacity: .2, child: rangeEvent(context)),
                        feedback: rangeEvent(context),
                        axis: Axis.vertical,
                        affinity: Axis.vertical,
                        onDragEnd: (details) => updatePosition(details.offset),
                        child: Transform.scale(
                          scale: scale,
                          child: rangeEvent(context),
                        ),
                      ),
                    )

在方法updatePosition中,您在更新之前验证新位置:
  void updatePosition(Offset newPosition) => setState(() {
        if (newPosition.dy > 10 &&
            newPosition.dy < MediaQuery.of(context).size.height * 0.9) {
          position = newPosition;
        } else {
          position = const Offset(0, 0);// initial possition 
        }
      });

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

如果有人能找到一个合适的解决方案,我仍然很感兴趣 :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接