7

There's a lot of questions on this problem, but most are too specialized to answer my question.

I have a GoogleMap which I tell to fit its camera to certain bounds. Perhaps not very surprisingly, I get an error:

java.lang.IllegalStateException: Map size should not be 0. Most likely, layout has not yet occured for the map view.

So let's abstract this problem to any View.

When does this 'layout' event actually take place? onMeasure() doesn't show up in the Activity Lifecycle, for example. When is it safe to call my layout-needing method?

Maarten
  • 6,894
  • 7
  • 55
  • 90

4 Answers4

16

As the documentation says, you should use this

mapa.moveCamera(CameraUpdateFactory.newLatLngBounds(Builder.build(), 
                    this.getResources().getDisplayMetrics().widthPixels, 
                    this.getResources().getDisplayMetrics().heightPixels, 
                    50));

Where this is an activity, Instead

mapa.moveCamera(CameraUpdateFactory.newLatLngBounds(Builder.build(), 
                    50));

This works for me.

The documentation

Note: Only use the simpler method newLatLngBounds(boundary, padding) to generate a CameraUpdate if it is going to be used to move the camera after the map has undergone layout. During layout, the API calculates the display boundaries of the map which are needed to correctly project the bounding box. In comparison, you can use the CameraUpdate returned by the more complex method newLatLngBounds(boundary, width, height, padding) at any time, even before the map has undergone layout, because the API calculates the display boundaries from the arguments that you pass.

Francisco Hernandez
  • 2,378
  • 14
  • 18
  • I still get the issue using your mentioned code. I am using the call in a `FragmentActivity`. I might need to refactor my code so that I ensure it is called after layout is correctly initialized. – theblang Nov 21 '13 at 17:20
  • My problem was that I was trying to use `moveCamera` in `onConnected`, which was being called when I called, in `onResume`, `locationClient.connect()`. According to the official `MyLocationDemoActivity` in the samples, they just `requestLocationUpdates` in `onConnected`, then I use `moveCamera` in `onLocationChanged`. The one minor problem with this is that there is a brief moment when the map is elsewhere before it updates. – theblang Nov 21 '13 at 17:39
  • 1
    @FranciscoHernandez I like this solution. works well especially when you pop the backstack and have to reinitialize the map – Alan Mar 08 '16 at 20:44
9

To solve this particular problem in the updated Maps API, you should use

map.moveCamera(CameraUpdateFactory.newLatLngBounds(Builder.build(), 50));

For any other view, and older Maps API, use ViewTreeObserver.OnGlobalLayoutListener like in Simon's answer.

You can also use View.post() to add something to the View message queue, but I don't know if layout is guaranteed to have happened at the end of it (some people say it doesn't, but then even Groupon + Google do it wrong).

For a map fragment:

MapFragment.getView().post(new Runnable(){
    run() {
       ...Use the layout params...
    }
}
Maarten
  • 6,894
  • 7
  • 55
  • 90
  • 1
    This is not an an answer to the question asked. – Glenn Bech Aug 07 '13 at 13:15
  • I sometimes return to this question and think, why did Glenn Bech say that this was not an answer to the question asked, while I am the one that asked it **and** answered it. – Maarten Oct 24 '13 at 20:11
  • @GlennBech is right, this answer doesn't work with the latest Maps API. It's not sure that the layout will be finished when the `Runnable` will be executed. – gipi Nov 07 '13 at 16:08
  • Can you get a source on that? I've wondered about it for a while. The [docs](http://developer.android.com/reference/android/view/View.html#post\(java.lang.Runnable\)) are fuzzy on the what is at the end of the message queue of the `View`. I guess an [`OnGlobalLayoutListener`](http://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html#onGlobalLayout\(\)) would be the way to go then. – Maarten Nov 07 '13 at 16:22
  • This worked for me. It was the cheapest way to do it and the resolution sufficed. I, however, attempt to run the runnable three times (if the View's width is 0, it is re-ran). – pimguilherme May 22 '14 at 02:59
2

I think you mean do something after the UI is displayed.

Using a global layout listener has always worked well for me. It also has the advantage of being able to remeasure things if the layout is changed, e.g. if something is set to View.GONE or child views are added/removed.

public void onCreate(Bundle savedInstanceState)
{
     super.onCreate(savedInstanceState);

     // inflate your main layout here (use RelativeLayout or whatever your root ViewGroup type is
     LinearLayout mainLayout = (LinearLayout ) this.getLayoutInflater().inflate(R.layout.main, null); 

     // set a global layout listener which will be called when the layout pass is completed and the view is drawn
     mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(
     new ViewTreeObserver.OnGlobalLayoutListener() {
          public void onGlobalLayout() {
               // at this point, the UI is fully displayed
          }
     }
 );

 setContentView(mainLayout);

http://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html

Simon
  • 14,407
  • 8
  • 46
  • 61
1

I would use onWindowFocusChanged(boolean). Just check if the window of the activity just got the focus

@Override
public void onWindowFocusChanged(boolean hasFocus)
{
    super.onWindowFocusChanged(hasFocus);

    if( hasFocus )
    {
        // Ask for any view size here
    }
}
Xavi Gil
  • 11,460
  • 4
  • 56
  • 71