Widget annotation
How can I add a widget annotation on map
Here's how I did it, hope it helps you:
Widget -> Capture Widget -> Unit8list -> draw to mapbox
hello @vanvixi,
I have tried that and it works fine but I want to use flutter widget because I want to add 3d model as my annotation.
is that possible ..? thanks
I can achieve that using flutter_map but that plugin does not support terrain view as this plugin and I want to have the terrain view and the 3d model on the maps
@fodedoumbouya Any update on this? Did you figure out how?
yes and I use PointAnnotationOptions like this: PointAnnotationOptions( geometry: Point(coordinates: Position(position.longitude, position.latitude)), textOffset: [position.latitude, position.longitude], symbolSortKey: 10, image: image, //Uint8List iconSize: 0.5, );
I am displaying markers similar to you But depending on the zoom level to display markers again. markers when zoomed in will show images, text up showing far away will only show icons
when I zoom in it captures the widget -> causing lag, do you have any way to optimize
For my part, I added a small timer so that the icons are repainted only once after the user finishes zooming in and moving the map. Without the timer, the icons would be repainted multiple times during map movement.
Code Implementation:
void move(CameraChangedEventData? camera) {
// Cancel the previous timer if it exists
moveEndTimer?.cancel();
// Set a new timer to delay the repainting
moveEndTimer = Timer(
const Duration(milliseconds: 250),
() async {
// Get the current camera state
final camera = await mapController.getCameraState();
final center = camera.center;
final zoom = camera.zoom;
// Add your repainting logic here
// For example: repaintIcons(center, zoom);
},
);
}
@fodedoumbouya I tried but it's still not very smooth, when zoom stops it still jerks when the widget is captured
I have a question. do you draw all of your icons on the maps or you draw only the ones that are visible ..?
I show all. But my marker data is not much. I am testing only about 10 markers but still lag
yes 10 markers should not make it lag can I see how you draw it ..?
**i am code like this. first i will create widgets to hold the UIs i want to capture then i capture all and display
Maybe my code is not very optimized, please help me check it. pls**
@override
Widget build(BuildContext context) {
return Positioned(
top: -200,
left: -200,
child: Opacity(
opacity: 1, // Ẩn widget nhưng vẫn render
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// === MARKER ĐƠN GIẢN (Dùng khi zoom < 15) ===
// Marker Livestream đơn giản
RepaintBoundary(
key: _simpleLivestreamKey,
child: CustomMarkerWidget(
boundaryKey: GlobalKey(),
type: 'livestream',
rating: 4.5,
),
),
const SizedBox(height: 20),
// Marker Post đơn giản
RepaintBoundary(
key: _simplePostKey,
child: CustomMarkerWidget(
boundaryKey: GlobalKey(),
type: 'post',
rating: 5.0,
),
),
const SizedBox(height: 20),
// Marker Image đơn giản
RepaintBoundary(
key: _simpleImageKey,
child: CustomMarkerWidget(
boundaryKey: GlobalKey(),
type: 'image',
rating: 5.0,
),
),
const SizedBox(height: 20),
// === MARKER CHI TIẾT (Dùng khi zoom >= 15) ===
// Marker Livestream chi tiết
RepaintBoundary(
key: _detailLivestreamKey,
child: PhotoMarkerWidget(
boundaryKey: GlobalKey(),
rating: '4.5',
type: 'hot',
imageUrl:
'https://images.unsplash.com/photo-1747134392291-33541db5f30f?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
),
),
const SizedBox(height: 20),
// Marker Post chi tiết
RepaintBoundary(
key: _detailPostKey,
child: PhotoMarkerWidget(
boundaryKey: GlobalKey(),
rating: '5.0',
type: 'standard',
imageUrl:
'https://images.unsplash.com/photo-1728044849221-851cf8587fac?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
),
),
const SizedBox(height: 20),
// Marker Image chi tiết
RepaintBoundary(
key: _detailImageKey,
child: PhotoMarkerWidget(
boundaryKey: GlobalKey(),
rating: '5.0',
type: 'standard',
imageUrl:
'https://images.unsplash.com/photo-1726064855870-9a438a9517bf?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
),
),
],
),
),
);
}
}
Future<void> _captureAllMarkers() async {
Map<String, Uint8List?> simpleMarkerImages = {};
Map<String, Uint8List?> detailMarkerImages = {};
print('Đang chụp marker đơn giản...');
// Đợi một chút để đảm bảo widget đã render
await Future.delayed(const Duration(milliseconds: 200));
// Chụp marker đơn giản cho livestream
Uint8List? simpleLivestreamMarker =
await _captureMarker(_simpleLivestreamKey);
if (simpleLivestreamMarker != null) {
simpleMarkerImages['livestream'] = simpleLivestreamMarker;
}
// Chụp marker đơn giản cho post
Uint8List? simplePostMarker = await _captureMarker(_simplePostKey);
if (simplePostMarker != null) {
simpleMarkerImages['post'] = simplePostMarker;
}
// Chụp marker đơn giản cho image
Uint8List? simpleImageMarker = await _captureMarker(_simpleImageKey);
if (simpleImageMarker != null) {
simpleMarkerImages['image'] = simpleImageMarker;
}
// Chụp marker chi tiết cho livestream
Uint8List? detailLivestreamMarker =
await _captureMarker(_detailLivestreamKey);
if (detailLivestreamMarker != null) {
detailMarkerImages['livestream'] = detailLivestreamMarker;
}
// Chụp marker chi tiết cho post
Uint8List? detailPostMarker = await _captureMarker(_detailPostKey);
if (detailPostMarker != null) {
detailMarkerImages['post'] = detailPostMarker;
}
// Chụp marker chi tiết cho image
Uint8List? detailImageMarker = await _captureMarker(_detailImageKey);
if (detailImageMarker != null) {
detailMarkerImages['image'] = detailImageMarker;
}
// Gửi kết quả qua callback
widget.onMarkersGenerated(simpleMarkerImages, detailMarkerImages);
}
Future<Uint8List?> _captureMarker(GlobalKey key) async {
try {
RenderRepaintBoundary? boundary = key.currentContext?.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) {
print('Boundary là null');
return null;
}
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
} catch (e) {
print('Lỗi khi chụp marker: $e');
return null;
}
}
Hey @JasonEastar,
Thanks for sharing your code! This confirms that the lag is very likely coming from the RenderRepaintBoundary.toImage() calls you're making every time the zoom level changes to switch marker appearances. Capturing Flutter widgets to Uint8List is an expensive operation.
The most effective optimization is to pre-capture all your necessary marker image variations (simple and detailed for each type) one time when your map or data loads.
Here's the core idea:
-
Use your _captureAllMarkers logic, but call it only once at the start.
-
Store the resulting Uint8List images.
-
Use the Mapbox SDK's addImage method to add all these captured images to the map's style, giving each a unique ID (e.g., 'simple_livestream_icon', 'detail_post_widget').
-
When you add your PointAnnotations, initially set their image property to the ID of the appropriate starting image (e.g., the simple icon ID).
-
In your move (or onCameraChanged) callback (with the timer), when the zoom threshold is crossed, find the relevant annotations and update their image property to the new required image ID (e.g., change from 'simple_livestream_icon' to 'detail_livestream_widget').
Benefit: Updating an annotation's image property using an image ID already in the style is extremely fast, as it's handled efficiently by the native Mapbox renderer. This avoids the repetitive, slow widget capture during map interaction.
This moves the heavy work (widget capture) upfront, leading to smooth zoom-based marker transitions.
Great, thanks for the suggestion!
I am currently creating simple_livestream_icon with an available image, then using canvas to draw text on that image -> Store the resulting Uint8List image. The simple_livestream_icon part I have optimized to create as few images as possible. Which do you think is more optimal, using canvas or using widget?
For the detail_post_widget part: in case my detail_post_widget markers will be flexible with images and text in the widget. now the widget markers will be different 100 markers then there will be 100 different markers (containing images and text)... will taking a photo of all of them cause memory overflow... and is there a way to handle it more optimally.
Thank you very much.