如何在 Flutter 中移动屏幕中的小部件

Posted

技术标签:

【中文标题】如何在 Flutter 中移动屏幕中的小部件【英文标题】:How to move a widget in the screen in Flutter 【发布时间】:2018-09-13 23:17:42 【问题描述】:

我正在使用颤振,我有一个 容器 使用此代码的圆形形状

new Container(
 width: 50.0,
  height: 50.0,
   decoration: new BoxDecoration(
   shape: BoxShape.circle)

我想让这个圆圈像这样在屏幕上移动

我该怎么做?

【问题讨论】:

【参考方案1】:

这里是:

import 'package:flutter/material.dart';

class SecondScreen extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Drag app"),
        ),
        body: HomePage(),
      ),
    );
  


class HomePage extends StatefulWidget 
  @override
  State<StatefulWidget> createState() 
    return _HomePageState();
  


class _HomePageState extends State<HomePage> 
  double width = 100.0, height = 100.0;
  Offset position ;

  @override
  void initState() 
    super.initState();
    position = Offset(0.0, height - 20);
  

  @override
  Widget build(BuildContext context) 
    return Stack(
      children: <Widget>[
        Positioned(
          left: position.dx,
          //top: position.dy - height + 20,
          child: Draggable(
            child: Container(
              width: width,
              height: height,
              color: Colors.blue,
              child: Center(child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
            ),
            feedback: Container(
              child: Center(
                child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
              color: Colors.red[800],
              width: width,
              height: height,
            ),
            onDraggableCanceled: (Velocity velocity, Offset offset)
              setState(() => position = offset);
            ,
          ),
        ),
      ],
    );
  

【讨论】:

哇!这就是我要找的东西!效果很好【参考方案2】:

这里是如何做到这一点的整个过程

首先我们构建我们的骨架应用程序。然后我们可以将多个盒子嵌入到这个骨架中,每个盒子都有一个Offset、一个Color和一个LabelstringOffset 确定盒子在给定时刻的位置,它具有初始状态和根据用户拖动盒子的位置更新的状态。

然后创建一个使用DragTarget Classstatic UI element。我们可以将我们的Draggable Boxes 拖到这个DragTarget widget 上,以将其颜色更改为Draggable Box 的颜色。

完整示例:

class AppState extends State<App> 
  Color caughtColor = Colors.deepPurple;

  @override
  Widget build(BuildContext context) 
    return SafeArea(
      child: Stack(
        children: <Widget>[
           DragBox(Offset(0.0, 0.0), 'Box One', Colors.blueAccent),
           DragBox(Offset(150.0, 0.0), 'Box Two', Colors.orange),
           DragBox(Offset(300.0, 0.0), 'Box Three', Colors.lightGreen),
          Positioned(
            left: 125.0,
            bottom: 0.0,
            child: DragTarget(
              onAccept: (Color color) 
                caughtColor = color;
              ,
              builder: (
                  BuildContext context,
                  List<dynamic> accepted,
                  List<dynamic> rejected,
                  ) 
                return Container(
                  width: 150.0,
                  height: 150.0,
                  decoration: BoxDecoration(
                    color: accepted.isEmpty ? caughtColor : Colors.deepPurple.shade200,
                  ),
                  child: Center(
                    child: Text("Drag Here!", style: TextStyle(color: Colors.white)),
                  ),
                );
              ,
            ),
          )
        ],
      ),
    );
  


class DragBox extends StatefulWidget 
  final Offset initPos;
  final String label;
  final Color itemColor;

  DragBox(this.initPos, this.label, this.itemColor);

  @override
  DragBoxState createState() => DragBoxState();


class DragBoxState extends State<DragBox> 
  Offset position = Offset(0.0, 0.0);

  @override
  void initState() 
    super.initState();
    position = widget.initPos;
  

  @override
  Widget build(BuildContext context) 
    return Positioned(
        left: position.dx,
        top: position.dy,
        child: Draggable(
          data: widget.itemColor,
          child: Container(
            width: 100.0,
            height: 100.0,
            color: widget.itemColor,
            child: Center(
              child: Text(
                widget.label,
                style: TextStyle(
                  color: Colors.white,
                  decoration: TextDecoration.none,
                  fontSize: 20.0,
                ),
              ),
            ),
          ),
          onDraggableCanceled: (velocity, offset) 
            setState(() 
              position = offset;
            );
          ,
          feedback: Container(
            width: 120.0,
            height: 120.0,
            color: widget.itemColor.withOpacity(0.5),
            child: Center(
              child: Text(
                widget.label,
                style: TextStyle(
                  color: Colors.white,
                  decoration: TextDecoration.none,
                  fontSize: 18.0,
                ),
              ),
            ),
          ),
        ));
  

参考:Building a Drag and Drop Application

【讨论】:

【参考方案3】:

您正在寻找的是Draggable 小部件。然后,您可以使用传递的 onDraggableCanceled 处理翻译和可用于更新展示位置的偏移量

onDraggableCanceled :(velocity,offset) 
//update the position here
 

更新

检查图像后,您需要将“Drop me here”部分设为DragTarget,该部分具有onAccept 方法,当您拖放Draggable 时,该方法将处理逻辑

【讨论】:

onDraggableCanceled 在这里是不必要的。他想要的是DragTarget 啊,我看了图片后发现 @aziza 您好,如何在此处更新小部件的新位置?我是 Flutter 新手,不知道如何根据 offset 参数设置 Widget 的绝对位置。你能在这里提供一些代码吗?【参考方案4】:

您可以使用Draggable 类来拖动要拖动的项目,并将其放置或粘贴到屏幕上的某个位置,您必须使用DragTarget 类包装该项目。在DragTargetonAccept 方法中可以编写逻辑。您也可以在这里参考我的代码

import 'package:flutter/material.dart';
void main() => runApp(new MyApp());

class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.indigo,
      ),
      home: new MyHomePage(title: 'Flutter Demo Drag Box'),
    );
  


