Build custom marker with user imageURL and their name for google map in flutter

2.1k Views Asked by At

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.

enter image description here

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:

enter image description here

1

There are 1 best solutions below

0
On BEST ANSWER

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:

enter image description here