Skip to main content
  1. Posts/

Relative click position on a widget

<time datetime="2023-02-09 00:00:00 &#43;0000 UTC">9 February 2023</time><span class="px-2 text-primary-500">&middot;</span><span title="Reading time">3 mins</span><span class="px-2 text-primary-500">&middot;</span> <span class="mb-[2px]"> <a href="https://github.com/cgutierr-zgz/cgutierr-zgz.github.io/edit/main/content/posts/relative-position-click/index.md" class="text-lg hover:text-primary-500" rel="noopener noreferrer" target="_blank" title=""><span class="relative inline-block align-text-bottom px-1 icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M490.3 40.4C512.2 62.27 512.2 97.73 490.3 119.6L460.3 149.7L362.3 51.72L392.4 21.66C414.3-.2135 449.7-.2135 471.6 21.66L490.3 40.4zM172.4 241.7L339.7 74.34L437.7 172.3L270.3 339.6C264.2 345.8 256.7 350.4 248.4 353.2L159.6 382.8C150.1 385.6 141.5 383.4 135 376.1C128.6 370.5 126.4 361 129.2 352.4L158.8 263.6C161.6 255.3 166.2 247.8 172.4 241.7V241.7zM192 63.1C209.7 63.1 224 78.33 224 95.1C224 113.7 209.7 127.1 192 127.1H96C78.33 127.1 64 142.3 64 159.1V416C64 433.7 78.33 448 96 448H352C369.7 448 384 433.7 384 416V319.1C384 302.3 398.3 287.1 416 287.1C433.7 287.1 448 302.3 448 319.1V416C448 469 405 512 352 512H96C42.98 512 0 469 0 416V159.1C0 106.1 42.98 63.1 96 63.1H192z"/></svg> </span> Suggest an edit</a> <a href="https://github.com/cgutierr-zgz/position_relative_to_widget" class="text-lg hover:text-primary-500" rel="noopener noreferrer" target="_blank"> · <span class="relative inline-block align-text-bottom px-1 icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg> </span> View source </a> </span>
I’ve recently had an usecase that I never had before: I needed to -based on a given widget- retrieve the relative position of a click on that widget and make an action based on that position.
It might be a super simple usecase, but I never had to do it before, so I found it interesting to share it with you! 😄

Maybe current context’s size is enough? 🤔 #

I started by using the current context’s size to calculate the position of the click

@override
Widget build(BuildContext context) {
  final size = MediaQuery.of(context).size;
  return GestureDetector(
    onTapDown: (details) {
      if (details.globalPosition.dx < size.width / 2) {
        // left side
        if (details.globalPosition.dy < size.height / 2) {
          // top left
        } else {
          // bottom left
        }
      } else {
        // right side
        if (details.globalPosition.dy < size.height / 2) {
          // top right
        } else {
          // bottom right
        }
      }
    },
  );
}
// ...

Ok, this works… for a widget that takes the whole screen.

decent-click

But what if we want to use this on a widget that is not taking the whole screen?

bad-click

We would need to calculate the position of the widget on the screen and then calculate the relative position of the click on that widget.

Let’s use the RenderBox 🚀 #

The RenderBox class is a base class for render objects that have a visual representation. It provides a basic set of layout and painting functionality, and the RenderBox.size property is the size of the box.

So, we can use the RenderBox.localToGlobal method to get the position of the widget on the screen and then calculate the relative position of the click on that widget.

@override
Widget build(BuildContext context) {
  return GestureDetector(
	onTapDown: (details) {
	  final renderBox = context.findRenderObject() as RenderBox;
	  final position = renderBox.localToGlobal(Offset.zero);
	  if (details.globalPosition.dx < position.dx + renderBox.size.width / 2) {
		// left side
		if (details.globalPosition.dy < position.dy + renderBox.size.height / 2) {
		  // top left
		} else {
		  // bottom left
		}
	  } else {
		// right side
		if (details.globalPosition.dy < position.dy + renderBox.size.height / 2) {
		  // top right
		} else {
		  // bottom right
		}
	  }
	},
  );
}
good-click

Whoa! That’s a lot of code for something that should be simple 😅
I did not manage to find a simpler way to do this, but if you know one, please let me know in the comments 😉

Conclusion 📝 #

In this post, I showed you how to get the relative position of a click on a widget using the RenderBox class.
Might be useful for you if you need to do something like this in your app 😄

I hope you enjoyed it and that you found it useful.
If you have any questions or suggestions, feel free to leave a comment below. 😄
Thanks for reading! 🤓

The full source code for this post is available here 🔍

References 📚 #

Author
Carlos Gutiérrez