Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is draggable widget not being placed in correct position?

Tags:

flutter

dart

I'm struggling to understand why after moving a draggable widget it gets placed into another position about 100px lower than where I'm placing it... The only thing I can think of is that it's adding the height of the Appbar and status bar ...

I thought I was doing something wrong so I decided to create the simplest example I could and it's still doing the same thing.

Just to confirm, I don't want to use drag targets, or anything like that ... I'd simply like the draggable widget to land exactly where I place the thing. However, I do need it inside of a Stack

[EDIT] It seems that removing the AppBar allows the Draggable to land exactly where you place it. However, I don't want the draggable widget to go behind the Status bar and so after adding a SafeArea I'm left with a similar problem. [/EDIT]

import 'package:flutter/material.dart';

class DraggableTest extends StatefulWidget {
  static const routeName = '/draggable-test';
  @override
  _DraggableTestState createState() => _DraggableTestState();
}

class _DraggableTestState extends State<DraggableTest> {
  Offset _dragOffset = Offset(0, 0);

  Widget _dragWidget() {
    return Positioned(
      left: _dragOffset.dx,
      top: _dragOffset.dy,
      child: Draggable(
        child: Container(
          height: 120,
          width: 90,
          color: Colors.black,
        ),
        childWhenDragging: Container(
          height: 120,
          width: 90,
          color: Colors.grey,
        ),
        feedback: Container(
          height: 120,
          width: 90,
          color: Colors.red,
        ),
        onDragEnd: (drag) {
          setState(() {
            _dragOffset = drag.offset;
          });
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Stack(
        children: <Widget>[
          _dragWidget(),
        ],
      ),
    );
  }
}
like image 644
Chris Avatar asked Oct 26 '25 15:10

Chris


1 Answers

The main issue has to do with global position vs local position: Your Draggable widget gives global position whereas the Positioned inside your Stack takes local position. When there is no AppBar global and local positions match so the issue disappear but is still here.

So the real fix here is:

  1. Convert your global coordinates to locale ones:
RenderBox renderBox = context.findRenderObject();
onDragEnd(renderBox.globalToLocal(drag.offset));
  1. You now need a context. This context has to be local (the of the Draggable for example). So in the final implementation you can embed either the Stack or the Draggable in a StatelessWidget class in order to get a local context.

Here is my final implementation:

import 'package:flutter/material.dart';

main() {
  runApp(MaterialApp(
    home: DraggableTest(),
  ));
}

class DraggableTest extends StatefulWidget {
  static const routeName = '/draggable-test';

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

class _DraggableTestState extends State<DraggableTest> {
  Offset _dragOffset = Offset(0, 0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Stack(
        children: <Widget>[
          Positioned(
            left: _dragOffset.dx,
            top: _dragOffset.dy,
            child: DragWidget(onDragEnd: onDragEnd),
          ),
        ],
      ),
    );
  }

  void onDragEnd(Offset offset) {
    setState(() {
      _dragOffset += offset;
    });
  }
}

class DragWidget extends StatelessWidget {
  final void Function(Offset) onDragEnd;

  const DragWidget({Key key, this.onDragEnd}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Draggable(
      child: Container(
        height: 120,
        width: 90,
        color: Colors.black,
      ),
      childWhenDragging: Container(
        height: 120,
        width: 90,
        color: Colors.grey,
      ),
      feedback: Container(
        height: 120,
        width: 90,
        color: Colors.red,
      ),
      onDragEnd: (drag) {
        RenderBox renderBox = context.findRenderObject();
        onDragEnd(renderBox.globalToLocal(drag.offset));
      },
    );
  }
}

Note that the offset returned by renderBox.globalToLocal(drag.offset) is the offset inside the Draggable (from the start position to the end position). It is why we need to compute the final offset by setting _dragOffset += offset.

like image 167
Lulupointu Avatar answered Oct 29 '25 07:10

Lulupointu