新しい Android Google Maps API を使用しています。
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オブジェクトにマーカーを描画するのに最適な場所ですか、それともマーカーを描画してカメラの位置を別の場所に設定する必要がありますか?
OK ここに記載 このAPIはレイアウト前に使用できません。
使用する正しいAPIは次のとおりです。
注:マップがレイアウトされた後にカメラを移動するために使用される場合、より単純なメソッドnewLatLngBounds(boundary、padding)のみを使用してCameraUpdateを生成します。レイアウト中に、APIは、境界ボックスを正しく投影するために必要なマップの表示境界を計算します。これに対して、APIは渡す引数から表示境界を計算するため、マップがレイアウトされる前であっても、より複雑なメソッドnewLatLngBounds(boundy、width、height、padding)によって返されるCameraUpdateをいつでも使用できます。
問題を解決するために、画面サイズを計算し、幅と高さを提供しました
public static CameraUpdate newLatLngBounds(
LatLngBounds bounds, int width, int height, int padding)
これにより、境界ボックスの事前レイアウトを指定できました。
OnCameraChangeListenerで単純なnewLatLngBoundsメソッドを使用できます。すべてが完全に機能し、画面サイズを計算する必要はありません。このイベントは、マップサイズの計算後に発生します(わかりました)。
例:
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);
}
});
これらの答えは結構ですが、私は別のアプローチ、より単純なアプローチを採用します。マップがレイアウトされた後にのみメソッドが機能する場合は、待ってください:
map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
@Override
public void onMapLoaded() {
map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
}
});
解決策はそれよりも簡単です。
// 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
をキャッチし、代わりにビューでグローバルリスナーを使用します。他の方法を使用してバインドサイズを事前に設定(事前レイアウト)することは、画面デバイスではなく、ビューのサイズを計算する必要があることを意味します。マップでフルスクリーンに移動し、フラグメントを使用しない場合にのみ一致します。
これは私の修正です。その場合、マップがロードされるまで待ちます。
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));
}
});
}
マーカーの追加と削除はレイアウト完了前に行うことができますが、カメラの移動はできません(OPの回答に記載されているnewLatLngBounds(boundary, padding)
を使用する場合を除く)。
おそらく、最初のカメラ更新を実行するのに最適な場所は、 Googleのサンプルコード に示すように、ワンショットOnGlobalLayoutListener
を使用することです。 MarkerDemoActivity.JavaのsetUpMap()
からの次の抜粋を参照してください。
// 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));
}
});
}
アプリのさまざまな場所で同じコードを使用しなければならなかったため、受け入れられた答えは私には機能しません。
カメラが変わるのを待つ代わりに、マップサイズを指定するという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));
それが他の誰かに役立つことを願っています!
受け入れられた答えは、コメントで指摘されているように、少しハッキーです。実装した後、分析でIllegalStateException
ログの許容量を超えていることに気付きました。私が使用した解決策は、OnMapLoadedCallback
を実行するCameraUpdate
を追加することです。コールバックはGoogleMap
に追加されます。マップが完全に読み込まれた後、カメラの更新が実行されます。
これにより、マップはカメラの更新を実行する前にズームアウト(0,0)ビューを短時間表示します。しかし、これはクラッシュを引き起こしたり、文書化されていない動作に依存したりするよりも許容できると思います。
map.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(),10));
これを使って私のために働いた
ごくまれに、MapViewレイアウトは終了しますが、GoogleMapレイアウトは終了せず、ViewTreeObserver.OnGlobalLayoutListener
自体がクラッシュを止めることはできません。 Google Play Servicesパッケージバージョン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));
}
});
}
}
});
}
Google Maps SDKの最新バージョン(9.6以降)で動作するさまざまなアプローチを使用し、 onCameraIdleListener に基づいています。これまで見てきたように、onCameraIdle
の後に常に呼び出されるコールバックメソッドonMapReady
です。したがって、私のアプローチは次のコードのように見えます(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
*/
}
OnMapReadyとonGlobalLayoutの2つのコールバックを組み合わせて、両方のイベントがトリガーされたときにのみ放出される1つの単一のobservableを作成する方法を作成しました。
https://Gist.github.com/abhaysood/e275b3d0937f297980d14b439a8e0d4a
OK同じ問題に直面しています。 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...
}
ちなみに、膨らませてから戻る前に、onCreateView
からresetCamera()
を呼び出しています。
これは、例外を最初にキャッチし(マップが「サイズを取得する」という言い回しとして...)、その後、カメラをリセットする必要がある場合、マップにはすでにサイズがあり、パディングを通して。
問題は documentation で説明されています。
マップのレイアウトが完了するまで、このカメラの更新でカメラを変更しないでください(このメソッドが適切な境界ボックスとズームレベルを正しく決定するには、マップにサイズが必要です)。それ以外の場合は、
IllegalStateException
がスローされます。マップを使用するには不十分です(つまり、getMap()
はnull以外のオブジェクトを返します)。マップを含むビューも、その寸法が決定されるようにレイアウトされている必要があります。これが発生したことを確認できない場合は、代わりにnewLatLngBounds(LatLngBounds, int, int, int)
を使用して、マップの寸法を手動で提供してください。
それはかなりまともな解決策だと思います。それが誰かを助けることを願っています。
可能なユーザーインタラクションの開始を待つ必要がある場合は、前の回答で説明したようにOnMapLoadedCallback
を使用します。ただし、必要なのがマップのデフォルトの場所を提供することだけである場合は、それらの回答に記載されている解決策は必要ありません。 MapFragment
とMapView
はどちらも、開始時にGoogleMapOptions
を受け入れて、デフォルトの場所を提供できます。唯一の秘trickは、システムがオプションなしでそれらを呼び出すため、レイアウトに直接含めるのではなく、動的に初期化することです。
レイアウトでこれを使用します。
<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();
起動が高速であることに加えて、最初に世界地図が表示されず、2番目にカメラが移動します。マップは指定された場所から直接始まります。
私はこれが機能し、他のソリューションよりも簡単であることがわかりました:
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);
}
});
}
}
これは、レイアウトの実行後に呼び出しを再試行します。おそらく無限ループを回避するための安全性を含めることができます。
なぜこのようなものを使用しないのですか:
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), padding);
try {
map.moveCamera(cameraUpdate);
} catch (Exception e) {
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
cameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), width, height, padding);
map.moveCamera(cameraUpdate);
}
OnCameraChangeListener()は非推奨 で述べたように、setOnCameraChangeListener
は非推奨になりました。したがって、次の3つのmtehodsのいずれかに置き換える必要があります。
私の場合、OnCameraIdleListener
を使用し、内部でそれを削除しました。これは、移動時に何度も呼び出されるためです。
googleMap.setOnCameraIdleListener {
googleMap.setOnCameraIdleListener(null) // It removes the listener.
googleMap.moveCamera(track)
googleMap.cameraPosition
clusterManager!!.cluster()
// Add another listener to make ClusterManager correctly zoom clusters and markers.
googleMap.setOnCameraIdleListener(clusterManager)
}
更新
私はプロジェクトでgoogleMap.setOnCameraIdleListener
を削除しました。マップが表示されたときに呼び出されなかったが、googleMap.setOnCameraIdleListener(clusterManager)
を保持していたためです。
別のアプローチは次のようになります(最上位のビューがrootContainer
という名前のFrameLayoutであると仮定します。ただし、タイプや名前に関係なく常に最上位のコンテナを選択する限り機能します):
((FrameLayout)findViewById(R.id.rootContainer)).getViewTreeObserver()
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
layoutDone = true;
}
});
layoutDone
がtrue
である場合にのみ機能するようにカメラ関数を変更すると、余分な関数を追加したり、ロジックをlayoutListener
ハンドラーに結び付けたりすることなく、すべての問題を解決できます。
Google Mapsリポジトリには、活用できるヘルパークラスがあります。レイアウトとマップの両方が準備できるのを待ってから、GoogleMapでコールバックを通知します。
元のソースは次のとおりです。
Kotlinの実装もあります。
public class OnMapAndViewReadyListener implements OnGlobalLayoutListener, OnMapReadyCallback {
/** A listener that needs to wait for both the GoogleMap and the View to be initialized. */
public interface OnGlobalLayoutAndMapReadyListener {
void onMapReady(GoogleMap googleMap);
}
private final SupportMapFragment mapFragment;
private final View mapView;
private final OnGlobalLayoutAndMapReadyListener devCallback;
private boolean isViewReady;
private boolean isMapReady;
private GoogleMap googleMap;
public OnMapAndViewReadyListener(
SupportMapFragment mapFragment, OnGlobalLayoutAndMapReadyListener devCallback) {
this.mapFragment = mapFragment;
mapView = mapFragment.getView();
this.devCallback = devCallback;
isViewReady = false;
isMapReady = false;
googleMap = null;
registerListeners();
}
private void registerListeners() {
// View layout.
if ((mapView.getWidth() != 0) && (mapView.getHeight() != 0)) {
// View has already completed layout.
isViewReady = true;
} else {
// Map has not undergone layout, register a View observer.
mapView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
// GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later.
mapFragment.getMapAsync(this);
}
@Override
public void onMapReady(GoogleMap googleMap) {
// NOTE: The GoogleMap API specifies the listener is removed just prior to invocation.
this.googleMap = googleMap;
isMapReady = true;
fireCallbackIfReady();
}
@SuppressWarnings("deprecation") // We use the new method when supported
@SuppressLint("NewApi") // We check which build version we are using.
@Override
public void onGlobalLayout() {
// Remove our listener.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
isViewReady = true;
fireCallbackIfReady();
}
private void fireCallbackIfReady() {
if (isViewReady && isMapReady) {
devCallback.onMapReady(googleMap);
}
}
}