Flutter 自定义 Google 地图标记信息窗口

2024-03-23

我正在 Flutter 中研究 Google 地图标记。

单击每个标记时,我想显示一个自定义信息窗口,其中可以包含按钮、图像等。但是在 Flutter 中有一个属性TextInfoWindow只接受String.

我如何实现向地图标记添加按钮、图像InfoWindow.


偶然发现这个问题并找到了适合我的解决方案:

为了解决这个问题我写了一个自定义信息小部件 https://github.com/Menelphor/CustomInfoWidget/blob/master/custom_info_widget,随意定制。例如通过一些阴影剪辑阴影路径 https://gist.github.com/coman3/e631fd55cd9cdf9bd4efe8ecfdbb85a7.

执行

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;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter 自定义 Google 地图标记信息窗口 的相关文章

  • Google Play 商店中不支持的设备 - Flutter

    我已将我的应用程序上传到谷歌商店 但我的一些朋友无法安装它 他们得到 您的设备与此版本不兼容 我已经检查了sdk 屏幕尺寸和权限要求 也将uses features required设置为false 但仍然有一些设备无法安装它 我在游戏控制
  • “无法从构建环境获取 Google 服务文件中的 GOOGLE_APP_ID”错误

    我正在使用下面的指南为我的使用 flutter firebase 的项目设置开发和生产环境https www tengio com blog multiple firebase environments with flutter https
  • 在列表视图中颤动网格视图

    我想构建像 Ios 应用商店这样的设计 如下图所示 我想要实现的是有 5 个顶级类别 每个类别都有显示图像的网格 我这样尝试过 return new Scaffold backgroundColor Colors white appBar
  • Flutter更新Appbar中的文本

    我需要帮助更新应用栏中的文本以匹配我当前所在的页面 因此 如果我在 设置 页面中 那么我需要在 AppBar 文本中显示它 我添加代码和屏幕截图是为了更好地解释我想要实现的目标 主dart void main gt runApp MyApp
  • 如何在 Flutter 中创建网络图像列表

    我使用 Carousel Pro 包在屏幕上实现轮播 在这个 Carousel 类中它需要一个图像列表 Carousel 类的语法是 Carousel images NetworkImage https cdn images 1 mediu
  • 导入的包“路径”不是导入包的依赖项

    我刚刚升级了我的软件包 现在我开始看到一条警告 是什么原因造成的 您的一个或部分包裹 很可能是path provider https pub dev packages path provider had path https pub dev
  • 如何:默认显示 video_player 插件的播放控件 (flutter-web)

    有什么方法可以默认显示视频播放器的控件吗 如果我在浏览器中右键单击视频 我就可以显示它们 所以我假设必须有一种默认显示的方法 我无法找到默认显示 video player 控件的方法 但使用了这个包 它在 Flutter web 上工作得很
  • 颤动中的弯曲导航栏

    目前 我在 flutter 中有一个库https pub dev packages curved navigation bar https pub dev packages curved navigation bar并已经在我的项目中实现了
  • 源单元“_BuildScript_”中“语义分析”阶段出现异常

    评估根项目 android 时出现问题 配置项目 app 时出现问题 无法打开构建文件 C Users InFED Laptop Documents GitHub sustain and save android app build gra
  • 如何连接flutter到localhost mysql数据库

    我想将本地主机 mysql 数据库连接到 flutter 但我没有这样做 我尝试了 mysql1 与这些连接 ConnectionSettings host 10 0 2 2 port 3306 user root password roo
  • 我无法捕捉到 Flutter 上 Firebase Auth 的异常

    我正在尝试使用 flutter 上的 firebase auth 在我的应用程序上创建登录功能 我必须处理很多例外情况 但我根本无法捕获它们 这是我的代码 Future
  • Flutter中的pushReplacementNamed和popAndPushNamed有什么区别?

    The NavigatorState班级在Flutter navigator dart有 2 种具有类似行为的方法 有什么区别pushReplacementNamed and popAndPushNamed在颤振中 pushReplacem
  • 如何在flutter中从设备存储读取CSV文件

    我想将数据从 flutter 中的 CSV 文件导入到 firebase 数据库中 因此 我使用文件选择器从设备中选择 CSV 文件 现在我如何从该文件中读取数据 首先从 dart 包导入 file picker 和 CSV 包 比定义方法
  • 如何设置 dart objectbox 并预填充本地数据库?

    我想用 Flutter 设置一个 ObjectBox 数据库 我想用值预先填充数据库文件 安装应用程序时 数据库文件将被复制并由应用程序使用 我希望能够继续提供架构迁移 是否可以 如何建立这种类型的架构 你有什么例子吗 SOLUTION 通
  • Flutter 分析/构建在 GitHub 操作中失败

    当运行以下操作时 它会失败flutter analyze 如果我删除它 稍后会失败flutter build 这两个命令在本地都可以正常工作 我理解该消息 但无法理解包路径可能有什么问题 GitHub 操作错误 flutter analyz
  • 当模拟器存在时 Flutter 应用程序不运行

    模拟器在那里 但当我启动应用程序时它不起作用 请帮忙 我已经尝试了一切 Cal Flutter 新手 我已遵循安装指南并尝试了其他堆栈流答案的修复 我正在运行 Windows 8 1 单一用户 我已经从命令提示符和 android 终端中尝
  • VSCode和flutter,如何连接多个设备?

    我在 macOS 上使用 Visual Studio Code 来开发 Flutter 应用程序 我可以在 VSC 左下角选择一个设备 我还可以使用在多个设备上运行flutter run d all 我想知道如何使用 VSC 中的调试控制台
  • 颤动附近的连接

    当我尝试在设备上做广告或发现时 我收到此错误 但是前一天在环路上效果很好 PlatformException Failure 17 API Nearby CONNECTIONS API is not available on this de
  • 有没有办法模拟小部件或屏幕特定位置的触摸?

    我想触摸或点击小部件上的某处 而不让用户在此时明确触摸屏幕 有什么办法可以做到吗 我已经检查了SO答案 有些人建议使用 集成测试 但在未物理或以某种方式连接到笔记本电脑的设备上无法执行 集成测试 无法找到更好的措辞 我还尝试进行 hitTe
  • 滑动时向 PageView 添加新页面

    我目前正在制作一个日历应用程序 我想向右或向左滑动以转到下个月或上个月 我使用 PageView 时首先设置了一个包含 3 个项目的数组 第一个页面是第二个项目 我想向右滑动并在末尾添加一个页面 我想向左滑动并在开头添加一个页面 目前 如果

