Skip to content

Instantly share code, notes, and snippets.

@itsJoKr
Last active July 24, 2024 11:23
Show Gist options
  • 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);
}
@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