2012-12-03 55 views
85

我正在使用新的Android Google Maps APImoveCamera with CameraUpdateFactory.newLatLngBounds崩潰

我創建了一個包含MapFragment的活動。在活動onResume中,我將標記設置爲GoogleMap對象,然後爲包含所有標記的地圖定義邊界框。

這是使用下面的僞代碼:

LatLngBounds.Builder builder = new LatLngBounds.Builder(); 
while(data) { 
    LatLng latlng = getPosition(); 
    builder.include(latlng); 
} 
CameraUpdate cameraUpdate = CameraUpdateFactory 
    .newLatLngBounds(builder.build(), 10); 
map.moveCamera(cameraUpdate); 

map.moveCamera()的調用導致我的應用程序和以下堆棧崩潰:

Caused by: java.lang.IllegalStateException: 
    Map size should not be 0. Most likely, layout has not yet 

    at maps.am.r.b(Unknown Source) 
    at maps.y.q.a(Unknown Source) 
    at maps.y.au.a(Unknown Source) 
    at maps.y.ae.moveCamera(Unknown Source) 
    at com.google.android.gms.maps.internal.IGoogleMapDelegate$Stub 
     .onTransact(IGoogleMapDelegate.java:83) 
    at android.os.Binder.transact(Binder.java:310) 
    at com.google.android.gms.maps.internal.IGoogleMapDelegate$a$a 
     .moveCamera(Unknown Source) 
    at com.google.android.gms.maps.GoogleMap.moveCamera(Unknown Source) 
    at ShowMapActivity.drawMapMarkers(ShowMapActivity.java:91) 
    at ShowMapActivity.onResume(ShowMapActivity.java:58) 
    at android.app.Instrumentation 
     .callActivityOnResume(Instrumentation.java:1185) 
    at android.app.Activity.performResume(Activity.java:5182) 
    at android.app.ActivityThread 
     .performResumeActivity(ActivityThread.java:2732) 

如果 - 而不是我用的是newLatLngBounds()工廠方法newLatLngZoom()方法然後相同的陷阱不會發生。

onResume是將標記繪製到GoogleMap對象上的最佳位置,還是應該繪製標記並在其他位置設置相機位置?

回答

48

好的我工作了。由於documented here該API不能用於預佈局。

正確API來使用被描述爲:

注意:僅使用更簡單的方法newLatLngBounds(邊界,填充) 以產生如果將要使用後移動 相機提供CameraUpdate地圖已經過佈局。在佈局過程中,API 會計算出 正確投影邊界框所需的地圖顯示邊界。相比之下,您可以在任何時候使用更復雜的方法 newLatLngBounds(邊界,寬度,高度,填充)返回的CameraUpdate,即使在地圖佈局之前,因爲API會根據參數計算顯示邊界 你通過了。

要解決這個問題,我計算我的屏幕尺寸和所提供的寬度和高度

public static CameraUpdate newLatLngBounds(
    LatLngBounds bounds, int width, int height, int padding) 

這就讓我指定邊框預先佈局。

+16

但是如果地圖不佔用整個屏幕呢? – theblang

+0

在我的情況下,我只是假設了一個比實際效果更大的屏幕,並且需要更仔細地計算填充,所以它實際上不是*太大。一個更簡單的原因。 – rfay

+1

你能展示如何根據屏幕尺寸來計算它嗎? – Caipivara

131

您可以使用簡單的newLatLngBounds方法OnCameraChangeListener。所有的工作都會很完美,而且你不需要計算屏幕尺寸。這個事件發生在地圖大小計算後(據我所知)。

實施例:

map.setOnCameraChangeListener(new OnCameraChangeListener() { 

    @Override 
    public void onCameraChange(CameraPosition arg0) { 
     // Move camera. 
     map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 10)); 
     // Remove listener to prevent position reset on camera move. 
     map.setOnCameraChangeListener(null); 
    } 
}); 
+0

這是工作! –

+1

這可以工作,因爲setOnCameraChangeListener保證在地圖佈局後至少運行一次?這是未來的證明嗎? –

+0

@GlennBech不,谷歌地圖API不會對你說這種行爲。但實際上它總是被稱爲。 –

14

