Detecting Clicks On Overlapping CustomPaint Widgets

This post will describe how to detect on which figure is clicked when there are multiple overlapping figures. We are going to start by showing how to do this for three overlapping figures. After that, we are going to apply it to the hexagon grid from the previous post.

Detecting clicks on three overlapping figures

So instead of hexagons, let’s draw something else. We are going to draw three circles, so for this circle, we need a new CustomPainter. As described in the last post we have to implement the paint and shouldRepaint method.

<span class="token keyword">class</span> <span class="token class-name">CirclePainter</span> <span class="token keyword">extends</span> <span class="token class-name">CustomPainter</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> double radius<span class="token punctuation">;</span>
  <span class="token keyword">final</span> Offset center<span class="token punctuation">;</span>
  <span class="token keyword">final</span> Color color<span class="token punctuation">;</span>

  <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>center<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>radius<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>color<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token metadata symbol">@override</span>
  <span class="token keyword">void</span> <span class="token function">paint</span><span class="token punctuation">(</span>Canvas canvas<span class="token punctuation">,</span> Size size<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    Paint paint <span class="token operator">=</span> <span class="token function">Paint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">.</span>color <span class="token operator">=</span> color<span class="token punctuation">;</span>
    canvas<span class="token punctuation">.</span><span class="token function">drawCircle</span><span class="token punctuation">(</span>center<span class="token punctuation">,</span> radius<span class="token punctuation">,</span> paint<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token metadata symbol">@override</span>
  bool <span class="token function">shouldRepaint</span><span class="token punctuation">(</span>CustomPainter oldDelegate<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

This CustomPainter can be used by the CustomPaint widget and since we again put them in a Stacked widget, they can be overlapping. The following code snippet will show three circles with the red and yellow circles on top of the blue circle.

<span class="token keyword">class</span> <span class="token class-name">HexagonGridDemo</span> <span class="token keyword">extends</span> <span class="token class-name">StatelessWidget</span> <span class="token punctuation">{</span>
  <span class="token metadata symbol">@override</span>
  Widget <span class="token function">build</span><span class="token punctuation">(</span>BuildContext context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">MaterialApp</span><span class="token punctuation">(</span>
      home<span class="token punctuation">:</span> <span class="token function">Scaffold</span><span class="token punctuation">(</span>
        appBar<span class="token punctuation">:</span> <span class="token function">AppBar</span><span class="token punctuation">(</span>
          title<span class="token punctuation">:</span> <span class="token function">Text</span><span class="token punctuation">(</span><span class="token string">'Hexagon Grid Demo'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
        body<span class="token punctuation">:</span> <span class="token function">Stack</span><span class="token punctuation">(</span>
          children<span class="token punctuation">:</span> <span class="token operator"><</span>Widget<span class="token operator">></span><span class="token punctuation">[</span>
            <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
              painter<span class="token punctuation">:</span> <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token function">Offset</span><span class="token punctuation">(</span><span class="token number">90</span><span class="token punctuation">,</span> <span class="token number">120</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">70</span><span class="token punctuation">,</span> Colors<span class="token punctuation">.</span>blue<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
              painter<span class="token punctuation">:</span> <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token function">Offset</span><span class="token punctuation">(</span><span class="token number">60</span><span class="token punctuation">,</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> Colors<span class="token punctuation">.</span>red<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
              painter<span class="token punctuation">:</span> <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token function">Offset</span><span class="token punctuation">(</span><span class="token number">140</span><span class="token punctuation">,</span> <span class="token number">70</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">40</span><span class="token punctuation">,</span> Colors<span class="token punctuation">.</span>yellow<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">)</span>
          <span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

Adding a Listener

So how can we detect on which of the circles is clicked? First of all, we need to add a key to each circle. The key is used to find the rendered object of that Widget. We are also going to add a Listener Widget above the Stack Widget to listen to different click events. The full list of possible clicks and touches can be found in the Flutter docs. So we are going to implement the onPointerDown method. So then our main.dart is changed into the following:

<span class="token keyword">class</span> <span class="token class-name">HexagonGridDemo</span> <span class="token keyword">extends</span> <span class="token class-name">StatelessWidget</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> GlobalKey blueCircle <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GlobalKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">final</span> GlobalKey redCircle <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GlobalKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">final</span> GlobalKey yellowCircle <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GlobalKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">final</span> result <span class="token operator">=</span> <span class="token function">BoxHitTestResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token metadata symbol">@override</span>
  Widget <span class="token function">build</span><span class="token punctuation">(</span>BuildContext context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">MaterialApp</span><span class="token punctuation">(</span>
      home<span class="token punctuation">:</span> <span class="token function">Scaffold</span><span class="token punctuation">(</span>
        appBar<span class="token punctuation">:</span> <span class="token function">AppBar</span><span class="token punctuation">(</span>
          title<span class="token punctuation">:</span> <span class="token function">Text</span><span class="token punctuation">(</span><span class="token string">'Hexagon Grid Demo'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
        body<span class="token punctuation">:</span> <span class="token function">Listener</span><span class="token punctuation">(</span>
          onPointerDown<span class="token punctuation">:</span> <span class="token punctuation">(</span>PointerEvent pointerEvent<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token function">handleClick</span><span class="token punctuation">(</span>pointerEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span><span class="token punctuation">,</span>
          child<span class="token punctuation">:</span> <span class="token function">Stack</span><span class="token punctuation">(</span>
            children<span class="token punctuation">:</span> <span class="token operator"><</span>Widget<span class="token operator">></span><span class="token punctuation">[</span>
              <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
                key<span class="token punctuation">:</span> blueCircle<span class="token punctuation">,</span>
                painter<span class="token punctuation">:</span> <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token function">Offset</span><span class="token punctuation">(</span><span class="token number">90</span><span class="token punctuation">,</span> <span class="token number">120</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">70</span><span class="token punctuation">,</span> Colors<span class="token punctuation">.</span>blue<span class="token punctuation">)</span><span class="token punctuation">,</span>
                child<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
              <span class="token punctuation">)</span><span class="token punctuation">,</span>
              <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
                key<span class="token punctuation">:</span> redCircle<span class="token punctuation">,</span>
                painter<span class="token punctuation">:</span> <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token function">Offset</span><span class="token punctuation">(</span><span class="token number">60</span><span class="token punctuation">,</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> Colors<span class="token punctuation">.</span>red<span class="token punctuation">)</span><span class="token punctuation">,</span>
                child<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
              <span class="token punctuation">)</span><span class="token punctuation">,</span>
              <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
                key<span class="token punctuation">:</span> yellowCircle<span class="token punctuation">,</span>
                painter<span class="token punctuation">:</span> <span class="token function">CirclePainter</span><span class="token punctuation">(</span><span class="token function">Offset</span><span class="token punctuation">(</span><span class="token number">140</span><span class="token punctuation">,</span> <span class="token number">70</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">40</span><span class="token punctuation">,</span> Colors<span class="token punctuation">.</span>yellow<span class="token punctuation">)</span><span class="token punctuation">,</span>
                child<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
              <span class="token punctuation">)</span>
            <span class="token punctuation">]</span><span class="token punctuation">,</span>
          <span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token keyword">final</span> PointerEvent pointerEvent<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// detect click on circles</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

So now it is time to implement the handleClick method. We use a helper that does all the magic. The isClicked returns true if the clicks were in the CustomPaint of the corresponding GlobalKey. From the GlobalKey we can use the currentContext.findRenderObject();. This will return the RenderBox of the CustomPaint widget. So now we can use the hitTest on the RenderBox. Before we do that we have to translate the position of the Listener to a local position. This can be done with the globalToLocal method on the RenderBox. So the global point (40,120) will return the local point (40,40). The x coordinate remains the same since we have no other widgets horizontally. The y coordinate is subtracted by 80. This is because there is an AppBar above the Canvas. In this case, we could also use the localPosition of the details, since all CustomPaint elements are drawn in the same Canvas.

<span class="token function">handleClick</span><span class="token punctuation">(</span>PointerEvent details<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isClicked</span><span class="token punctuation">(</span>details<span class="token punctuation">,</span> redCircle<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"clicked the red circle"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isClicked</span><span class="token punctuation">(</span>details<span class="token punctuation">,</span> blueCircle<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"clicked the blue circle"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isClicked</span><span class="token punctuation">(</span>details<span class="token punctuation">,</span> yellowCircle<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"clicked the yellow circle"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

bool <span class="token function">isClicked</span><span class="token punctuation">(</span><span class="token keyword">final</span> PointerEvent details<span class="token punctuation">,</span> <span class="token keyword">final</span> GlobalKey key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> RenderBox circleBox <span class="token operator">=</span> key<span class="token punctuation">.</span>currentContext<span class="token punctuation">.</span><span class="token function">findRenderObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  Offset localClick <span class="token operator">=</span> circleBox<span class="token punctuation">.</span><span class="token function">globalToLocal</span><span class="token punctuation">(</span>details<span class="token punctuation">.</span>position<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//    Offset localClick = details.localPosition</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>circleBox<span class="token punctuation">.</span><span class="token function">hitTest</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> position<span class="token punctuation">:</span> localClick<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

So if we would try this out, you will probably see the following on each click:

flutter<span class="token punctuation">:</span> clicked the red circle
flutter<span class="token punctuation">:</span> clicked the blue circle
flutter<span class="token punctuation">:</span> clicked the yellow circle

Implementing the hitTest

This is because we have to do one more thing. Our CustomPainter has to implement the hitTest method. The hitTest method has as input an Offset position. We can use the contains method of the path to determine if it is in or outside the circle. We can create a path of the circle with the fromCircle method of the Rect. It expects the center and the radius of the circle, which we already know because we needed them to draw the circle.

<span class="token metadata symbol">@override</span>
bool <span class="token function">hitTest</span><span class="token punctuation">(</span>Offset position<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> Path path <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Path</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  path<span class="token punctuation">.</span><span class="token function">addRect</span><span class="token punctuation">(</span>Rect<span class="token punctuation">.</span><span class="token function">fromCircle</span><span class="token punctuation">(</span>center<span class="token punctuation">:</span> center<span class="token punctuation">,</span> radius<span class="token punctuation">:</span> radius<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> path<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>position<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

So after this method is implemented, the clicked on should print each circle that is clicked. You can try it out for yourself in this Dartpad. Just a reminder, the prints will be shown in the console in the bottom left corner. 

Detecting clicks on a list of CustomPaint widgets

So how do we integrate this into our Hexagon Grid we created in our last post.
We again have to the following things:

  • Implement the hitTest for our HexagonPainter
  • Add GlobalKeys to our CustomPaint widgets
  • Add a Listener on the Stacked widget
  • Iterate over the CustomPaint widgets to detect which Hexagon is clicked

Implementing the hitTest

So let’s start with implementing the hitTest. Since we already create a hexagon path in the paint method, we can reuse that for the hitTest. We get an Offset position, create a path, and use the contains method on the path.

<span class="token keyword">class</span> <span class="token class-name">HexagonPainter</span> <span class="token keyword">extends</span> <span class="token class-name">CustomPainter</span> <span class="token punctuation">{</span>
  <span class="token keyword">static</span> <span class="token keyword">const</span> int SIDES_OF_HEXAGON <span class="token operator">=</span> <span class="token number">6</span><span class="token punctuation">;</span>
  <span class="token keyword">final</span> double radius<span class="token punctuation">;</span>
  <span class="token keyword">final</span> Offset center<span class="token punctuation">;</span>

  <span class="token function">HexagonPainter</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>center<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>radius<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token metadata symbol">@override</span>
  <span class="token keyword">void</span> <span class="token function">paint</span><span class="token punctuation">(</span>Canvas canvas<span class="token punctuation">,</span> Size size<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    Paint paint <span class="token operator">=</span> <span class="token function">Paint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">.</span>color <span class="token operator">=</span> Colors<span class="token punctuation">.</span>blue<span class="token punctuation">;</span>
    Path path <span class="token operator">=</span> <span class="token function">createHexagonPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    canvas<span class="token punctuation">.</span><span class="token function">drawPath</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> paint<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  Path <span class="token function">createHexagonPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">final</span> path <span class="token operator">=</span> <span class="token function">Path</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> angle <span class="token operator">=</span> <span class="token punctuation">(</span>math<span class="token punctuation">.</span>pi <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">/</span> SIDES_OF_HEXAGON<span class="token punctuation">;</span>
    Offset firstPoint <span class="token operator">=</span> <span class="token function">Offset</span><span class="token punctuation">(</span>radius <span class="token operator">*</span> math<span class="token punctuation">.</span><span class="token function">cos</span><span class="token punctuation">(</span><span class="token number">0.0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> radius <span class="token operator">*</span> math<span class="token punctuation">.</span><span class="token function">sin</span><span class="token punctuation">(</span><span class="token number">0.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    path<span class="token punctuation">.</span><span class="token function">moveTo</span><span class="token punctuation">(</span>firstPoint<span class="token punctuation">.</span>dx <span class="token operator">+</span> center<span class="token punctuation">.</span>dx<span class="token punctuation">,</span> firstPoint<span class="token punctuation">.</span>dy <span class="token operator">+</span> center<span class="token punctuation">.</span>dy<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>int i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator"><=</span> SIDES_OF_HEXAGON<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      double x <span class="token operator">=</span> radius <span class="token operator">*</span> math<span class="token punctuation">.</span><span class="token function">cos</span><span class="token punctuation">(</span>angle <span class="token operator">*</span> i<span class="token punctuation">)</span> <span class="token operator">+</span> center<span class="token punctuation">.</span>dx<span class="token punctuation">;</span>
      double y <span class="token operator">=</span> radius <span class="token operator">*</span> math<span class="token punctuation">.</span><span class="token function">sin</span><span class="token punctuation">(</span>angle <span class="token operator">*</span> i<span class="token punctuation">)</span> <span class="token operator">+</span> center<span class="token punctuation">.</span>dy<span class="token punctuation">;</span>
      path<span class="token punctuation">.</span><span class="token function">lineTo</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    path<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> path<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token metadata symbol">@override</span>
  bool <span class="token function">shouldRepaint</span><span class="token punctuation">(</span>CustomPainter oldDelegate<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token boolean">false</span><span class="token punctuation">;</span>

  <span class="token metadata symbol">@override</span>
  bool <span class="token function">hitTest</span><span class="token punctuation">(</span>Offset position<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">final</span> Path path <span class="token operator">=</span> <span class="token function">createHexagonPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> path<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>position<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

Add GlobalKeys to our CustomPaint widgets

To add GlobalKeys for each of our CustomPaint widgets, we extracted a HexagonModel for the variables needed in the HexagonPaint Widget. This contains the current variables: center, radius. It also creates a new GlobalKey. The constructor of the HexagonPaint now expects a HexagonModel. We add the key of the model to the CustomPaint widget.

<span class="token keyword">class</span> <span class="token class-name">HexagonModel</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> Offset center<span class="token punctuation">;</span>
  <span class="token keyword">final</span> double radius<span class="token punctuation">;</span>
  <span class="token keyword">final</span> GlobalKey key <span class="token operator">=</span> <span class="token function">GlobalKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">HexagonModel</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>center<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>radius<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">HexagonPaint</span> <span class="token keyword">extends</span> <span class="token class-name">StatelessWidget</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> HexagonModel model<span class="token punctuation">;</span>

  <span class="token function">HexagonPaint</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>model<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token metadata symbol">@override</span>
  Widget <span class="token function">build</span><span class="token punctuation">(</span>BuildContext context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token function">CustomPaint</span><span class="token punctuation">(</span>
      key<span class="token punctuation">:</span> model<span class="token punctuation">.</span>key<span class="token punctuation">,</span>
      painter<span class="token punctuation">:</span> <span class="token function">HexagonPainter</span><span class="token punctuation">(</span>model<span class="token punctuation">.</span>center<span class="token punctuation">,</span> model<span class="token punctuation">.</span>radius<span class="token punctuation">)</span><span class="token punctuation">,</span>
      child<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

Add a Listener on the Stacked widget

Just like the circles, we added a Listener above the Container with all the Paint widgets. The HexagonGrid is now passed to the container and the grid will display the list of HexagonPaint Widgets.

<span class="token metadata symbol">@override</span>
Widget <span class="token function">build</span><span class="token punctuation">(</span>BuildContext context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token function">MaterialApp</span><span class="token punctuation">(</span>
    home<span class="token punctuation">:</span> <span class="token function">Scaffold</span><span class="token punctuation">(</span>
      appBar<span class="token punctuation">:</span> <span class="token function">AppBar</span><span class="token punctuation">(</span>
        title<span class="token punctuation">:</span> <span class="token function">Text</span><span class="token punctuation">(</span><span class="token string">'Hexagon Grid Demo'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">,</span>
      body<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span>
        color<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>grey<span class="token punctuation">[</span><span class="token number">200</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        padding<span class="token punctuation">:</span> EdgeInsets<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        child<span class="token punctuation">:</span> <span class="token function">LayoutBuilder</span><span class="token punctuation">(</span>builder<span class="token punctuation">:</span> <span class="token punctuation">(</span>context<span class="token punctuation">,</span> constraints<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          grid<span class="token punctuation">.</span><span class="token function">initialize</span><span class="token punctuation">(</span>constraints<span class="token punctuation">.</span>maxWidth<span class="token punctuation">,</span> constraints<span class="token punctuation">.</span>maxHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token keyword">return</span> <span class="token function">Listener</span><span class="token punctuation">(</span>
            onPointerDown<span class="token punctuation">:</span> <span class="token punctuation">(</span>PointerEvent details<span class="token punctuation">)</span> <span class="token punctuation">{</span>
              <span class="token function">handleClick</span><span class="token punctuation">(</span>details<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            child<span class="token punctuation">:</span> <span class="token function">Container</span><span class="token punctuation">(</span>
              width<span class="token punctuation">:</span> constraints<span class="token punctuation">.</span>maxWidth<span class="token punctuation">,</span>
              height<span class="token punctuation">:</span> constraints<span class="token punctuation">.</span>maxHeight<span class="token punctuation">,</span>
              color<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>transparent<span class="token punctuation">,</span>
              child<span class="token punctuation">:</span> grid<span class="token punctuation">,</span>
            <span class="token punctuation">)</span><span class="token punctuation">,</span>
          <span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Iterate over the CustomPaint widgets to detect which Hexagon is clicked

The handleClick now iterates over the HexagonPaints and passes the HexagonPaint to the determineClick. The method determineClick is almost the same as in the first example. The only thing that has changed is that we now get the key to the model of the HexagonPaint Widget instead of a variable in the Class. We used the firstWhere to detect which Hexagon is clicked, since none of the Hexagons are overlapping each other.

<span class="token function">handleClick</span><span class="token punctuation">(</span>PointerEvent details<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">var</span> hexagon <span class="token operator">=</span>
      grid<span class="token punctuation">.</span>hexagons<span class="token punctuation">.</span><span class="token function">firstWhere</span><span class="token punctuation">(</span><span class="token punctuation">(</span>hexagon<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token function">determineClick</span><span class="token punctuation">(</span>hexagon<span class="token punctuation">,</span> details<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

bool <span class="token function">determineClick</span><span class="token punctuation">(</span>HexagonPaint hexagon<span class="token punctuation">,</span> PointerEvent details<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> RenderBox hexagonBox <span class="token operator">=</span>
      hexagon<span class="token punctuation">.</span>model<span class="token punctuation">.</span>key<span class="token punctuation">.</span>currentContext<span class="token punctuation">.</span><span class="token function">findRenderObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">final</span> result <span class="token operator">=</span> <span class="token function">BoxHitTestResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>hexagonBox<span class="token punctuation">.</span><span class="token function">hitTest</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> position<span class="token punctuation">:</span> details<span class="token punctuation">.</span>localPosition<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Again we put the full code here in a Dartpad, so you can play around with the code without having a full setup. Should you still have any questions, feel free to ask them!

Leave a Reply