10

I'm trying to create a simple widget so that when the user presses the screen, a circle appears at that position. I have a CustomPaint widget wrapped by a Listener widget like this:

              new Listener(
                onPointerDown: down,
                child: new CustomPaint(
                  painter: painter,
                  size: Size.infinite,
                ),
              )

The problem is that the pointer down events are supplied in global coordinates, and the painting is done in coordinates local to the CustomPaint widget. How should I convert these two coordinate systems?

This page says I can use the RenderBox.globalToLocal method but then how do I get the RenderBox of the CustomPaint widget?

Mark
  • 4,749
  • 7
  • 44
  • 53

2 Answers2

14

You don't necessarily have to wrap the listener in a widget. You can also use a GlobalKey to get the RenderObject.

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  GlobalKey _paintKey = new GlobalKey();
  Offset _offset;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('CustomPaint example'),
      ),
      body: new Listener(
        onPointerDown: (PointerDownEvent event) {
          RenderBox referenceBox = _paintKey.currentContext.findRenderObject();
          Offset offset = referenceBox.globalToLocal(event.position);
          setState(() {
            _offset = offset;
          });
        },
        child: new CustomPaint(
          key: _paintKey,
          painter: new MyCustomPainter(_offset),
          child: new ConstrainedBox(
            constraints: new BoxConstraints.expand(),
          ),
        ),
      ),
    );
  }

}

class MyCustomPainter extends CustomPainter {
  final Offset _offset;
  MyCustomPainter(this._offset);

  @override
  void paint(Canvas canvas, Size size) {
    if (_offset == null) return;
    canvas.drawCircle(_offset, 10.0, new Paint()..color = Colors.blue);
  }

  @override
  bool shouldRepaint(MyCustomPainter other) => other._offset != _offset;
}
Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
  • 2
    I'm new to Flutter, but the inline doc for GlobalKey makes it sound like they should be avoided if possible ("Global keys are relatively expensive"). So is there any performance cost with a global key compared to wrapping the listener in a custom widget (as with my answer)? – Mark Oct 09 '17 at 08:43
  • 2
    It seems now you can get the local position directly from the event: event.localPosition. Much simpler. – Piotr Dabkowski Nov 11 '21 at 14:17
1

OK this worked for me (thanks to help from @mikemimik on gitter):

Wrap the listener in a new custom widget that extends StatelessWidget. The build() method of that widget then gets access to the RenderBox like this:

  @override
  Widget build(BuildContext context) {
    void down(PointerDownEvent evt) {
      RenderBox box = context.findRenderObject();
      painter.addPos(box.globalToLocal(evt.position));
    }

    return new Listener(
      onPointerDown: down,
      child: new CustomPaint(
        painter: painter,
        size: Size.infinite,
      ),
    );
  }
Mark
  • 4,749
  • 7
  • 44
  • 53