我正在使用Flutter开发Google Map Markers。
单击每个标记,我想显示一个自定义信息窗口,其中可以包含按钮,图像等。但是在Flutter中,有一个属性TextInfoWindow
仅接受String
。
如何实现将按钮和图像添加到地图标记的InfoWindow
。
答案 0 :(得分:4)
我今天偶然发现了一个相同的问题,我无法在TextInfoWindow中正确显示多行字符串。最后,我实现了一个模态底部工作表(https://docs.flutter.io/flutter/material/showModalBottomSheet.html)来规避该问题,该工作表会在您单击标记时显示,在我的情况下效果很好。
我也可以想象出许多用例,其中您想完全自定义标记的信息窗口,但是在GitHub(https://github.com/flutter/flutter/issues/23938)上阅读此问题似乎是不可能的,因为InfoWindow不是Flutter小部件。
答案 1 :(得分:2)
您可以将由小部件组成的标记显示为自定义“信息窗口”。基本上,您是在创建小部件的png图像并将其显示为标记。
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
class MarkerInfo extends StatefulWidget {
final Function getBitmapImage;
final String text;
MarkerInfo({Key key, this.getBitmapImage, this.text}) : super(key: key);
@override
_MarkerInfoState createState() => _MarkerInfoState();
}
class _MarkerInfoState extends State<MarkerInfo> {
final markerKey = GlobalKey();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => getUint8List(markerKey)
.then((markerBitmap) => widget.getBitmapImage(markerBitmap)));
}
Future<Uint8List> getUint8List(GlobalKey markerKey) async {
RenderRepaintBoundary boundary =
markerKey.currentContext.findRenderObject();
var image = await boundary.toImage(pixelRatio: 2.0);
ByteData byteData = await image.toByteData(format: ImageByteFormat.png);
return byteData.buffer.asUint8List();
}
@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: markerKey,
child: Container(
padding: EdgeInsets.only(bottom: 29),
child: Container(
width: 100,
height: 100,
color: Color(0xFF000000),
child: Text(
widget.text,
style: TextStyle(
color: Color(0xFFFFFFFF),
),
),
),
),
);
}
}
如果使用这种方法,则必须确保呈现窗口小部件,否则将无法正常工作。要将小部件转换为图像-必须渲染小部件才能进行转换。我将小部件隐藏在Stack中的地图下。
return Stack(
children: <Widget>[
MarkerInfo(
text: tripMinutes.toString(),
getBitmapImage: (img) {
customMarkerInfo = img;
}),
GoogleMap(
markers: markers,
...
最后一步是创建标记。从窗口小部件传递的数据保存在customMarkerInfo中-字节,因此请将其转换为Bitmap。
markers.add(
Marker(
position: position,
icon: BitmapDescriptor.fromBytes(customMarkerInfo),
markerId: MarkerId('MarkerID'),
),
);
答案 2 :(得分:1)
下面是我为项目中的自定义InfoWindow实施的4个步骤
第1步:为GoogleMap和“信息窗口自定义”创建一个堆栈。
Stack(
children: <Widget>[
Positioned.fill(child: GoogleMap(...),),
Positioned(
top: {offsetY},
left: {offsetX},
child: YourCustomInfoWidget(...),
)
]
)
第2步:当用户单击带有功能的屏幕上的标记计算器位置时:
screenCoordinate = await _mapController.getScreenCoordinate(currentPosition.target)
步骤3:计算器offsetY,offsetX和setState。
相关问题:https://github.com/flutter/flutter/issues/41653
devicePixelRatio = Platform.isAndroid ? MediaQuery.of(context).devicePixelRatio : 1.0;
offsetY = (screenCoordinate?.y?.toDouble() ?? 0) / devicePixelRatio - infoWidget.size.width;
offsetX = (screenCoordinate?.x?.toDouble() ?? 0) / devicePixelRatio - infoWidget.size.height;
第4步:点击时禁用标记自动移动相机
Marker(
...
consumeTapEvents: true,)
答案 3 :(得分:1)
要创建基于窗口小部件的信息窗口,您需要将窗口小部件堆叠在Google地图上。借助ChangeNotifierProvider
,ChangeNotifier
和Consumer
,即使相机在Google地图上移动,您也可以轻松地重建小部件。
InfoWindowModel类:
class InfoWindowModel extends ChangeNotifier {
bool _showInfoWindow = false;
bool _tempHidden = false;
User _user;
double _leftMargin;
double _topMargin;
void rebuildInfoWindow() {
notifyListeners();
}
void updateUser(User user) {
_user = user;
}
void updateVisibility(bool visibility) {
_showInfoWindow = visibility;
}
void updateInfoWindow(
BuildContext context,
GoogleMapController controller,
LatLng location,
double infoWindowWidth,
double markerOffset,
) async {
ScreenCoordinate screenCoordinate =
await controller.getScreenCoordinate(location);
double devicePixelRatio =
Platform.isAndroid ? MediaQuery.of(context).devicePixelRatio : 1.0;
double left = (screenCoordinate.x.toDouble() / devicePixelRatio) -
(infoWindowWidth / 2);
double top =
(screenCoordinate.y.toDouble() / devicePixelRatio) - markerOffset;
if (left < 0 || top < 0) {
_tempHidden = true;
} else {
_tempHidden = false;
_leftMargin = left;
_topMargin = top;
}
}
bool get showInfoWindow =>
(_showInfoWindow == true && _tempHidden == false) ? true : false;
double get leftMargin => _leftMargin;
double get topMargin => _topMargin;
User get user => _user;
}
完整示例可以在我的blog上找到!
答案 4 :(得分:0)
偶然发现了这个问题,找到了适合我的解决方案:
要解决此问题,我确实写了Custom Info Widget,请随时对其进行自定义。
首先,我确实有一个由一个位置和一个小部件组成的对象点。 而且我确实计算了taxicapDistance。
Marker(
markerId: MarkerId(point.location.latitude.toString() + point.location.longitude.toString()),
position: point.location,
onTap: () => _onTap(point),
);
double _taxicabDistance(LatLng p1, LatLng p2) =>
(p1.latitude - p2.latitude).abs() + (p1.longitude - p2.longitude).abs();
现在是我的_onTap方法。首先,它将“摄像机”动画化到标记的位置,然后导航器推送“信息”小部件的弹出路径。
需要Future延迟,因为移动动画需要一些时间。
_onTap(PointObject point) async {
final RenderBox renderBox = context.findRenderObject();
Rect _itemRect = renderBox.localToGlobal(Offset.zero) & renderBox.size;
InfoWidgetRoute _infoWidgetRoute = InfoWidgetRoute(
child: point.child,
buildContext: context,
textStyle: const TextStyle(
fontSize: 14,
color: Colors.black,
),
mapsWidgetSize: _itemRect,
);
if (_taxicabDistance(_cameraPosition.target, point.location) > 0.0001) {
await mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
point.location.latitude,
point.location.longitude,
),
zoom: 15,
),
),
);
Future.delayed(
Platform.isIOS
? Duration(milliseconds: 500)
: Duration(milliseconds: 1250),
() {
Navigator.of(context, rootNavigator: true)
.push(_infoWidgetRoute)
.then<void>(
(newValue) {
_infoWidgetRoute = null;
},
);
},
);
} else {
Navigator.of(context, rootNavigator: true)
.push(_infoWidgetRoute)
.then<void>(
(newValue) {
_infoWidgetRoute = null;
},
);
}
}
答案 5 :(得分:0)
这是一种创建不依赖于InfoWindow的自定义标记的解决方案。 尽管如此,该方法不允许您在自定义标记上添加按钮 。
Flutter谷歌地图插件可让我们使用图像数据/资产来创建自定义标记。因此,此方法使用在Canvas上绘制以创建自定义标记,并使用PictureRecorder将其转换为图片,随后Google地图插件将使用该图片来呈现自定义标记。>
在Canvas上绘制示例代码并将其转换为可由插件使用的Image数据。
void paintTappedImage() async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder, Rect.fromPoints(const Offset(0.0, 0.0), const Offset(200.0, 200.0)));
final Paint paint = Paint()
..color = Colors.black.withOpacity(1)
..style = PaintingStyle.fill;
canvas.drawRRect(
RRect.fromRectAndRadius(
const Rect.fromLTWH(0.0, 0.0, 152.0, 48.0), const Radius.circular(4.0)),
paint);
paintText(canvas);
paintImage(labelIcon, const Rect.fromLTWH(8, 8, 32.0, 32.0), canvas, paint,
BoxFit.contain);
paintImage(markerImage, const Rect.fromLTWH(24.0, 48.0, 110.0, 110.0), canvas,
paint, BoxFit.contain);
final Picture picture = recorder.endRecording();
final img = await picture.toImage(200, 200);
final pngByteData = await img.toByteData(format: ImageByteFormat.png);
setState(() {
_customMarkerIcon = BitmapDescriptor.fromBytes(Uint8List.view(pngByteData.buffer));
});
}
void paintText(Canvas canvas) {
final textStyle = TextStyle(
color: Colors.white,
fontSize: 24,
);
final textSpan = TextSpan(
text: '18 mins',
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
minWidth: 0,
maxWidth: 88,
);
final offset = Offset(48, 8);
textPainter.paint(canvas, offset);
}
void paintImage(
ui.Image image, Rect outputRect, Canvas canvas, Paint paint, BoxFit fit) {
final Size imageSize =
Size(image.width.toDouble(), image.height.toDouble());
final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size);
final Rect inputSubrect =
Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubrect =
Alignment.center.inscribe(sizes.destination, outputRect);
canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
}
点击标记后,我们可以用Canvas生成的新图像替换点击的图像。从Google Maps插件示例应用中获取的示例代码相同。
void _onMarkerTapped(MarkerId markerId) async {
final Marker tappedMarker = markers[markerId];
if (tappedMarker != null) {
if (markers.containsKey(selectedMarker)) {
final Marker resetOld =
markers[selectedMarker].copyWith(iconParam: _markerIconUntapped);
setState(() {
markers[selectedMarker] = resetOld;
});
}
Marker newMarker;
selectedMarker = markerId;
newMarker = tappedMarker.copyWith(iconParam: _customMarkerIcon);
setState(() {
markers[markerId] = newMarker;
});
tappedCount++;
}
}
参考: