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
.
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.
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.
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.
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.
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, ), ), ); } }
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; }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With