flutter静态地图展示优化
最近项目里需要用到地图展示,但是仅仅是展示,不需要进行交互处理,效果如下:
针对这种场景,比较常见的用法是使用简单的地图控件,屏蔽掉交互事件就可以了。
但是实际使用发现,可以是可以,性能不是很好,内存占用比较高,而且页面拖拽的时候有时候会出现兼容闪烁问题,于是就想着怎么去优化这一块,最终生成了以下两种方案,有相同烦恼的童鞋可以参考参考。
老规矩: 先贴Demo
Plan A
首先我们使用的是谷歌地图,通过查阅官方的文档发现,谷歌其实已经提供了我们需要的功能,支持一键生成静态地图的功能:Maps Static API
通过指定链接可以获取到对应经纬度位置的图片
maps.googleapis.com/maps/api/st…
链接支持配置地图类型、地区、语言、缩放系数、尺寸、大头针图标、路径轨迹、图片类型等等等等,功能应有尽有,基本上是能满足各种日常的业务场景的。
你需要做的仅仅是通过官方进行注册,获取一个特有的mapkey就好了,其他的就交给谷歌吧。
效果如下:
听起来简直不要太完美有木有
but
-------------转折线---------------
美好的事物的获取总是不那么容易
唯一的一个缺陷:收费 (划重点)
费用是根据浏览量来计算的,对于日活较多的项目来说,还是会有一定的费用
当然,财大气粗的童鞋可以忽略这一点,直接用,不差钱。
另外,一些偏小众,或日活不那么多的项目也可以放心使用,一段时间内低于一定浏览量也是完全免费的。
有兴趣的同学可以查阅下官方文档的收费标准。
对于我们的项目来说,日活不低,为了白嫖,哦不,是为了给公司开源节流,杜绝养成走捷径的不良风气,经过慎重考虑 QAQ,我们毅然地舍弃了这个方案。
那么,我们该继续忍受原生地图的折磨吗?不,让我们接着往下看
Plan B
排除了图片链接方案后,我们尝试着手动将地图转化为图片。
首先我们尝试了使用dart原生截图方法对地图进行处理,生成图片
1.Flutter系统截图
在flutter项目中使用RepaintBoundary
组件可以实现局部截图功能
RepaintBoundary(
key: containerKey,
child: YourMapWidget(),
)
但是对地图实践发现截取出来的是一张空白的图
这是因为谷歌地图是在原生平台上绘制的,使用RepaintBoundary
对其进行截取是不可行的
2.地图自带截图功能
思路
谷歌地图GoogleMap
提供了takeSnapshot
方法用于截图
Apple地图AppleMap
也提供了相似的方法进行处理
实际上在上述方法内部也就是通过原生进行截图处理的
通过该方法我们可以拿到对应截图的字节流数据,从而转化为对应的图片
/// 进行截图
void startSnapShot() async {
debugPrint("地图开始截屏");
try {
final uin8list = await state.mapController?.takeSnapshot();
if (uin8list != null) {
state.mapImage = MemoryImage(uin8list);
update();
} else {
debugPrint("地图截屏失败");
}
} catch (e) {
debugPrint("地图截屏错误:$e");
}
}
拿到图片之后将现有的布局进行刷新,替换掉地图控件,并进行释放,就可以实现正常的地图图片展示了,内存正常释放,并且不会再出现上面提到的兼容闪烁问题。
/// controller释放
void releaseMapController() {
debugPrint("地图controller释放");
state.mapController?.dispose();
state.mapController = null;
}
效果:
谷歌地图(上面是地图控件,下面是截图)
苹果地图 (上面是地图控件,下面是截图)
问题
-
地图在数据未加载完毕,出现图像之前进行截图,会得到一张空白的图片
针对这个问题,通过查阅Api发现地图控件并未提供加载完毕的回调事件,也就是说我们没有很好的办法来监听地图的数据加载
不过在实践中发现,除了网速很差的情况,一般地图都能在零点几秒内完成加载
所以在这里我采用了延时1秒左右进行截图的方案
Future.delayed( const Duration(milliseconds: 1000), () async { logic.startSnapShot(); }, );
-
地图未出现在当前屏幕中,进行截图会得到一张空白的图片
这就要求我们对地图的出现进行监听,当它出现在屏幕中才能进行截图操作
此外,还需要考虑到上面提到的数据加载延时问题,
最终的方案是使用VisibilityDetector组件对地图可见度进行监听,当地图第一次出现时,延时进行截图,如果期间地图滑出屏幕,则不进行处理。当地图再次进入屏幕,则不需要延时,立即进行截图处理。
return VisibilityDetector( key: state.mapKey, onVisibilityChanged: (VisibilityInfo info) async { state.mapVisibleFraction = info.visibleFraction; // debugPrint("地图可见度:${info.visibleFraction}"); // 地图第一次出现的时候,延时0.6s后才进行截图,避免地图未渲染完毕,截图空白 if (info.visibleFraction > 0 && state.isMapFirstShow) { state.isMapFirstShow = false; Future.delayed( const Duration(milliseconds: 600), () async { state.isMapFinishLoad = true; if (state.mapVisibleFraction > 0.2) { logic.startSnapShot(); } }, ); } if (info.visibleFraction < 0.2) return; if (state.mapController == null || !state.isMapFinishLoad || state.isMapWaitSnapShot) return; logic.startSnapShot(); }, child: GoogleMap( ... ), );
-
3.安卓端使用谷歌地图进行截图处理时,在极端场景下,触发了截图之后立刻跳转屏幕,可能会出现crash问题
这个跟地图SDK内部原生的处理有关,目前没有好的解决方案,只能等官方出手了,大家用的时候可以实际测试一下
总结
- 追求简单易用性,或日活不大的App可以使用谷歌静态地图来实现
- 不符合第一条条件的可以使用上述的截图方案,详细代码可以参考Demo
- 如果对性能要求不高,那就直接使用地图控件把
转载自:https://juejin.cn/post/7297159273905848361