本文介紹了Google Maps SDK for Android:流暢地將相機(jī)設(shè)置為新位置的動(dòng)畫,渲染沿途的所有瓷磚的處理方法,對(duì)大家解決問題具有一定的參考價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧!
問題描述
背景
許多類似的問題以前似乎在SO上被問過(最明顯的是android google maps not loading the map when using GoogleMap.AnimateCamera()和How can I smoothly pan a GoogleMap in Android?),但在這些帖子中發(fā)布的答案或評(píng)論都沒有讓我確切地知道如何做到這一點(diǎn)。
我最初以為它會(huì)像調(diào)用animateCamera(CameraUpdateFactory.newLatLng(), duration, callback)
一樣簡(jiǎn)單,但像上面第一個(gè)鏈接的op一樣,我得到的只是一個(gè)灰色或非常模糊的貼圖,直到動(dòng)畫完成,即使我將其放慢到幾十秒長(zhǎng)!
我已經(jīng)設(shè)法找到并實(shí)現(xiàn)了this helper class,它很好地允許瓷磚沿途渲染,但即使延遲為0,每個(gè)動(dòng)畫之間也有明顯的延遲。
代碼
好了,是時(shí)候編寫一些代碼了。下面是(稍作修改的)幫助器類:
package com.coopmeisterfresh.googlemaps.NativeModules;
import android.os.Handler;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.GoogleMap;
import java.util.ArrayList;
import java.util.List;
public class CameraUpdateAnimator implements GoogleMap.OnCameraIdleListener {
private final GoogleMap mMap;
private final GoogleMap.OnCameraIdleListener mOnCameraIdleListener;
private final List<Animation> cameraUpdates = new ArrayList<>();
public CameraUpdateAnimator(GoogleMap map, GoogleMap.
OnCameraIdleListener onCameraIdleListener) {
mMap = map;
mOnCameraIdleListener = onCameraIdleListener;
}
public void add(CameraUpdate cameraUpdate, boolean animate, long delay) {
if (cameraUpdate != null) {
cameraUpdates.add(new Animation(cameraUpdate, animate, delay));
}
}
public void clear() {
cameraUpdates.clear();
}
public void execute() {
mMap.setOnCameraIdleListener(this);
executeNext();
}
private void executeNext() {
if (cameraUpdates.isEmpty()) {
mOnCameraIdleListener.onCameraIdle();
} else {
final Animation animation = cameraUpdates.remove(0);
new Handler().postDelayed(() -> {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}, animation.mDelay);
}
}
@Override
public void onCameraIdle() {
executeNext();
}
private static class Animation {
private final CameraUpdate mCameraUpdate;
private final boolean mAnimate;
private final long mDelay;
public Animation(CameraUpdate cameraUpdate, boolean animate, long delay) {
mCameraUpdate = cameraUpdate;
mAnimate = animate;
mDelay = delay;
}
}
}
和我實(shí)現(xiàn)它的代碼:
// This is actually a React Native Component class, but I doubt that should matter...?
public class NativeGoogleMap extends SimpleViewManager<MapView> implements
OnMapReadyCallback, OnRequestPermissionsResultCallback {
// ...Other unrelated methods removed for brevity
private void animateCameraToPosition(LatLng targetLatLng, float targetZoom) {
// googleMap is my GoogleMap instance variable; it
// gets properly initialised in another class method
CameraPosition currPosition = googleMap.getCameraPosition();
LatLng currLatLng = currPosition.target;
float currZoom = currPosition.zoom;
double latDelta = targetLatLng.latitude - currLatLng.latitude;
double lngDelta = targetLatLng.longitude - currLatLng.longitude;
double latInc = latDelta / 5;
double lngInc = lngDelta / 5;
float zoomInc = 0;
float minZoom = googleMap.getMinZoomLevel();
float maxZoom = googleMap.getMaxZoomLevel();
if (lngInc > 15 && currZoom > minZoom) {
zoomInc = (minZoom - currZoom) / 5;
}
CameraUpdateAnimator animator = new CameraUpdateAnimator(googleMap,
() -> googleMap.animateCamera(CameraUpdateFactory.zoomTo(
targetZoom), 5000, null));
for (double nextLat = currLatLng.latitude, nextLng = currLatLng.
longitude, nextZoom = currZoom; Math.abs(nextLng) < Math.abs(
targetLatLng.longitude);) {
nextLat += latInc;
nextLng += lngInc;
nextZoom += zoomInc;
animator.add(CameraUpdateFactory.newLatLngZoom(new
LatLng(nextLat, nextLng), (float)nextZoom), true);
}
animator.execute();
}
}
問題
有沒有更好的方法來完成這項(xiàng)看似簡(jiǎn)單的任務(wù)?我在想,也許我需要將我的動(dòng)畫移動(dòng)到工作線程或其他什么地方;這會(huì)有幫助嗎?
感謝閱讀(我知道這是一種努力:p)!
更新30/09/2021
我已經(jīng)根據(jù)Andy在評(píng)論中的建議更新了上面的代碼,雖然它可以工作(盡管存在相同的延遲和渲染問題),但最終的算法需要更復(fù)雜一些,因?yàn)槲蚁肟s小到縱向三角洲的中點(diǎn),然后隨著旅程的繼續(xù)而返回。
一次完成所有這些計(jì)算,并同時(shí)流暢地渲染所有必要的瓷磚,對(duì)于我正在測(cè)試的廉價(jià)手機(jī)來說,似乎太多了?;蛘哌@是API本身的限制?無論如何,我如何才能使所有這些工作順利進(jìn)行,而不會(huì)在排隊(duì)的動(dòng)畫之間出現(xiàn)任何延遲?
推薦答案
這是我使用您的實(shí)用程序幀播放機(jī)的嘗試。
注意事項(xiàng):
縮放值基于總步長(zhǎng)(此處設(shè)置為500)進(jìn)行內(nèi)插,并給定起始值和停止值。
Google地圖實(shí)用程序用于根據(jù)分?jǐn)?shù)距離SphericalUtil.interpolate
計(jì)算下一個(gè)LNG。
分?jǐn)?shù)距離不應(yīng)是線性函數(shù),以減少新瓷磚的引入。換句話說,在更高的變焦(更近的距離)下,相機(jī)移動(dòng)的距離更短,而縮小時(shí)相機(jī)的移動(dòng)量(從中心到中心)呈指數(shù)級(jí)增加。這需要更多的解釋…
如您所見,遍歷被一分為二–與距離移動(dòng)的指數(shù)函數(shù)相反。
最遠(yuǎn)的";max";Zoom(壞名字)可以是總距離的函數(shù)-計(jì)算為在中點(diǎn)處包圍整個(gè)路徑。目前,對(duì)于這種情況,它被硬編碼為4。
注意:映射animate
函數(shù)不能,因?yàn)樗诿恳徊?/em>都引入了自己的彈跳球效果,這是不受歡迎的。因此,在給定相當(dāng)數(shù)量的步驟后,可以使用move
函數(shù)。
此方法嘗試最大限度地減少每一步的磁貼加載,但最終TileLoader是無法(輕松)監(jiān)視的查看限制因素。
AnimateCameraToPosition
// flag to control the animate callback (at completion).
boolean done = false;
private void animateCameraToPosition(LatLng targetLatLng, float targetZoom) {
CameraPosition currPosition = gMap.getCameraPosition();
LatLng currLatLng = currPosition.target;
//meters_per_pixel = 156543.03392 * Math.cos(latLng.lat() * Math.PI / 180) / Math.pow(2, zoom)
int maxSteps = 500;
// number of steps between start and midpoint and midpoint and end
int stepsMid = maxSteps / 2;
// current zoom
float initz = currPosition.zoom;
//TODO maximum zoom (can be computed from overall distance) such that entire path
// is visible at midpoint.
float maxz = 4.0f;
float finalz = targetZoom;
CameraUpdateAnimator animator = new CameraUpdateAnimator(gMap, () -> {
if (!done) {
gMap.animateCamera(CameraUpdateFactory.
zoomTo(targetZoom), 5000, null);
}
done = true;
});
// loop from start to midpoint
for (int i = 0; i < stepsMid; i++) {
// compute interpolated zoom (current --> max) (linear)
float z = initz - ((initz - maxz) / stepsMid) * i;
// Compute fractional distance using an exponential function such that for the first
// half the fraction delta advances slowly and accelerates toward midpoint.
double ff = (i * (Math.pow(2,maxz) / Math.pow(2,z))) / maxSteps;
LatLng nextLatLng =
SphericalUtil.interpolate(currLatLng, targetLatLng, ff);
animator.add(CameraUpdateFactory.newLatLngZoom(
nextLatLng, z), false, 0);
}
// loop from midpoint to final
for (int i = 0; i < stepsMid; i++) {
// compute interpolated zoom (current --> max) (linear)
float z = maxz + ((finalz - maxz) / stepsMid) * i;
double ff = (maxSteps - ((i+stepsMid) * ( (Math.pow(2,maxz) / Math.pow(2,z)) ))) / (double)maxSteps;
LatLng nextLatLng =
SphericalUtil.interpolate(currLatLng, targetLatLng, ff);
animator.add(CameraUpdateFactory.newLatLngZoom(
nextLatLng, z), false, 0);
}
animator.add(CameraUpdateFactory.newLatLngZoom(
targetLatLng, targetZoom), true, 0);
//
animator.execute();
}
測(cè)試代碼
我用這兩個(gè)點(diǎn)(和代碼)測(cè)試了從自由女神像到西海岸的一個(gè)點(diǎn):
gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(40.68924, -74.04454), 13.0f));
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
animateCameraToPosition(new LatLng(33.899832, -118.020450), 13.0f);
}
}, 5000);
CameraUpdateAnimator模式
我稍微修改了相機(jī)更新動(dòng)畫師:
public void execute() {
mMap.setOnCameraIdleListener(this);
executeNext();
}
private void executeNext() {
if (cameraUpdates.isEmpty()) {
mMap.setOnCameraIdleListener(mOnCameraIdleListener);
mOnCameraIdleListener.onCameraIdle();
} else {
final Animation animation = cameraUpdates.remove(0);
// This optimization is likely unnecessary since I think the
// postDelayed does the same on a delay of 0 - execute immediately.
if (animation.mDelay > 0) {
new Handler().postDelayed(() -> {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}, animation.mDelay);
} else {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}
}
}
樣本前
使用
// assume initial (40.68924, -74.04454) z=13.0f
gMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(33.899832,-118.020450), 13.0f), 30000, null);
采樣后
這些是從仿真器錄制的。我也側(cè)面加載到我的手機(jī)(Samsum SM-G960U)上,也有類似的結(jié)果(使用1000步0延遲)。
所以我認(rèn)為這不能完全滿足您的要求:有一些不明確的磁貼&因?yàn)樗鼈兪菑奈鞣絺魅氲摹?/em>
自由女神像前往圣地亞哥附近的某個(gè)地方
500步0延遲
100步驟0延遲
50步延遲100ms
診斷
在某些方面,深入了解Maps對(duì)磁貼做了什么是很有用的??梢酝ㄟ^安裝簡(jiǎn)單的UrlTileProvider
并記錄請(qǐng)求來提供洞察。此實(shí)現(xiàn)將獲取Google Tiles,盡管它們的分辨率通常較低。
要執(zhí)行此操作,需要執(zhí)行以下操作:
// Turn off this base map and install diagnostic tile provider
gMap.setMapType(GoogleMap.MAP_TYPE_NONE);
gMap.addTileOverlay(new TileOverlayOptions().tileProvider(new MyTileProvider(256,256)).fadeIn(true));
并定義診斷文件提供程序
public class MyTileProvider extends UrlTileProvider {
public MyTileProvider(int i, int i1) {
super(i, i1);
}
@Override
public URL getTileUrl(int x, int y, int zoom) {
Log.i("tiles","x="+x+" y="+y+" zoom="+zoom);
try {
return new URL("http://mt1.google.com/vt/lyrs=m&x="+x+"&y="+y+"&z="+zoom);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
您馬上就會(huì)注意到,瓷磚層總是以整數(shù)單位(int
)定義的。縮放中提供的分?jǐn)?shù)縮放(例如LatLngZoom
)嚴(yán)格使用內(nèi)存中的圖像-很好了解?!?/p>
這里有一個(gè)完整的示例:
// initial zoom
x=2411 y=3080 zoom=13
x=2410 y=3080 zoom=13
x=2411 y=3081 zoom=13
x=2410 y=3081 zoom=13
x=2411 y=3079 zoom=13
x=2410 y=3079 zoom=13
和最大值:
x=9 y=12 zoom=5
x=8 y=12 zoom=5
x=9 y=11 zoom=5
x=8 y=11 zoom=5
x=8 y=13 zoom=5
x=9 y=13 zoom=5
x=7 y=12 zoom=5
x=7 y=11 zoom=5
x=7 y=13 zoom=5
x=8 y=10 zoom=5
x=9 y=10 zoom=5
x=7 y=10 zoom=5
下面是每次調(diào)用tiler(x軸)時(shí)縮放(y軸)的圖表。每個(gè)縮放層與所需的IMO計(jì)數(shù)大致相同。完全變焦顯示的時(shí)間是原來的兩倍,因?yàn)檫@是重復(fù)的中點(diǎn)。但也有一些異常情況需要解釋(例如110左右)。
這是磁貼提供商記錄的縮放圖表。因此,每個(gè)x軸點(diǎn)將代表單個(gè)磁貼提取。
這篇關(guān)于Google Maps SDK for Android:流暢地將相機(jī)設(shè)置為新位置的動(dòng)畫,渲染沿途的所有瓷磚的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,