Trying to Build custom marker with user imageURL and their name for google map in flutter. like below image but i'm less familiar with custom paint. I just know to simple shapes. I have followed this to build like given image.
Code :
Future<BitmapDescriptor> getMarkerImage(NearbyUsers user) async {
if (user.image != null) {
final File markerImageFile =
await DefaultCacheManager().getSingleFile(user.image);
Size s = user.userId == profileData.userId.toString()
? Size(150, 150)
: Size(120, 120);
var icon = await getMarkerIcon(markerImageFile.path, s, user);
return icon;
} else {
return BitmapDescriptor.defaultMarker;
}
}
Future<BitmapDescriptor> getMarkerIcon(
String imagePath, Size size, NearbyUsers user) async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
final Radius radius = Radius.circular(size.width / 2);
final Paint nameTagPaint = Paint()..color = MyTheme.primaryColor;
final double nameTagWidth = 40.0;
final Paint shadowPaint = Paint()
..color = MyTheme.primaryColor.withAlpha(120);
final double shadowWidth = 15.0;
final Paint borderPaint = Paint()..color = Colors.white;
final double borderWidth = 3.0;
final double imageOffset = shadowWidth + borderWidth;
// Add shadow circle
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(0.0, 0.0, size.width, size.height),
topLeft: radius,
topRight: radius,
bottomLeft: radius,
bottomRight: radius,
),
shadowPaint);
// Add border circle
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(shadowWidth, shadowWidth,
size.width - (shadowWidth * 2), size.height - (shadowWidth * 2)),
topLeft: radius,
topRight: radius,
bottomLeft: radius,
bottomRight: radius,
),
borderPaint);
// Name tag BG
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromCircle(center: Offset(nameTagWidth/2,nameTagWidth/2), radius: size.height *0.4 ),
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
),
nameTagPaint);
// Add Name text
TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
textPainter.text = TextSpan(
text: '${user.fname} ${user.lname}',
style: TextStyle(fontSize: 20.0, color: Colors.white),
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(1.0, 1.0),
// Offset(size.width - nameTagWidth / 2 - textPainter.width / 2,
// nameTagWidth / 2 - textPainter.height / 2)
);
// Oval for the image
Rect oval = Rect.fromLTWH(imageOffset, imageOffset,
size.width - (imageOffset * 2), size.height - (imageOffset * 2));
// Add path for oval image
canvas.clipPath(Path()..addOval(oval));
// Add image
ui.Image image = await getImageFromPath(
imagePath); // Alternatively use your own method to get the image
paintImage(canvas: canvas, image: image, rect: oval, fit: BoxFit.fitWidth);
// Convert canvas to image
final ui.Image markerAsImage = await pictureRecorder
.endRecording()
.toImage(size.width.toInt(), size.height.toInt());
// Convert image to bytes
final ByteData byteData =
await markerAsImage.toByteData(format: ui.ImageByteFormat.png);
final Uint8List uint8List = byteData.buffer.asUint8List();
return BitmapDescriptor.fromBytes(uint8List);
}
Future<ui.Image> getImageFromPath(String imagePath) async {
File imageFile = File(imagePath);
Uint8List imageBytes = imageFile.readAsBytesSync();
final Completer<ui.Image> completer = new Completer();
ui.decodeImageFromList(imageBytes, (ui.Image img) {
return completer.complete(img);
});
return completer.future;
}
Output:
After long research now I am able to understand the a lil bit of flutter custom painter, Painter or canvas whatever we call it.
but I'm still dumb for set offset to image of shadow circle and not able to give them dynamic offset according to giving size. for now just gave static offset. Please ignore this for now.
FULL CODE:
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:upstanders/global/theme/colors.dart';
class MapSample extends StatefulWidget {
@override
_MapSampleState createState() => _MapSampleState();
}
class _MapSampleState extends State<MapSample> {
BitmapDescriptor customIcon;
Set<Marker> markers = {};
Future<BitmapDescriptor> createCustomMarkerBitmapWithNameAndImage(
String imagePath, Size size, String name) async {
TextSpan span = new TextSpan(
style: new TextStyle(
height: 1.2,
color: Colors.black,
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
text: name);
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
tp.layout();
ui.PictureRecorder recorder = new ui.PictureRecorder();
Canvas canvas = new Canvas(recorder);
final double shadowWidth = 15.0;
final double borderWidth = 3.0;
final double imageOffset = shadowWidth + borderWidth;
final Radius radius = Radius.circular(size.width / 2);
final Paint shadowCirclePaint = Paint()
..color = MyTheme.primaryColor.withAlpha(180);
// Add shadow circle
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(
size.width / 8, size.width / 2, size.width, size.height),
topLeft: radius,
topRight: radius,
bottomLeft: radius,
bottomRight: radius,
),
shadowCirclePaint);
// TEXT BOX BACKGROUND
Paint textBgBoxPaint = Paint()..color = MyTheme.primaryColor;
Rect rect = Rect.fromLTWH(
0,
0,
tp.width + 35,
50,
);
canvas.drawRRect(
RRect.fromRectAndRadius(rect, Radius.circular(10.0)),
textBgBoxPaint,
);
//ADD TEXT WITH ALIGN TO CANVAS
tp.paint(canvas, new Offset(20.0, 5.0));
/* Do your painting of the custom icon here, including drawing text, shapes, etc. */
Rect oval = Rect.fromLTWH(35, 78, size.width - (imageOffset * 2),
size.height - (imageOffset * 2));
// ADD PATH TO OVAL IMAGE
canvas.clipPath(Path()..addOval(oval));
ui.Image image = await getImageFromPath(
imagePath);
paintImage(canvas: canvas, image: image, rect: oval, fit: BoxFit.fitWidth);
ui.Picture p = recorder.endRecording();
ByteData pngBytes = await (await p.toImage(300, 300))
.toByteData(format: ui.ImageByteFormat.png);
Uint8List data = Uint8List.view(pngBytes.buffer);
return BitmapDescriptor.fromBytes(data);
}
Future<ui.Image> getImageFromPath(String imagePath) async {
File imageFile = File(imagePath);
Uint8List imageBytes = imageFile.readAsBytesSync();
final Completer<ui.Image> completer = new Completer();
ui.decodeImageFromList(imageBytes, (ui.Image img) {
return completer.complete(img);
});
return completer.future;
}
Future<BitmapDescriptor> getMarkerIcon(String image, String name) async {
if (image != null) {
final File markerImageFile =
await DefaultCacheManager().getSingleFile(image);
Size s = Size(120, 120);
var icon = await createCustomMarkerBitmapWithNameAndImage(markerImageFile.path, s, name);
return icon;
} else {
return BitmapDescriptor.defaultMarker;
}
}
@override
void initState() {
super.initState();
setAllMarkers();
}
setAllMarkers() async {
markers.add(
Marker(
markerId: MarkerId("1"),
position: LatLng(30.699285146824476, 76.69179040341325),
icon: await getMarkerIcon(
"https://quizprojectapp.s3.us-east-2.amazonaws.com/quizImage_1629380529687.png",
"DAVID"),
),
);
markers.add(
Marker(
markerId: MarkerId("2"),
position: LatLng(30.70246327295858, 76.69501482303754),
icon: await getMarkerIcon(
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTcA3AmOb_BMAsPra4XquXuWFMNAi7grJL0ug&usqp=CAU",
"ROSE"),
),
);
markers.add(
Marker(
markerId: MarkerId("3"),
position: LatLng(30.704382049382055, 76.70102297115308),
icon: await getMarkerIcon(
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSuIavvjuQFB38Se2ZNa0GkZ1Gol3C5OwioHA&usqp=CAU",
"JASSABELLE"),
),
);
setState(() {});
}
@override
build(BuildContext context) {
return Scaffold(
body: GoogleMap(
initialCameraPosition:
CameraPosition(target: const LatLng(30.699285146824476, 76.69179040341325), zoom: 15),
markers: markers),
);
}
}
RESULT: