How can I paint a Widget on a Canvas in Flutter?

4,607

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;
    }
  }
}
Share:
4,607
Magnus
Author by

Magnus

I have delivered value. But at what cost? Bachelor of Science degree in Computer Engineering. ✪ Started out on ATARI ST BASIC in the 1980's, writing mostly "Look door, take key" type games.    ✪ Spent a few years in high school writing various small programs for personal use in Delphi.    ✪ Learned PHP/SQL/HTML/JS/CSS and played around with that for a few years.    ✪ Did mostly Android and Java for a few years.    ✪ Graduated from Sweden Mid University with a BSc in Computer Engineering. At this point, I had learned all there was to know about software development, except where to find that darn "any" key...    ✪ Currently working with Flutter/Dart and Delphi (again).   

Updated on November 21, 2022

Comments

  • Magnus
    Magnus over 1 year

    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;
    }