class MyHomePage extends StatelessWidget 
  MyHomePage(Key key, this.title) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(title),
      ),
      body:
          new DragGame(), // This trailing comma makes auto-formatting nicer for build methods.
    );
  


class DragGame extends StatefulWidget 
  @override
  _DragGameState createState() => new _DragGameState();


class _DragGameState extends State<DragGame> 
  int boxNumberIsDragged;

  @override
  void initState() 
    boxNumberIsDragged = null;
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return new Container(
        constraints: BoxConstraints.expand(),
        color: Colors.grey,
        child: new Stack(
          children: <Widget>[
            buildDraggableBox(1, Colors.red, new Offset(30.0, 100.0)),
            buildDraggableBox(2, Colors.yellow, new Offset(30.0, 200.0)),
            buildDraggableBox(3, Colors.green, new Offset(30.0, 300.0)),
          ],
        ));
  

  Widget buildDraggableBox(int boxNumber, Color color, Offset offset) 
    return new Draggable(
      maxSimultaneousDrags: boxNumberIsDragged == null || boxNumber == boxNumberIsDragged ? 1 : 0,
      child: _buildBox(color, offset),
      feedback: _buildBox(color, offset),
      childWhenDragging: _buildBox(color, offset, onlyBorder: true),
      onDragStarted: () 
        setState(()
          boxNumberIsDragged = boxNumber;
        );
      ,
      onDragCompleted: () 
        setState(()
          boxNumberIsDragged = null;
        );
      ,
      onDraggableCanceled: (_,__) 
        setState(()
          boxNumberIsDragged = null;
        );
      ,
    );
  

  Widget _buildBox(Color color, Offset offset, bool onlyBorder: false) 
    return new Container(
      height: 50.0,
      width: 50.0,
      margin: EdgeInsets.only(left: offset.dx, top: offset.dy),
      decoration: BoxDecoration(
          color: !onlyBorder ? color : Colors.grey,
          border: Border.all(color: color)),
    );
  

【讨论】:

【参考方案5】:

首先,用PositionedContainer 包裹在Stack 中。

然后,使用Pan Gesture 在您的Container 中实现Pan,并使用onPan... 方法处理Pan Gesture

代码如下:

偏移位置;

@override
  void initState() 
    super.initState();
    position = Offset(10, 10);
      

@override
    Widget build(BuildContext context) 

        double _width = MediaQuery.of(context).size.width;
        double _height = _width * 9 / 16;


        return GestureDetector(
          onPanStart: (details) => _onPanStart(context, details),
          onPanUpdate: (details) => _onPanUpdate(context, details, position),
          onPanEnd: (details) => _onPanEnd(context, details),
          onPanCancel: () => _onPanCancel(context),

          child: SafeArea(
            child: Stack(
              children: <Widget>[
                Positioned(
                  top: position.dy,
                  child: Container(
                    color: Colors.red,
                    width: _width,
                    height: _height,
                  ),
                ),
              ],
            ),
          ),
        );
      

      void _onPanStart(BuildContext context, DragStartDetails details) 
        print(details.globalPosition.dy);

      

      void _onPanUpdate(BuildContext context, DragUpdateDetails details, Offset offset) 
        setState(() 
          position = details.globalPosition;
        );
      

      void _onPanEnd(BuildContext context, DragEndDetails details) 
        print(details.velocity);
      

      void _onPanCancel(BuildContext context) 
        print("Pan canceled !!");
      

希望这会有所帮助!

【讨论】:

以上是关于如何在 Flutter 中移动屏幕中的小部件的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:检测任何在屏幕上不可见但在小部件树中的小部件的重建

如何在 Flutter 中的小部件下设置所有 Text 小部件的颜色?

如何在无效输入时摇动 Flutter 中的小部件?

如何在 Flutter 的小部件树中将新的 MaterialPageRoute 作为子项打开

如何从 Flutter-web 中的小部件创建图像?

Gridview 中的小部件在离屏时丢失状态