随机推荐

  • 如何在JPA中定义单向OneToMany关系

    我在 JPA 中的实体映射方面遇到以下问题 我有两个实体 第一个是查找 第二个是代表实体翻译的文本 现在我需要将 Lookup 绑定到 Text 但我不希望 Text 引用 Lookup 为了使事情变得更复杂 文本在这种关系中不使用其主键
  • 将行添加到命名范围

    我在 Google 表格中有一个命名范围 A1 K14 我想做的就是在命名范围的底部添加一个新行 这似乎是一项容易的任务 使用此代码不会扩展命名范围 并且我没有收到错误消息 它确实在命名范围之外插入一个新行 这不是我想要做的 如果我改为in
  • 带有单位编号/子前提的 Google 地方自动完成建议不会出现在响应数组中

    我正在使用 Google Places API 使用 javascript 自动完成地址 当我在输入框中输入地址的单元号和街道号时 它会在建议下拉列表中显示结果 但是当我选择地址时 操作 place changed 事件的侦听器没有任何地址
  • Rails:如何向包含变音符号的收件人发送电子邮件?

    我想发送一封包含以下设置的电子邮件 def registration confirmation user recipients user username lt user email gt from Netzwerk Muensterlan
  • 内连接与何处连接

    两者之间的性能 在 Oracle 中 是否存在差异 Select from Table1 T1 Inner Join Table2 T2 On T1 ID T2 ID And Select from Table1 T1 Table2 T2
  • Hive“ANALYZE TABLE”如何从java执行

    我需要计算配置单元表中的行数 为此 我正在使用查询 ANALYZE TABLE p 7 COMPUTE STATISTICS noscan 我想通过java获取结果 我正在尝试以下操作 代码并没有运气 我得到的错误是 Exception i
  • 如何跳转到一个巨大的文本文件中的特定行?

    下面的代码是否有其他替代方案 startFromLine 141978 or whatever line I need to jump to urlsfile open filename rb 0 linesCounter 1 for li
  • 将键值对文件读入 std::map

    我有一个 Visual Studio 2008 C 03 项目 我想将键值对文件读取到 std map 中 为此 我创建了一个istreambuf pair iterator如下 typedef std map lt std string
  • 求解四变量线性方程

    问题 我需要用 Python 解这些方程 a 3b 2c 2d 1 2a b c 2d 0 3a b 2c d 1 2a c 3d 0 这样我就可以得到a b c和d的值 有没有办法可以用分数来显示它们 My code import num
  • 如何使用版本 Maven 插件更新依赖同级模块的版本

    我在更新依赖同级项目的依赖版本时遇到问题 我的简化项目设置如下 root parent tool core tool functional tests 父项目拥有所有全局属性和依赖管理 功能测试取决于工具 而工具又取决于工具核心 根pom
  • ImageView - 高度与宽度匹配吗?

    我有一个图像视图 我希望它的宽度为 fill parent 我希望它的高度是最终的宽度 例如
  • 来自相机的原始图像数据,如“645 PRO”

    不久前我已经问过这个问题并且我也得到了很好的答案 我一直在这个论坛上上下搜索 但找不到我想要的东西 真的需要 我想从相机获取原始图像数据 至目前为止 我试图从中获取 imageDataSampleBuffer 中的数据 方法 capture
  • 如何编写HQL插入查询?

    我正在努力编写一个 HQL 查询来在表中插入新记录 我已经看到了一些插入查询 如下所示 但我不想从另一个表插入数据 如下代码所示 String hql INSERT INTO Employee firstName lastName sala
  • 局部变量赋值以避免多次强制转换

    最近有一个问题询问在 Java 中将调用 getter 的结果分配给局部变量以避免多次调用同一访问器是否是一个好主意 我找不到原始帖子 但共识似乎是这通常是不必要的 因为 Hotspot 无论如何都会优化方法调用开销 然而 对于采用这种技术
  • 执行 PHP 切换每个案例多个值的最佳方法?

    你会如何执行这个 PHP switch 语句 另请注意 这些版本要小得多 我需要创建的版本将添加更多的值 版本1 switch p case home case current home current break case users o
  • 在 Camel-CXF 中将自定义 Soap-Header 设置为 pojo-message

    我的 CXF 肥皂头有问题 我使用合同优先开发方法建立了一个 cxf 项目 我想使用 cxf 组件调用 Web 服务 如下所示
  • 詹金斯 HTTPS Git

    目前正在研究自动化概念验证 所以我试图让 Jenkins 使用我们的 GIT 存储库 但在填写凭据后 我遇到了一个奇怪的错误 Failed to connect to repository Could not init C apache t
  • 在返回带有取消的 IAsyncEnumerable 的函数中迭代 IAsyncEnumerable

    正如标题所说 我必须执行以下功能 public async IAsyncEnumerable
  • 减少深度优先树遍历的空间使用

    在 Haskell 中 我们可以在恒定空间中对无限列表进行过滤 求和等操作 因为 Haskell 仅在需要时生成列表节点 并且垃圾收集它完成的节点 我希望它能与无限的树一起使用 下面是一个相当愚蠢的程序 它生成一个无限二叉树 其中的节点代表
  • Flutter 自定义 Google 地图标记信息窗口

    我正在 Flutter 中研究 Google 地图标记 单击每个标记时 我想显示一个自定义信息窗口 其中可以包含按钮 图像等 但是在 Flutter 中有一个属性TextInfoWindow只接受String 我如何实现向地图标记添加按钮