如何在不闪烁的情况下更新TileOverlay?

39
我有一些动态瓦片内容需要在地图顶部显示(具体来说,是天气图片--雷达、卫星、温度等)。我正在使用Google Maps API for Android v2。我遇到的问题是,显然更新瓷砖图像的唯一方法(即当新数据到达或当帧在时间跨度动画中推进时)是调用TileOverlay.clearImageCache。不幸的是,当我这样做时,瓦片叠加会闪烁一下。这是因为clearImageCache立即从显示中删除现有的瓦片图像,但在解码和显示新的瓦片图像之前存在延迟。
我正在使用自定义的TileProvider缓存瓦片图像,而不是每次从服务器获取它们。但即使它只传递缓存的瓦片(即我的TileProvider.getTile实现没有施加任何显著的延迟),在过程中仍然存在足够的延迟,以至于用户可以看到闪烁。
有人知道如何避免这种闪烁吗?有没有办法可以双缓冲瓦片叠加层?我试着用地图上附加的两个TileOverlays来双缓冲它,其中一个是不可见的,但是即使我调用clearImageCache后,不可见的TileOverlay也不会开始从我的TileProvider获取任何瓦片。

1
你尝试过先添加新的,再删除旧的吗? - yarian
2
不会有帮助。这些瓷砖是半透明的,因此同时显示两组瓷砖会使一切变暗。我只是在交换一个视觉瑕疵和另一个之间做选择。不过还是谢谢你的建议。 - erickj00001
将新元素添加到背景映射的(z-order)下面,然后将它们推到前面,并同时将旧元素向后推。本质上是使用z-order而不是可见性进行双缓冲? - GHC
那也不起作用。即使我将Z索引设置为非常低的负数,TileOverlay仍然可见,这表明它被硬编码为始终在地图上方显示叠加层。Z索引仅控制哪些覆盖物相对于其他覆盖物绘制在顶部。 - erickj00001
@erickj00001,你找到任何解决方法了吗?对于任何试图“动画化”叠加层的人来说,这是一个困难的问题。 - aez
2
我还没有找到任何好的解决方法。我们最终使用GroundOverlay实例来代替,这很麻烦,因为我们必须明确编写逻辑以跟踪所需的瓷砖。更糟糕的是,在动画期间它们必须被销毁和重新创建,这很慢。(有一个setImage方法,但它有一个巨大的内存泄漏:http://code.google.com/p/gmaps-api-issues/issues/detail?id=6286) - erickj00001
4个回答

1

能否在加载即将出现的瓷砖图像时将其可见性设置为false?Tile.setVisible(false);

然后,在您想要更改时(在即将出现的瓷砖加载后),将即将出现的瓷砖设置为可见,将当前瓷砖设置为不可见?

CurrentTile.setVisible(false);
NewTile.setVisible(true);

这样更改都在同一渲染帧内完成,无需等待缓存图像加载的延迟。

如果你的意思是将TileOverlay本身设置为不可见,那么我已经尝试过了。请参阅我的原始帖子——问题在于它在不可见时不会开始获取任何瓷砖。如果你的意思是设置单个瓷砖的属性,那么当使用TileOverlay时,我认为你无法访问单个瓷砖图形对象。 - erickj00001

0
一个相当不错的解决方案是将瓦片下载和缓存与TileProvider分离。这样,您可以完全控制它们何时被下载,并在下载完成后仅替换byte[]引用。
这可能会更加复杂,因为您必须注意当前可见区域和缩放级别,以免全部下载,而只下载那些将可见。 编辑 使用以下代码进行测试:
try {
    InputStream is = getAssets().open("tile1.jpg");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int b = is.read();
    while (b != -1) {
        baos.write(b);
        b = is.read();
    }
    byte[] array = baos.toByteArray();
    cache = new Tile(256, 256, array);
    is = getAssets().open("tile2.jpg");
    baos = new ByteArrayOutputStream();
    b = is.read();
    while (b != -1) {
        baos.write(b);
        b = is.read();
    }
    array = baos.toByteArray();
    nextCache = new Tile(256, 256, array);
} catch (IOException ex) {
    Log.e("tag", "error reading tiles", ex);
}

tileOverlay = map.addTileOverlay(new TileOverlayOptions().tileProvider(new TileProvider() {
    @Override
    public Tile getTile(int x, int y, int zoom) {
        return cache;
    }
}));

然后在其他地方:

Tile temp = cache;
cache = nextCache;
nextCache = temp;
tileOverlay.clearTileCache();

"最快"的代码仍然失败。

如果您无法切换到GroundOverlay或Markers,则另一个想法是尝试使用第三方地图瓦片,您当前的天气瓦片在上方和下方的下一个瓦片,以便它们可以在几秒钟后加载和切换它们(使用zOrder)。


这正是我最初希望能够做到的 - 但就像我在原始帖子中所说的那样,即使所有瓷砖都已缓存并且TileProvider.getTile没有施加任何重大延迟,仍然会出现闪烁。这是因为clearImageCache立即从显示中删除了瓷砖,但TileOverlay在显示新瓷砖之前仍有一些工作要做。 - erickj00001
1
我添加了测试代码,你说得对。似乎没有办法消除闪烁。也许最好考虑使用GroundOverlay或Markers,并且在onMarkerClick返回false,这样它就不会居中并且不会显示信息窗口。 - MaciejGórski

0
我找到的解决方法是将图层的透明度设置为1。这使它们隐藏但仍然请求瓷砖。然后您可以切换哪些图层的透明度为1和0。
由于在更改两个图层的透明度之间有一个渲染调用,仍然可能会出现一些闪烁。没有办法使这成为原子操作。
还发现没有很好的方法知道图层何时完全加载了瓷砖。即使在从TileProvider返回瓷砖之后,Google Maps还需要对其进行一些处理,因此您需要一些延迟才能切换瓷砖。

0

对我起作用的解决方案(我尝试每秒刷新瓷砖):

    // Adding "invisible" overlay
    val newTileOverlay =  mMap?.addTileOverlay(
        TileOverlayOptions()
            .tileProvider(getTileProvider()).transparency(1f).visible(true)
    )

    mTileOverlay?.transparency = 0.5f // making previous overlay visible
    mOldTileOverlay?.remove() // removing previously displayed visible overlay
    mOldTileOverlay = mTileOverlay
    mTileOverlay = newTileOverlay

所以我们同时有两个图层(一个可见,一个不可见)。我不确定它会如何影响性能。


请问您能否提供更多的信息和代码给我? - Homayoon Ahmadi
我正在努力应对这个。 - Homayoon Ahmadi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接