Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter custom Google Map marker info window

Tags:

I am working on Google Map Markers in Flutter.

On the click of each Marker, I want to show a Custom Info Window which can include a button, image etc. But in Flutter there is a property TextInfoWindow which only accept String.

How can i achieve adding buttons, images to the map marker's InfoWindow.

like image 787
shalini Avatar asked Jan 09 '19 06:01

shalini


People also ask

How do I add custom info to Windows flutter?

Please try custom_info_window. It is a package that allows widget based custom info window for google_maps_flutter. By using this, you will be able to move your widget like info window on the top of the map's marker.

How do you get all details of address when I am put mark on Google map and then it will fill up in a text box in flutter?

like I've mentioned in this images when I put a marker and then click on show address it will take that address and filled into textboxes. Hope you understand the question.

How do I change the marker icon on Google Maps in flutter?

import 'dart:ui' as ui; import 'dart:typed_data'; II. Create function for changing the custom image (you will like to use as marker icon )to the byte array.


2 Answers

Stumbled across this problem and found a solution which works for me:

To solve it I did write a Custom Info Widget, feel free to customize it. For example with some shadow via ClipShadowPath.

Implementation

import 'dart:async';  import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';  import 'custom_info_widget.dart';  void main() => runApp(MyApp());  class PointObject {   final Widget child;   final LatLng location;    PointObject({this.child, this.location}); }  class MyApp extends StatelessWidget {   // This widget is the root of your application.   @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'Flutter Demo',       theme: ThemeData(         primarySwatch: Colors.blue,       ),       initialRoute: "/",       routes: {         "/": (context) => HomePage(),       },     );   } }  class HomePage extends StatefulWidget {   @override   _HomePageState createState() => _HomePageState(); }  class _HomePageState extends State<HomePage> {   PointObject point = PointObject(     child:  Text('Lorem Ipsum'),     location: LatLng(47.6, 8.8796),   );    StreamSubscription _mapIdleSubscription;   InfoWidgetRoute _infoWidgetRoute;   GoogleMapController _mapController;    @override   Widget build(BuildContext context) {     return Scaffold(       body: Container(         color: Colors.green,         child: GoogleMap(           initialCameraPosition: CameraPosition(             target: const LatLng(47.6, 8.6796),             zoom: 10,           ),           circles: Set<Circle>()             ..add(Circle(               circleId: CircleId('hi2'),               center: LatLng(47.6, 8.8796),               radius: 50,               strokeWidth: 10,               strokeColor: Colors.black,             )),           markers: Set<Marker>()             ..add(Marker(               markerId: MarkerId(point.location.latitude.toString() +                   point.location.longitude.toString()),               position: point.location,               onTap: () => _onTap(point),             )),           onMapCreated: (mapController) {             _mapController = mapController;           },            /// This fakes the onMapIdle, as the googleMaps on Map Idle does not always work           /// (see: https://github.com/flutter/flutter/issues/37682)           /// When the Map Idles and a _infoWidgetRoute exists, it gets displayed.           onCameraMove: (newPosition) {             _mapIdleSubscription?.cancel();             _mapIdleSubscription = Future.delayed(Duration(milliseconds: 150))                 .asStream()                 .listen((_) {               if (_infoWidgetRoute != null) {                 Navigator.of(context, rootNavigator: true)                     .push(_infoWidgetRoute)                     .then<void>(                   (newValue) {                     _infoWidgetRoute = null;                   },                 );               }             });           },         ),       ),     );   }  /// now my _onTap Method. First it creates the Info Widget Route and then   /// animates the Camera twice:   /// First to a place near the marker, then to the marker.   /// This is done to ensure that onCameraMove is always called     _onTap(PointObject point) async {     final RenderBox renderBox = context.findRenderObject();     Rect _itemRect = renderBox.localToGlobal(Offset.zero) & renderBox.size;      _infoWidgetRoute = InfoWidgetRoute(       child: point.child,       buildContext: context,       textStyle: const TextStyle(         fontSize: 14,         color: Colors.black,       ),       mapsWidgetSize: _itemRect,     );      await _mapController.animateCamera(       CameraUpdate.newCameraPosition(         CameraPosition(           target: LatLng(             point.location.latitude - 0.0001,             point.location.longitude,           ),           zoom: 15,         ),       ),     );     await _mapController.animateCamera(       CameraUpdate.newCameraPosition(         CameraPosition(           target: LatLng(             point.location.latitude,             point.location.longitude,           ),           zoom: 15,         ),       ),     );   } }  

CustomInfoWidget:

import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:meta/meta.dart';  class _InfoWidgetRouteLayout<T> extends SingleChildLayoutDelegate {   final Rect mapsWidgetSize;   final double width;   final double height;    _InfoWidgetRouteLayout(       {@required this.mapsWidgetSize,       @required this.height,       @required this.width});    /// Depending of the size of the marker or the widget, the offset in y direction has to be adjusted;   /// If the appear to be of different size, the commented code can be uncommented and   /// adjusted to get the right position of the Widget.   /// Or better: Adjust the marker size based on the device pixel ratio!!!!)    @override   Offset getPositionForChild(Size size, Size childSize) { //    if (Platform.isIOS) {     return Offset(       mapsWidgetSize.center.dx - childSize.width / 2,       mapsWidgetSize.center.dy - childSize.height - 50,     ); //    } else { //      return Offset( //        mapsWidgetSize.center.dx - childSize.width / 2, //        mapsWidgetSize.center.dy - childSize.height - 10, //      ); //    }   }    @override   BoxConstraints getConstraintsForChild(BoxConstraints constraints) {     //we expand the layout to our predefined sizes     return BoxConstraints.expand(width: width, height: height);   }    @override   bool shouldRelayout(_InfoWidgetRouteLayout oldDelegate) {     return mapsWidgetSize != oldDelegate.mapsWidgetSize;   } }  class InfoWidgetRoute extends PopupRoute {   final Widget child;   final double width;   final double height;   final BuildContext buildContext;   final TextStyle textStyle;   final Rect mapsWidgetSize;    InfoWidgetRoute({     @required this.child,     @required this.buildContext,     @required this.textStyle,     @required this.mapsWidgetSize,     this.width = 150,     this.height = 50,     this.barrierLabel,   });    @override   Duration get transitionDuration => Duration(milliseconds: 100);    @override   bool get barrierDismissible => true;    @override   Color get barrierColor => null;    @override   final String barrierLabel;    @override   Widget buildPage(BuildContext context, Animation<double> animation,       Animation<double> secondaryAnimation) {     return MediaQuery.removePadding(       context: context,       removeBottom: true,       removeLeft: true,       removeRight: true,       removeTop: true,       child: Builder(builder: (BuildContext context) {         return CustomSingleChildLayout(           delegate: _InfoWidgetRouteLayout(               mapsWidgetSize: mapsWidgetSize, width: width, height: height),           child: InfoWidgetPopUp(             infoWidgetRoute: this,           ),         );       }),     );   } }  class InfoWidgetPopUp extends StatefulWidget {   const InfoWidgetPopUp({     Key key,     @required this.infoWidgetRoute,   })  : assert(infoWidgetRoute != null),         super(key: key);    final InfoWidgetRoute infoWidgetRoute;    @override   _InfoWidgetPopUpState createState() => _InfoWidgetPopUpState(); }  class _InfoWidgetPopUpState extends State<InfoWidgetPopUp> {   CurvedAnimation _fadeOpacity;    @override   void initState() {     super.initState();     _fadeOpacity = CurvedAnimation(       parent: widget.infoWidgetRoute.animation,       curve: Curves.easeIn,       reverseCurve: Curves.easeOut,     );   }    @override   Widget build(BuildContext context) {     return FadeTransition(       opacity: _fadeOpacity,       child: Material(         type: MaterialType.transparency,         textStyle: widget.infoWidgetRoute.textStyle,         child: ClipPath(           clipper: _InfoWidgetClipper(),           child: Container(             color: Colors.white,             padding: EdgeInsets.only(bottom: 10),             child: Center(child: widget.infoWidgetRoute.child),           ),         ),       ),     );   } }  class _InfoWidgetClipper extends CustomClipper<Path> {   @override   Path getClip(Size size) {     Path path = Path();     path.lineTo(0.0, size.height - 20);     path.quadraticBezierTo(0.0, size.height - 10, 10.0, size.height - 10);     path.lineTo(size.width / 2 - 10, size.height - 10);     path.lineTo(size.width / 2, size.height);     path.lineTo(size.width / 2 + 10, size.height - 10);     path.lineTo(size.width - 10, size.height - 10);     path.quadraticBezierTo(         size.width, size.height - 10, size.width, size.height - 20);     path.lineTo(size.width, 10.0);     path.quadraticBezierTo(size.width, 0.0, size.width - 10.0, 0.0);     path.lineTo(10, 0.0);     path.quadraticBezierTo(0.0, 0.0, 0.0, 10);     path.close();     return path;   }    @override   bool shouldReclip(CustomClipper<Path> oldClipper) => false; }  
like image 155
Markus Hein Avatar answered Nov 10 '22 00:11

Markus Hein


I stumbled across the same problem just today, I couldn't get a multiline string to show properly in TextInfoWindow. I ended up circumventing the problem by implementing a modal bottom sheet (https://docs.flutter.io/flutter/material/showModalBottomSheet.html) that shows when you click on a marker, which in my case worked out quite nicely.

I can also imagine many use cases where you'd want to fully customize the marker's info window, but reading this issue on GitHub (https://github.com/flutter/flutter/issues/23938) it looks like it's currently not possible, because the InfoWindow is not a Flutter widget.

like image 42
Maetthu24 Avatar answered Nov 09 '22 23:11

Maetthu24