Flutter:如何为 Draggable 小部件设置边界?

Posted

技术标签:

【中文标题】Flutter:如何为 Draggable 小部件设置边界?【英文标题】:Flutter: How to set boundaries for a Draggable widget? 【发布时间】:2020-09-10 03:47:54 【问题描述】:

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

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

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

我尝试使用带有onPointerMove 的侦听器。我已将此事件与 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;
            );
          ,
        ),
      ),
    );
  

【问题讨论】:

【参考方案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,
                );
              ,
            );
          ,
        ),
      ),
    );
  

【讨论】:

【参考方案2】:

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

我没有尝试在拖动过程中控制拖动对象,而是让它离开我的屏幕,然后我将它放回原来的位置,以防它离开屏幕。

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

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

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

【讨论】:

以上是关于Flutter:如何为 Draggable 小部件设置边界?的主要内容,如果未能解决你的问题,请参考以下文章

如何为可扩展的 Flutter 小部件设置动画以将其滑出屏幕

如何为我的小部件添加边距?了解 EdgeInsets 的效果

Flutter:如何为 Selectable Text 构建自定义工具栏~

Flutter:如何为透明容器添加阴影?

如何为容器添加背景颜色,包括 Flutter 中的 Expanded

如何用 Flutter 处理嵌套的 InkWell?