Flutter 颤振自定义谷歌地图标记信息窗口

Flutter 颤振自定义谷歌地图标记信息窗口,flutter,flutter-layout,Flutter,Flutter Layout,我正在为Flutter中的谷歌地图标记工作 单击每个标记后,我想显示一个自定义信息窗口,该窗口可以包括按钮、图像等。但是在Flatter中有一个属性TextInfoWindow,它只接受字符串 如何将按钮、图像添加到地图标记的InfoWindow我今天偶然发现了同样的问题,我无法在TextInfoWindow中正确显示多行字符串。最后,我实现了一个模式底部表单(),当你点击一个标记时它会显示出来,这在我的例子中非常好地解决了这个问题 我还可以想象,在许多用例中,您希望完全定制标记器的信息窗口,但

我正在为Flutter中的谷歌地图标记工作

单击每个标记后,我想显示一个自定义信息窗口,该窗口可以包括按钮、图像等。但是在Flatter中有一个属性
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;
}