Skip to content

Instantly share code, notes, and snippets.

@itsJoKr
Last active November 11, 2023 11:16
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save itsJoKr/ce5ec57bd6dedf74d1737c1f39481913 to your computer and use it in GitHub Desktop.
Save itsJoKr/ce5ec57bd6dedf74d1737c1f39481913 to your computer and use it in GitHub Desktop.
Quick way to convert the widget to marker, not supposed to work with images.
import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;
/// This just adds overlay and builds [_MarkerHelper] on that overlay.
/// [_MarkerHelper] does all the heavy work of creating and getting bitmaps
class MarkerGenerator {
final Function(List<Uint8List>) callback;
final List<Widget> markerWidgets;
MarkerGenerator(this.markerWidgets, this.callback);
void generate(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => afterFirstLayout(context));
}
void afterFirstLayout(BuildContext context) {
addOverlay(context);
}
void addOverlay(BuildContext context) {
OverlayState overlayState = Overlay.of(context);
OverlayEntry entry = OverlayEntry(
builder: (context) {
return _MarkerHelper(
markerWidgets: markerWidgets,
callback: (List<Uint8List> bitmapList) {
callback.call(bitmapList);
// Remove marker widgets from Overlay when finished
entry.remove();
},
);
},
maintainState: true);
overlayState.insert(entry);
}
}
/// Maps are embeding GoogleMap library for Andorid/iOS into flutter.
///
/// These native libraries accept BitmapDescriptor for marker, which means that for custom markers
/// you need to draw view to bitmap and then send that to BitmapDescriptor.
///
/// Because of that Flutter also cannot accept Widget for marker, but you need draw it to bitmap and
/// that's what this widget does:
///
/// 1) It draws marker widget to tree
/// 2) After painted access the repaint boundary with global key and converts it to uInt8List
/// 3) Returns set of Uint8List (bitmaps) through callback
class _MarkerHelper extends StatefulWidget {
final List<Widget> markerWidgets;
final Function(List<Uint8List>) callback;
const _MarkerHelper({Key key, this.markerWidgets, this.callback})
: super(key: key);
@override
_MarkerHelperState createState() => _MarkerHelperState();
}
class _MarkerHelperState extends State<_MarkerHelper> with AfterLayoutMixin {
List<GlobalKey> globalKeys = List<GlobalKey>();
@override
void afterFirstLayout(BuildContext context) {
_getBitmaps(context).then((list) {
widget.callback(list);
});
}
@override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(MediaQuery.of(context).size.width, 0),
child: Material(
type: MaterialType.transparency,
child: Stack(
children: widget.markerWidgets.map((i) {
final markerKey = GlobalKey();
globalKeys.add(markerKey);
return RepaintBoundary(
key: markerKey,
child: i,
);
}).toList(),
),
),
);
}
Future<List<Uint8List>> _getBitmaps(BuildContext context) async {
var futures = globalKeys.map((key) => _getUint8List(key));
return Future.wait(futures);
}
Future<Uint8List> _getUint8List(GlobalKey markerKey) async {
RenderRepaintBoundary boundary =
markerKey.currentContext.findRenderObject();
var image = await boundary.toImage(pixelRatio: 2.0);
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData.buffer.asUint8List();
}
}
/// AfterLayoutMixin
mixin AfterLayoutMixin<T extends StatefulWidget> on State<T> {
@override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => afterFirstLayout(context));
}
void afterFirstLayout(BuildContext context);
}
@Jip1912
Copy link

Jip1912 commented Aug 13, 2021

MyCustomMarker

Hi thanks for the code, sorry for the late reply. I've tried the code and it works as you showed, but how do I use my own widget with this? I don't know how the custom painter and canvas works and I'm not sure if I can use my own widget with this as marker. This is currently my custom marker:

SizedBox(
  width: 100,
  height: 140,
  child: Stack(
    clipBehavior: Clip.none,
    children: [
      Center(
        child: Container(
          //Rcolor: Colors.red.withOpacity(0.5),
          width: 100,
          height: 100,
            child: CircleAvatar(
              radius: 50,
              backgroundImage: _image,
            ),
          decoration: new BoxDecoration(
            shape: BoxShape.circle,
            border: new Border.all(
              color: Colors.amber[400],
              width: 4.0,
            ),
          ),
        ),
      ),
      Positioned.fill(
        bottom: 0,
        child: Align(
          alignment: Alignment.bottomCenter,
          child: Container(
            decoration: new BoxDecoration(
              borderRadius: BorderRadius.circular(4.0),
              color: kPrimaryColor
            ),
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal:4.0),
              child: Text(
                "€${element['uurloon'].toString()}",
                style: TextStyle(color: Colors.white, fontSize: 18),
              ),
            )
          ),
        ),
      )
    ],
  ),
);

How do I use this with your code?

@AymanProjects
Copy link

Excellent work! Thank you

@DarkMikey
Copy link

DarkMikey commented Sep 27, 2021

Good work, but I couldn't get it to fully work with network image and assets. Somehow, when I initially opened the map, nothing would happen. After that everything worked fine.

After all I switched to the canvas solution as mentioned by @itsJoKr which works very well and efficient.

https://stackoverflow.com/a/58954691/5589379

@Rumanali786
Copy link

can anyone tell me how to use this class
please answer me
Thank you

@SamiAlsubhi
Copy link