添加和刪除標記可以做到佈局前完成,但移動攝像機不能(除了使用newLatLngBounds(boundary, padding)如OP的回答說明)。

可能地,執行初始相機更新的最佳位置是使用如Google's sample code所示的一次性OnGlobalLayoutListener,例如,請參閱setUpMap()MarkerDemoActivity的摘錄。java的

// Pan to see all markers in view. 
// Cannot zoom to bounds until the map has a size. 
final View mapView = getSupportFragmentManager() 
    .findFragmentById(R.id.map).getView(); 
if (mapView.getViewTreeObserver().isAlive()) { 
    mapView.getViewTreeObserver().addOnGlobalLayoutListener(
    new OnGlobalLayoutListener() { 
     @SuppressLint("NewApi") // We check which build version we are using. 
     @Override 
     public void onGlobalLayout() { 
      LatLngBounds bounds = new LatLngBounds.Builder() 
        .include(PERTH) 
        .include(SYDNEY) 
        .include(ADELAIDE) 
        .include(BRISBANE) 
        .include(MELBOURNE) 
        .build(); 
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 
       mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } else { 
       mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 
      } 
      mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); 
     } 
    }); 
} 
+0

我創建了一個[地圖示例項目的gradlefied版本](https://github.com/johnjohndoe/GoogleMapsV2Sample)。 – JJD

+0

我在定位更改時出現此錯誤。我也從示例代碼中發現了這一點,但這對我並不適用。我仍然得到同樣的錯誤。 – osrl

0

另一種方法是類似的東西(假設你的最頂層的看法是一個名爲rootContainer FrameLayout裏,即使它會工作,只要你總是選擇你最上面的容器,無論它有哪些類型或名稱):

((FrameLayout)findViewById(R.id.rootContainer)).getViewTreeObserver() 
    .addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 
     public void onGlobalLayout() { 
      layoutDone = true; 
     } 
    }); 

修改您的相機功能只工作,如果layoutDonetrue將解決所有的問題,而無需額外的功能或線邏輯電路添加到layoutListener處理程序。

34

的解決方案是簡單得多....

// Pan to see all markers in view. 
try { 
    this.gmap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); 
} catch (IllegalStateException e) { 
    // layout not yet initialized 
    final View mapView = getFragmentManager() 
     .findFragmentById(R.id.map).getView(); 
    if (mapView.getViewTreeObserver().isAlive()) { 
     mapView.getViewTreeObserver().addOnGlobalLayoutListener(
     new OnGlobalLayoutListener() { 
      @SuppressWarnings("deprecation") 
      @SuppressLint("NewApi") 
      // We check which build version we are using. 
      @Override 
      public void onGlobalLayout() { 
       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 
        mapView.getViewTreeObserver() 
         .removeGlobalOnLayoutListener(this); 
       } else { 
        mapView.getViewTreeObserver() 
         .removeOnGlobalLayoutListener(this); 
       } 
       gmap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); 
      } 
     }); 
    } 
} 

捕捉IllegalStateException,並使用全球聽衆的觀點來代替。 使用其他方法預先設置邊界大小(預佈局)意味着您必須計算THE VIEW的大小,而不是屏幕設備。它們只在你的地圖全屏時才匹配,不使用片段。

+0

直到現在我一直在使用你的小片段,但它仍然失敗......正確的答案是第一個:-) – cesards

+1

如何/何時失敗?從來沒有這個問題。 第一個答案只是傳遞硬編碼大小作爲後備。 「It works」as in「it does crash crash」,但它沒有做同樣的事情。 –

+0

感謝@JJD的編輯 –

2

好吧,我面臨同樣的問題。我有我的片段與我的SupportmapFragment,ABS和導航抽屜。我所做的是:

public void resetCamera() { 

    LatLngBounds.Builder builderOfBounds = new LatLngBounds.Builder(); 
    // Set boundaries ... 
    LatLngBounds bounds = builderOfBounds.build(); 
    CameraUpdate cu; 
    try{ 
     cu = CameraUpdateFactory.newLatLngBounds(bounds,10); 
     // This line will cause the exception first times 
     // when map is still not "inflated" 
     map.animateCamera(cu); 
     System.out.println("Set with padding"); 
    } catch(IllegalStateException e) { 
     e.printStackTrace(); 
     cu = CameraUpdateFactory.newLatLngBounds(bounds,400,400,0); 
     map.animateCamera(cu); 
     System.out.println("Set with wh"); 
    } 

    //do the rest... 
} 

