Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Positioning Issue with Draggable Widget #173

Open
ibrahimEltayfe opened this issue Mar 23, 2024 · 5 comments
Open

Positioning Issue with Draggable Widget #173

ibrahimEltayfe opened this issue Mar 23, 2024 · 5 comments

Comments

@ibrahimEltayfe
Copy link

ibrahimEltayfe commented Mar 23, 2024

When using the draggable widget or any other widget that uses offset, the positioning appears to be skewed or displaced. it does not retain its intended positions accurately during the interaction, resulting in an inaccurate rendering of the UI.

without.scaled.box.mp4
with.scaled.box.mp4

Here is the code:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      builder: (context, child) => ResponsiveBreakpoints.builder(
        breakpoints: [
          const Breakpoint(start: 0, end: 450, name: MOBILE),
          const Breakpoint(start: 451, end: 800, name: TABLET),
          const Breakpoint(start: 801, end: 1920, name: DESKTOP),
          const Breakpoint(start: 1921, end: double.infinity, name: '4K'),
        ],
        child: child!,
      ),
      //home: _Home()
      home: Builder(builder: (context) {
        return ResponsiveScaledBox(
          width: ResponsiveValue<double>(context, conditionalValues: const [
            Condition.between(start: 0, end: 450, value: 360),
            Condition.between(start: 450, end: 800, value: 460),
            Condition.between(start: 800, end: 1100, value: 580),
            Condition.between(start: 1100, end: 1400, value: 620),
            Condition.between(start: 1400, end: 9999, value: 900),
          ]).value,
          child: const _Home(),
        );
      }),
    );
  }
}

class _Home extends StatefulWidget {
  const _Home({super.key});

  @override
  State<_Home> createState() => _HomeState();
}

class _HomeState extends State<_Home> {
  Offset position = const Offset(0, 0);

  void _onDragUpdate(BuildContext context, DragUpdateDetails details) {
    position += details.delta;
  }

  void _onDragEnd(BuildContext context, DraggableDetails details) {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            Positioned(
              top: position.dy,
              left: position.dx,
              child: Draggable(
                onDragUpdate: (details) => _onDragUpdate(context, details),
                onDragEnd: (details) => _onDragEnd(context, details),

                childWhenDragging: const SizedBox.shrink(),
                feedback: const Material(child: Text("DRAGGABLE",style: TextStyle(fontSize: 50),)),
                child: const Text("DRAGGABLE",style: TextStyle(fontSize: 50),),
              ),
            ),
          ]
        ),
      ),
    );
  }
}

Flutter (Channel stable, 3.19.3)

@abigotado
Copy link

abigotado commented Mar 29, 2024

I have the same problem. In my case I'm dragging a chip with some label:

2024-03-28.20.05.32.mov

feedback appears misplaced in both directions and offset of this misplacement changes on moving.

No big difference with the code above - Chip as a child of draggable and the same chip as feedback and no onDragCallbacks.

#17 this topic was closed without any solution provided

@rayliverified Could you please help us to resolve this?

@abigotado
Copy link

@ibrahimEltayfe Thank you very much for sharing your solution!

Dunno why, but it my case it doesn't work - feedback is still misplaced. But I made a workaround this way:

 dragAnchorStrategy: (final draggable, final context, final position) {
      final responsiveBreakpoints = ResponsiveBreakpoints.of(context);
      final mediaQuery = MediaQuery.of(context);
      final double widthOffset =
          mediaQuery.size.width - responsiveBreakpoints.screenWidth;
      final double heightOffset =
          mediaQuery.size.height - responsiveBreakpoints.screenHeight;

      final RenderBox renderObject = context.findRenderObject()! as RenderBox;
      return renderObject.globalToLocal(
        Offset(
          position.dx - widthOffset / 2,
          position.dy - heightOffset / 2,
        ),
      );
    },

It's not quite proper, I think, but better, than nothing. And when you are moving your feedback widgets, it's sliding from your finger a little bit (further you move, more it slides).

Maybe it'll be a good idea to leave a report in flutter issues too.

@ibrahimEltayfe
Copy link
Author

ibrahimEltayfe commented Apr 1, 2024

@abigotado

Nice, your solution worked fine like this:

Offset adjustPosition(BuildContext context, Offset position){
  final RenderBox? renderObject = context.findRenderObject() as RenderBox?;
  return renderObject?.globalToLocal(
    Offset(
      position.dx,
      position.dy,
    ),
  ) ?? position;
}

without any further calculations, because globalToLocal is converting the global position (coordinates relative to the entire screen) to the coordinates relative to the widget itself. This takes into account any transformations or scaling applied to the widget's layout.

class _HomeState extends State<_Home> {
  Offset position = const Offset(0, 0);
  Offset currentPos = const Offset(0, 0);

  void _onDragUpdate(BuildContext context, DragUpdateDetails details) {
    currentPos += details.delta;
  }

  void _onDragEnd(BuildContext context, DraggableDetails details) {
    setState(() {
      position = adjustPosition(context, currentPos);
    });
  }

  Offset adjustPosition(BuildContext context, Offset position){
    final RenderBox? renderObject = context.findRenderObject() as RenderBox?;
    return renderObject?.globalToLocal(position,) ?? position;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            Positioned(
              top: position.dy,
              left: position.dx,
              child: Draggable(
                onDragUpdate: (details) => _onDragUpdate(context, details),
                onDragEnd: (details) => _onDragEnd(context, details),

                childWhenDragging: const SizedBox.shrink(),
                feedback: const Material(child: Text("DRAGGABLE",style: TextStyle(fontSize: 50),)),
                child: const Text("DRAGGABLE",style: TextStyle(fontSize: 50),),
              ),
            ),
          ]
        ),
      ),
    );
  }
}

The feedback positioning is working fine without handling its offset, I do not know why the feedback position not working properly on your side, my problem was with placing the item on the scaled screen. but the coordinates coming from the DraggableDetails are fine.

My remaining problem is the scale of the feedback item, I will try to make a calculation for the scale.

Screen.Recording.2024-04-01.at.7.58.29.AM.mov

@abigotado
Copy link

@ibrahimEltayfe Well, I have some suggestions about the difference. Do you place ResponsiveScaledBox at home parameter of MaterialApp? In my case I use builder. And when I move it to home - I see the same behavior as you describe - wrong scale. And when return it to builder - it has wrong position. Flutter team has confirmed the problem, see here: flutter/flutter#146002

@lvxduck
Copy link

lvxduck commented May 17, 2024

@ibrahimEltayfe

Your solution for the position works great.
Did you find the solution for the scale issue?

@abigotado

Nice, your solution worked fine like this:

Offset adjustPosition(BuildContext context, Offset position){
  final RenderBox? renderObject = context.findRenderObject() as RenderBox?;
  return renderObject?.globalToLocal(
    Offset(
      position.dx,
      position.dy,
    ),
  ) ?? position;
}

without any further calculations, because globalToLocal is converting the global position (coordinates relative to the entire screen) to the coordinates relative to the widget itself. This takes into account any transformations or scaling applied to the widget's layout.

class _HomeState extends State<_Home> {
  Offset position = const Offset(0, 0);
  Offset currentPos = const Offset(0, 0);

  void _onDragUpdate(BuildContext context, DragUpdateDetails details) {
    currentPos += details.delta;
  }

  void _onDragEnd(BuildContext context, DraggableDetails details) {
    setState(() {
      position = adjustPosition(context, currentPos);
    });
  }

  Offset adjustPosition(BuildContext context, Offset position){
    final RenderBox? renderObject = context.findRenderObject() as RenderBox?;
    return renderObject?.globalToLocal(position,) ?? position;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            Positioned(
              top: position.dy,
              left: position.dx,
              child: Draggable(
                onDragUpdate: (details) => _onDragUpdate(context, details),
                onDragEnd: (details) => _onDragEnd(context, details),

                childWhenDragging: const SizedBox.shrink(),
                feedback: const Material(child: Text("DRAGGABLE",style: TextStyle(fontSize: 50),)),
                child: const Text("DRAGGABLE",style: TextStyle(fontSize: 50),),
              ),
            ),
          ]
        ),
      ),
    );
  }
}

The feedback positioning is working fine without handling its offset, I do not know why the feedback position not working properly on your side, my problem was with placing the item on the scaled screen. but the coordinates coming from the DraggableDetails are fine.

My remaining problem is the scale of the feedback item, I will try to make a calculation for the scale.

Screen.Recording.2024-04-01.at.7.58.29.AM.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants