web-dev-qa-db-ja.com

CameraUpdateFactory.newLatLngBoundsを使用したmoveCameraがクラッシュする

新しい 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オブジェクトにマーカーを描画するのに最適な場所ですか、それともマーカーを描画してカメラの位置を別の場所に設定する必要がありますか?

92
Lee

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)

これにより、境界ボックスの事前レイアウトを指定できました。

49
Lee

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);
    }
});
132
Paul Annekov

これらの答えは結構ですが、私は別のアプローチ、より単純なアプローチを採用します。マップがレイアウトされた後にのみメソッドが機能する場合は、待ってください:

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

解決策はそれよりも簡単です。

// 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をキャッチし、代わりにビューでグローバルリスナーを使用します。他の方法を使用してバインドサイズを事前に設定(事前レイアウト)することは、画面デバイスではなく、ビューのサイズを計算する必要があることを意味します。マップでフルスクリーンに移動し、フラグメントを使用しない場合にのみ一致します。

34
Daniele Segato

これは私の修正です。その場合、マップがロードされるまで待ちます。

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));
        }
    });
}
14
pl3kn0r

マーカーの追加と削除はレイアウト完了前に行うことができますが、カメラの移動はできません(OPの回答に記載されているnewLatLngBounds(boundary, padding)を使用する場合を除く)。

おそらく、最初のカメラ更新を実行するのに最適な場所は、 Googleのサンプルコード に示すように、ワンショットOnGlobalLayoutListenerを使用することです。 MarkerDemoActivity.JavasetUpMap()からの次の抜粋を参照してください。

// 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));
        }
    });
}
14
StephenT

アプリのさまざまな場所で同じコードを使用しなければならなかったため、受け入れられた答えは私には機能しません。

カメラが変わるのを待つ代わりに、マップサイズを指定するという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));

それが他の誰かに役立つことを願っています!

11
Teo Inke

受け入れられた答えは、コメントで指摘されているように、少しハッキーです。実装した後、分析でIllegalStateExceptionログの許容量を超えていることに気付きました。私が使用した解決策は、OnMapLoadedCallbackを実行するCameraUpdateを追加することです。コールバックはGoogleMapに追加されます。マップが完全に読み込まれた後、カメラの更新が実行されます。

これにより、マップはカメラの更新を実行する前にズームアウト(0,0)ビューを短時間表示します。しかし、これはクラッシュを引き起こしたり、文書化されていない動作に依存したりするよりも許容できると思います。

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

これを使って私のために働いた

5
Ramesh Bhupathi

ごくまれに、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));
                    }
                });
            }
        }
    });
}
4
Xingang Huang

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
    */
}
4
Viacheslav

OnMapReadyとonGlobalLayoutの2つのコールバックを組み合わせて、両方のイベントがトリガーされたときにのみ放出される1つの単一のobservableを作成する方法を作成しました。

https://Gist.github.com/abhaysood/e275b3d0937f297980d14b439a8e0d4a

3
Abhay Sood

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)を使用して、マップの寸法を手動で提供してください。

それはかなりまともな解決策だと思います。それが誰かを助けることを願っています。

2
unmultimedio

可能なユーザーインタラクションの開始を待つ必要がある場合は、前の回答で説明したようにOnMapLoadedCallbackを使用します。ただし、必要なのがマップのデフォルトの場所を提供することだけである場合は、それらの回答に記載されている解決策は必要ありません。 MapFragmentMapViewはどちらも、開始時に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番目にカメラが移動します。マップは指定された場所から直接始まります。

1
Gábor

私はこれが機能し、他のソリューションよりも簡単であることがわかりました:

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);
            }
        });
    }
}

これは、レイアウトの実行後に呼び出しを再試行します。おそらく無限ループを回避するための安全性を含めることができます。

0
John Patterson

なぜこのようなものを使用しないのですか:

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);
}
0
Hristo Lalev

OnCameraChangeListener()は非推奨 で述べたように、setOnCameraChangeListenerは非推奨になりました。したがって、次の3つのmtehodsのいずれかに置き換える必要があります。

  • GoogleMap.OnCameraMoveStartedListener
  • GoogleMap.OnCameraMoveListener
  • GoogleMap.OnCameraIdleListener

私の場合、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)を保持していたためです。

0
CoolMind

別のアプローチは次のようになります(最上位のビューがrootContainerという名前のFrameLayoutであると仮定します。ただし、タイプや名前に関係なく常に最上位のコンテナを選択する限り機能します):

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

layoutDonetrueである場合にのみ機能するようにカメラ関数を変更すると、余分な関数を追加したり、ロジックをlayoutListenerハンドラーに結び付けたりすることなく、すべての問題を解決できます。

0
Machinarius

Google Mapsリポジトリには、活用できるヘルパークラスがあります。レイアウトとマップの両方が準備できるのを待ってから、GoogleMapでコールバックを通知します。

元のソースは次のとおりです。

https://github.com/googlemaps/Android-samples/blob/7ee737b8fd6d39c77f8b3716ba948e1ce3730e61/ApiDemos/Java/app/src/main/Java/com/example/mapdemo/OnMapAndViewReadyListener.Java

Kotlinの実装もあります。

https://github.com/googlemaps/Android-samples/blob/master/ApiDemos/kotlin/app/src/main/Java/com/example/kotlindemos/OnMapAndViewReadyListener.kt

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);
    }
}
}
0
dazza5000