而且,順便說一句,我以後充氣並在返回前調用從onCreateViewresetCamera()
這是第一次捕捉異常(雖然地圖「獲取大小」作爲一種說法...),然後,其他時間我需要重置相機,地圖已經有大小,並通過填充。

問題是documentation解釋,它說:

不要使用此相機更新,直到該映射 經歷佈局(以這種方法來正確地確定 適當的邊框和更改攝像機縮放級別,地圖必須有一個尺寸)。 否則會拋出IllegalStateException。它不是 足以使地圖可用(即,getMap()返回 非空對象);包含地圖的視圖也必須經過 佈局,以確定其尺寸。如果您不能 確定發生了這種情況,請改爲使用newLatLngBounds(LatLngBounds, int, int, int),並手動提供地圖的尺寸​​。

我認爲這是一個相當不錯的解決方案。 希望它可以幫助別人。

+1

是否有任何偵聽器在佈局完全加載時偵聽。 ? –

8

正如在評論中指出的那樣,接受的答案是一個小黑客。在實施它之後,我發現分析中的日誌數量超過了可接受的數量IllegalStateException。我使用的解決方案是添加OnMapLoadedCallback其中執行CameraUpdate。回調被添加到GoogleMap。完全加載地圖後,將執行相機更新。

這確實會導致地圖在執行相機更新前簡要顯示縮小(0,0)視圖。我覺得這比引起崩潰或依靠無證行爲更可以接受。

51

這些答案都很好,但我會選擇一種更簡單的方法。 如果地圖奠定了以後的方法僅適用了,就等着它:

map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { 
    @Override 
    public void onMapLoaded() { 
     map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30)); 
    } 
}); 
+0

謝謝,這就是我發現的:D – Megamind

+0

我經過多次實驗後發現我需要實現這個答案和上面Daniele的*組合*。有時地圖還沒有被加載,有時佈局還沒有完成。我無法按照需要移動攝像機,直到發生這些事件。 – RobP

+1

來自文檔的此方法的警告:「如果由於連接問題導致地圖從不加載,或者地圖不斷變化,並且由於用戶不斷地與地圖交互而永不完成加載,則此事件不會觸發。」 (重點是我的)。 – stkent

4

在極少數情況下,MapView的佈局已經完成,但是GoogleMap的佈局還沒有完成,ViewTreeObserver.OnGlobalLayoutListener本身不能阻止崩潰。我看到了Google Play服務包版本5084032的崩潰。這種罕見情況可能是由於我的MapView的可見性發生動態變化而引起的。

爲了解決這個問題,我嵌入onGlobalLayout()GoogleMap.OnMapLoadedCallback

if (mapView.getViewTreeObserver().isAlive()) { 
    mapView.getViewTreeObserver().addOnGlobalLayoutListener(
    new ViewTreeObserver.OnGlobalLayoutListener() { 
     @Override 
     @SuppressWarnings("deprecation") 
     public void onGlobalLayout() { 
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 
       mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } else { 
       mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 
      } 
      try { 
       map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 5)); 
      } catch (IllegalStateException e) { 
       map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { 
        @Override 
        public void onMapLoaded() { 
         Log.d(LOG_TAG, "move map camera OnMapLoadedCallback"); 
         map.moveCamera(CameraUpdateFactory 
          .newLatLngBounds(bounds, 5)); 
        } 
       }); 
      } 
     } 
    }); 
} 
+0

有意思的是:如果我將'mapView.getViewTreeObserver()'重構爲**局部變量**,則會發生以下**錯誤**:*「IllegalStateException:此ViewTreeObserver不是活着,再次調用getViewTreeObserver()「*。你試過這個嗎? – JJD

+0

如果mapView.getViewTreeObserver()。isAlive()碰巧是錯誤的,我會爲map.setOnMapLoadedCallback() – southerton

1

如果您需要等待可能的用戶交互來啓動,使用OnMapLoadedCallback如前面的答案中描述。但是,如果您只需要爲地圖提供默認位置,則不需要這些答案中列出的任何解決方案。 MapFragmentMapView都可以在開始時接受GoogleMapOptions,這可以提供默認位置。唯一的竅門是不要直接將它們包含在佈局中,因爲系統會在沒有選項的情況下調用它們,而是動態初始化它們。

使用此在您的佈局:

<FrameLayout 
    android:id="@+id/map" 
    android:name="com.google.android.gms.maps.SupportMapFragment" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" /> 
onCreateView()

並更換片段:

GoogleMapOptions options = new GoogleMapOptions(); 
options.camera(new CameraPosition.Builder().target(location).zoom(15).build()); 
// other options calls if required 

SupportMapFragment fragment = (SupportMapFragment) getFragmentManager().findFragmentById(R.id.map); 
if (fragment == null) { 
    FragmentTransaction transaction = getFragmentManager().beginTransaction(); 
    fragment = SupportMapFragment.newInstance(options); 
    transaction.replace(R.id.map, fragment).commit(); 
    getFragmentManager().executePendingTransactions(); 
} 
if (fragment != null) 
    GoogleMap map = fragment.getMap(); 

除了是更快地啓動,就沒有世界的地圖顯示第一和攝像機移動第二。地圖將直接從指定位置開始。

9

這是我的修復,只是等待直到地圖加載在那種情況下。

final int padding = getResources().getDimensionPixelSize(R.dimen.spacing);  

try { 
    mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(pCameraBounds, padding)); 
} catch (IllegalStateException ise) { 

    mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { 

     @Override 
     public void onMapLoaded() { 
      mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(pCameraBounds, padding)); 
     } 
    }); 
} 
+1

這個工作。 –

5
map.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(),10)); 

利用這一點,工作了MEE

+0

這只是地圖的中心,但不會動態調整縮放到邊界。 –

+0

我們可以使用「LatLngBounds.Builder」爲更多的matkers計算邊界。 –

+0

@RameshBhupathi但它仍然保持一箇中心和縮放地圖視圖。 – Tima

0

我發現這個工作,比其他解決方案更簡單:

private void moveMapToBounds(final CameraUpdate update) { 
    try { 
     if (movedMap) { 
      // Move map smoothly from the current position. 
      map.animateCamera(update); 
     } else { 
      // Move the map immediately to the starting position. 
      map.moveCamera(update); 
      movedMap = true; 
     } 
    } catch (IllegalStateException e) { 
     // Map may not be laid out yet. 
     getWindow().getDecorView().post(new Runnable() { 
      @Override 
      public void run() { 
       moveMapToBounds(update); 
      } 
     }); 
    } 
} 

這將嘗試在呼叫佈局已經運行之後再次。可能可能包括避免無限循環的安全。

8

接受的答案不適用於我,因爲我必須在我的應用程序的各個位置使用相同的代碼。

我根據Lee提出的指定地圖大小的建議,創建了一個簡單的解決方案,而不是等待相機改變。這是萬一你的地圖是屏幕的大小。

// Gets screen size 
int width = getResources().getDisplayMetrics().widthPixels; 
int height = getResources().getDisplayMetrics().heightPixels; 
// Calls moveCamera passing screen size as parameters 
map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), width, height, 10)); 

希望它可以幫助別人!

4

我使用了適用於最新版Google地圖SDK(9.6+)和基於onCameraIdleListener的不同方法。正如我目前所看到的,它總是在onMapReady之後調用的回調方法onCameraIdle。所以,我的方法是這樣的一段代碼(考慮到它放在Activity):

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    // set content view and call getMapAsync() on MapFragment 
} 

@Override 
public void onMapReady(GoogleMap googleMap) { 
    map = googleMap; 
    map.setOnCameraIdleListener(this); 
    // other initialization stuff 
} 

@Override 
public void onCameraIdle() { 
    /* 
     Here camera is ready and you can operate with it. 
     you can use 2 approaches here: 

     1. Update the map with data you need to display and then set 
     map.setOnCameraIdleListener(null) to ensure that further events 
     will not call unnecessary callback again. 

     2. Use local boolean variable which indicates that content on map 
     should be updated 
    */ 
}