This is a working simple example, customize for your needs:

import 'dart:async';
import 'dart:ui' as ui;

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

Future<BitmapDescriptor?> getMarkerIconFromCanvas(String text) async {
  final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
  final Canvas canvas = Canvas(pictureRecorder);
  final Paint paint1 = Paint()..color = Colors.grey;
  const int size = 100; //change this according to your app
  canvas.drawCircle(const Offset(size / 2, size / 2), size / 2.0, paint1);
  TextPainter painter = TextPainter(textDirection: TextDirection.ltr);
  painter.text = TextSpan(
    text: text, //you can write your own text here or take from parameter
    style: const TextStyle(
        fontSize: size / 4, color: Colors.black, fontWeight: FontWeight.bold),
  );
  painter.layout();
  painter.paint(
    canvas,
    Offset(size / 2 - painter.width / 2, size / 2 - painter.height / 2),
  );

  final img = await pictureRecorder.endRecording().toImage(size, size);
  final data = await img.toByteData(format: ui.ImageByteFormat.png);
  final imageData = data?.buffer.asUint8List();
  if (imageData != null) {
    return BitmapDescriptor.fromBytes(imageData);
  }
  return null;
}

@mehmetext
Copy link

I found a link that explains how to use this class: https://stackoverflow.com/questions/52591556/custom-markers-with-flutter-google-maps-plugin

And I updated with null-safety:

import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;

/// This just adds overlay and builds [_MarkerHelper] on that overlay.
/// [_MarkerHelper] does all the heavy work of creating and getting bitmaps
class MarkerGenerator {
  final Function(List<Uint8List>) callback;
  final List<Widget> markerWidgets;

  MarkerGenerator(this.markerWidgets, this.callback);

  void generate(BuildContext context) {
    WidgetsBinding.instance
        .addPostFrameCallback((_) => afterFirstLayout(context));
  }

  void afterFirstLayout(BuildContext context) {
    addOverlay(context);
  }

  void addOverlay(BuildContext context) {
    OverlayState overlayState = Overlay.of(context);

    late OverlayEntry entry;

    entry = OverlayEntry(
        builder: (context) {
          return _MarkerHelper(
            markerWidgets: markerWidgets,
            callback: (List<Uint8List> bitmapList) {
              callback.call(bitmapList);
              // Remove marker widgets from Overlay when finished
              entry.remove();
            },
          );
        },
        maintainState: true);

    overlayState.insert(entry);
  }
}

/// Maps are embeding GoogleMap library for Andorid/iOS  into flutter.
///
/// These native libraries accept BitmapDescriptor for marker, which means that for custom markers
/// you need to draw view to bitmap and then send that to BitmapDescriptor.
///
/// Because of that Flutter also cannot accept Widget for marker, but you need draw it to bitmap and
/// that's what this widget does:
///
/// 1) It draws marker widget to tree
/// 2) After painted access the repaint boundary with global key and converts it to uInt8List
/// 3) Returns set of Uint8List (bitmaps) through callback
class _MarkerHelper extends StatefulWidget {
  final List<Widget> markerWidgets;
  final Function(List<Uint8List>) callback;

  const _MarkerHelper({
    Key? key,
    required this.markerWidgets,
    required this.callback,
  }) : super(key: key);

  @override
  _MarkerHelperState createState() => _MarkerHelperState();
}

class _MarkerHelperState extends State<_MarkerHelper> with AfterLayoutMixin {
  List<GlobalKey> globalKeys = <GlobalKey>[];

  @override
  void afterFirstLayout(BuildContext context) {
    _getBitmaps(context).then((list) {
      widget.callback(list);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Transform.translate(
      offset: Offset(MediaQuery.of(context).size.width, 0),
      child: Material(
        type: MaterialType.transparency,
        child: Stack(
          children: widget.markerWidgets.map((i) {
            final markerKey = GlobalKey();
            globalKeys.add(markerKey);
            return RepaintBoundary(
              key: markerKey,
              child: i,
            );
          }).toList(),
        ),
      ),
    );
  }

  Future<List<Uint8List>> _getBitmaps(BuildContext context) async {
    var futures = globalKeys.map((key) => _getUint8List(key));
    return Future.wait(futures);
  }

  Future<Uint8List> _getUint8List(GlobalKey markerKey) async {
    RenderRepaintBoundary boundary =
        (markerKey.currentContext!.findRenderObject() as RenderRepaintBoundary);
    var image = await boundary.toImage(pixelRatio: 2.0);
    ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    return byteData!.buffer.asUint8List();
  }
}

/// AfterLayoutMixin
mixin AfterLayoutMixin<T extends StatefulWidget> on State<T> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => afterFirstLayout(context));
  }

  void afterFirstLayout(BuildContext context);
}

@longdw
Copy link

longdw commented Oct 13, 2023

@mehmetext I use your code above, when I pass a Image.asset("xxx") to markerWidgets, it can't display the image, but can display Text

      Container(
        color: Colors.green,
        width: 100,
        height: 100,
        child: Row(
          children: [
            Image.asset("images/logo.png", width: 50, height: 50,),
            spaceH(6),
            Text(
              "rain",
              style: const TextStyle(
                  color: Colors.white,
                  fontSize: 16
              ),
            )
          ],
        ),
      )
    ], (List<Uint8List> data) {
             Call the map control here
    }).generate(context);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment