12

Is there any way to paint a Widget at a given position on a Canvas?

More specifically, I want to paint the child widgets of Marker's related to a FlutterMap on a separate Canvas in front of the actual FlutterMap widget. Here's an attempt at creating a CustomPainter that would do that, but I can't figure out how to actually paint the widgets on the canvas. Using the RenderObject requires a PaintingContext, which I don't know how to create/retrieve:

class MarkerPainter extends CustomPainter {
  MapController mc;
  BuildContext context;
  List<Marker> markers;

  MarkerPainter(this.context, this.mc, this.markers);

  @override
  void paint(Canvas canvas, Size size) {
    if( markers != null && markers.isNotEmpty ){
      for(int i=0; i<markers.length; i++){
        Marker marker = markers[i];
        Offset o = myCalculateOffsetFromLatLng(marker.point, mc, context);

        // Won't work, this needs a PaintingContext...
        marker.builder(context).createElement().renderObject.paint(context, o); 
      }
    }
  }

  @override
  bool shouldRepaint(MarkerPainter oldDelegate) => oldDelegate.markers != markers;
}
Magnus
  • 17,157
  • 19
  • 104
  • 189
  • Solved it in another way. As a side note for others looking to do custom painting on a FlutterMap: simply create a FlutterMap plugin, layer options and layer class. i.e. class MyLayerPlugin extends MapPlugin, class MyLayerOptions extends LayerOptions, class MyLayer extends StatelessWidget. Have a look at how the builtin Layer classes (ex. MarkerLayer) are implemented and implement something similar in your MyLayer class. Then register your MyLayerPlugin on the FlutterMap widget and add your layer as any other layer. – Magnus May 19 '20 at 11:59
  • it would be great if you share your solution in Answer – GNassro Dec 23 '21 at 03:49

1 Answers1

13

You cannot do this with a CustomPainter.

This class is only a simplification of the real deal: RenderObject, which has access to everything related to painter (and layout+more).

What you should do is, instead of a CustomPainter, create a RenderBox (a 2d RenderObject)

In your case, what you want is to draw a list of widgets. In that situation, you will need to create:

Wrapping up, a widget used this way:

MyExample(
  children: [
    Text('foo'),
    Text('bar'),
  ],
),

would be written this way:

import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class MyExample extends MultiChildRenderObjectWidget {
  MyExample({
    Key? key,
    required List<Widget> children,
  }) : super(key: key, children: children);

  @override
  RenderMyExample createRenderObject(BuildContext context) {
    return RenderMyExample();
  }
}

class MyExampleParentData extends ContainerBoxParentData<RenderBox> {}

class RenderMyExample extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, MyExampleParentData> {
  @override
  void setupParentData(RenderObject child) {
    if (child.parentData is! MyExampleParentData) {
      child.parentData = MyExampleParentData();
    }
  }

  @override
  void performLayout() {
    size = constraints.biggest;

    for (var child = firstChild; child != null; child = childAfter(child)) {
      child.layout(
        // limit children to a max height of 50
        constraints.copyWith(maxHeight: 50),
      );
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // Paints all children in order vertically, separated by 50px

    var verticalOffset = .0;
    for (var child = firstChild; child != null; child = childAfter(child)) {
      context.paintChild(child, offset + Offset(0, verticalOffset));

      verticalOffset += 50;
    }
  }
}
Pavel Shorokhov
  • 4,485
  • 1
  • 35
  • 44
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432