Flutter 颤振自定义谷歌地图标记信息窗口
我正在为Flutter中的谷歌地图标记工作 单击每个标记后,我想显示一个自定义信息窗口,该窗口可以包括按钮、图像等。但是在Flatter中有一个属性Flutter 颤振自定义谷歌地图标记信息窗口,flutter,flutter-layout,Flutter,Flutter Layout,我正在为Flutter中的谷歌地图标记工作 单击每个标记后,我想显示一个自定义信息窗口,该窗口可以包括按钮、图像等。但是在Flatter中有一个属性TextInfoWindow,它只接受字符串 如何将按钮、图像添加到地图标记的InfoWindow我今天偶然发现了同样的问题,我无法在TextInfoWindow中正确显示多行字符串。最后,我实现了一个模式底部表单(),当你点击一个标记时它会显示出来,这在我的例子中非常好地解决了这个问题 我还可以想象,在许多用例中,您希望完全定制标记器的信息窗口,但
TextInfoWindow
,它只接受字符串
如何将按钮、图像添加到地图标记的InfoWindow
我今天偶然发现了同样的问题,我无法在TextInfoWindow中正确显示多行字符串。最后,我实现了一个模式底部表单(),当你点击一个标记时它会显示出来,这在我的例子中非常好地解决了这个问题
我还可以想象,在许多用例中,您希望完全定制标记器的信息窗口,但在GitHub()上阅读此问题,似乎目前不可能,因为信息窗口不是一个颤振小部件。偶然发现了这个问题,并找到了一个适合我的解决方案:
为了解决这个问题,我写了一个,可以自由定制。例如,使用一些阴影过孔
实施
导入'dart:async';
进口“包装:颤振/材料.省道”;
导入“包:google_-maps_-flatter/google_-maps_-flatter.dart”;
导入“custom_info_widget.dart”;
void main()=>runApp(MyApp());
类PointObject{
最后一个孩子;
最终定位;
PointObject({this.child,this.location});
}
类MyApp扩展了无状态小部件{
//此小部件是应用程序的根。
@凌驾
小部件构建(构建上下文){
返回材料PP(
标题:“颤振演示”,
主题:主题数据(
主样本:颜色。蓝色,
),
初始路径:“/”,
路线:{
“/”:(上下文)=>主页(),
},
);
}
}
类主页扩展了StatefulWidget{
@凌驾
_HomePageState createState()=>\u HomePageState();
}
类_HomePageState扩展状态{
点对象点=点对象(
子项:文本(“Lorem Ipsum”),
地点:LatLng(47.6,8.8796),
);
StreamSubscription _-mapIdleSubscription;
InfoWidgetRoute_InfoWidgetRoute;
谷歌地图控制器(GoogleMapController);;
@凌驾
小部件构建(构建上下文){
返回脚手架(
主体:容器(
颜色:颜色。绿色,
孩子:谷歌地图(
initialCameraPosition:CameraPosition(
目标:施工(47.6,8.6796),
缩放:10,
),
圆:Set()
…加(圈)(
circleId:circleId('hi2'),
中心:拉特林(47.6,8.8796),
半径:50,
冲程宽度:10,
strokeColor:Colors.black,
)),
标记:Set()
…添加(标记)(
markerId:markerId(point.location.latitude.toString()+
point.location.longitude.toString()),
位置:point.location,
onTap:()=>\u onTap(点),
)),
onMapCreated:(映射控制器){
_mapController=mapController;
},
///这伪造了onMapIdle,因为googleMaps on Map Idle并不总是有效
///(见:https://github.com/flutter/flutter/issues/37682)
///当地图处于空闲状态且存在_infoWidgetRoute时,将显示该地图。
onCameraMove:(新职位){
_mapIdleSubscription?.cancel();
_mapIdleSubscription=Future.delayed(持续时间(毫秒:150))
.asStream()
.听{
如果(_infoWidgetRoute!=null){
of(上下文,rootNavigator:true)
.push(_infoWidgetRoute)
.那么(
(新价值){
_infoWidgetRoute=null;
},
);
}
});
},
),
),
);
}
///现在是我的_onTap方法。它首先创建信息小部件路由,然后
///为摄影机设置两次动画:
///首先到标记附近的地方,然后到标记处。
///这样做是为了确保始终调用onCameraMove
_onTap(点对象点)异步{
final RenderBox RenderBox=context.findenderobject();
Rect\u itemRect=renderBox.localToGlobal(Offset.zero)&renderBox.size;
_infoWidgetRoute=infoWidgetRoute(
child:point.child,
buildContext:context,
textStyle:const textStyle(
尺寸:14,
颜色:颜色,黑色,
),
mapsWidgetSize:_itemRect,
);
等待\u mapController.animateCamera(
CameraUpdate.newCameraPosition(
摄像定位(
目标:拉丁美洲(
点位置纬度-0.0001,
point.location.longitude,
),
缩放:15,
),
),
);
等待\u mapController.animateCamera(
CameraUpdate.newCameraPosition(
摄像定位(
目标:拉丁美洲(
点、位置、纬度,
point.location.longitude,
),
缩放:15,
),
),
);
}
}
CustomInfoWidget:
导入“包装:颤振/材料.省道”;
进口“包装:颤振/喷漆.省道”;
导入“package:meta/meta.dart”;
类_InfoWidgetRouteLayout扩展了SingleChildLayoutDelegate{
最终的矩形映射大小;
最终双倍宽度;
最终双倍高度;
_InfoWidgetRouteLayout(
{@required this.mapsWidgetSize,
@需要这个高度,
@需要此参数(宽度});
///根据标记或小部件的大小,必须调整y方向上的偏移量;
///如果显示的代码大小不同,则可以取消注释并删除注释代码
///调整以获得小部件的正确位置。
///或者更好:根据设备像素比例调整标记大小!!!!)
@凌驾
偏移量getPositionForChild(大小,大小childSize){
//if(Platform.isIOS){
返回偏移量(
mapsWidgetSize.center.dx-childSize.width/2,
mapsWidgetSize.center.dy-childSi
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);
}
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++;
}
}
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),
),
),
),
),
);
}
}
return Stack(
children: <Widget>[
MarkerInfo(
text: tripMinutes.toString(),
getBitmapImage: (img) {
customMarkerInfo = img;
}),
GoogleMap(
markers: markers,
...
markers.add(
Marker(
position: position,
icon: BitmapDescriptor.fromBytes(customMarkerInfo),
markerId: MarkerId('MarkerID'),
),
);
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;
}