32

I have an activity in which I have 3 buttons placed alongside each other. I have used a subclass of Button that will resize the button text to prevent the text from wrapping. I would like the 3 buttons to share the same text size. In order to do this I intend to detect the button with the smallest text size and set the other 2 buttons to that text size.

The problem I have is knowing when the Activity has completed laying out its components so that I can reliably know that the resizing of the text has occurred. From the Android documentation it would appear that the latest notification in the lifecycle is onResume() but it appears that the layout hasn't completed at this point. Is there a way of receiving notification that the Activity layout has finished?

kitson
  • 321
  • 1
  • 3
  • 3
  • I got no clue but maybe you can kind of do it manually - keep checking the components to see if they're null or if the text size has changed or something; figure out a criteria that tests if it's ready. – karnok Jul 06 '11 at 15:29
  • I really think you are overthinking this... as long as you manipulate your `View`s after `setContentView` has been called, you should be OK. – Alex Lockwood Jul 20 '12 at 03:02

11 Answers11

29

I've done something similar using Tree Observers. You may have to play around with it a bit to find which view(s) it should be attached too, but this will fire once the view is set. From there, you can get the size of the text, and make whatever changes are needed.

mTestButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        // make sure it is not called anymore 
        mTestButton.getViewTreeObserver().removeGlobalOnLayoutListener(this);

        float size = mTestButton.getTextSize();
    }
});


EDIT:
The method #removeGlobalOnLayoutListener has been deprecated, but is still valid. To use both the new and old method, see this stackoverflow answer.

Community
  • 1
  • 1
Greg
  • 733
  • 6
  • 14
  • This is a lifesaver. I wanted to set the camera of a google map to a bounding box using map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds,0)); but the call was only valid if the map view was finished. Thanks – gtsouk Jan 10 '14 at 22:56
  • @gtsouk : are you using a `MapView` or a `MapFragment`? I am trying to do the same as what you did, but I don't know how I can achieve the same thing for a `MapFragment`. It doesn't have the method `getViewTreeObserver`... – ercan Jan 27 '14 at 14:14
  • 2
    @ercan use mapFragment.getView().getViewTreeObserver() – gtsouk Jan 31 '14 at 23:39
2

From what I know there is not an event or something that tells you everything is loaded. What you maybe can do is at the end of the oncreate method start a AsyncTask which runs while the width of your last view is 0. When the layout is loaded, views will have an actually size instead of 0 so you know its loaded. In the callback from the ASyncTask you then can calculate the smallest button and determine the fontsize.

matsjoe
  • 1,480
  • 12
  • 17
1

The Activity lifecycle callback methods and its window/layout are not intrinsically linked. You are, however, guaranteed that the Buttons exist so long as you manipulate them after you have called setContentView.

Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • I try to perform some animations based on a button size in onCreate and after setContentView. Nothing seems to be working. However, when I try it in the click listener of the relevant button, it works. – stdout Feb 13 '17 at 22:57
0

I'm drawing images dynamically, and wanted to set the sizes of ImageViews by LinearLayout and then use those sizes. Therefore I need to create the images after layout is complete.

I used View.post( Runnable ) at the end of onCreate, then created the images in the Runnable. I experimented a bit, and found that onStart, onResume and onPostResume occurred before layout (ImageView width was 0). The posted Runnable was executed after all of them, and after layout, because the width had been set.

public void run() {
    // acquire the ImageView
    if (image.getWidth() == 0)
        image.post( this );

    else {
        // create the images, initialize the ImageViews etc.
    }
}

The posted Runnable presumably gets posted in the UI thread after the startup procedures. The ImageViews will request layouts, and the requests are posted after the Runnable. Just in case, I check that the width has been set and repost the Runnable if it hasn't. Once the initialization is finished, the Runnable isn't posted again.

grimripper
  • 71
  • 3
0

Perhaps you could set an OnLayoutChangeListener on your buttons (or the viewgroup containing the buttons) and do the font re-sizing there? I'm not sure if that gets called before or after the layout changes have taken effect...

QRohlf
  • 2,795
  • 3
  • 24
  • 27
  • The onLayoutChange method in OnLayoutChangeListener gets called after the layout has completed. However, this interface was only introduced in API 11 (3.0). – Charles Harley Jul 08 '11 at 09:44
  • Not only that, but `OnLayoutChangeListener` is called whenever the layout is *ever* changed. So if you're dealing with dynamic content, I don't recommend it.. – Jacksonkr Sep 25 '12 at 15:47
  • A further issue is that isInLayout() will be true and changing the font, and other operations, will results in requestLayout() while already in a layout pass, which isn't fatal but emits warnings and has perf implications. – i_am_jorf Jun 09 '15 at 22:14
0

You could try overriding Activity.onWindowFocusChanged(boolean) to reliably receive notification when the activity has become visible. You can then assume that the layout has completed. One problem you might have with this approach is the Activity will be visible to the user and thus if the button text is resized then the change in size will be briefly visible to the user.

Charles Harley
  • 7,184
  • 3
  • 32
  • 38
0

Android will do the layout in two steps, measure and layout. Because you just want the size of the button you can use getMeasuredWidth instead of getWidth, as this will be available earlier.

I would recommend you to use a LinearLayout instead of manually trying to define the width of the buttons. Make sure android:weightSum is specified, especially if you're going for a non full width layout.

<LinearLayout 
    ...
    android:orientation="horizontal"
    android:weightSum="3"
    >
    <Button 
        ...
        android:layout_weight="1"
        />
    <Button 
        ...
        android:layout_weight="1"
        />
    <Button 
        ...
        android:layout_weight="1"
        />
</LinearLayout>
Erik
  • 61
  • 3
0

In order to know when a view's layout has completed, perhaps i would give it a runnable in the onCreate() method of my activity and then calling this runnable from View.onSizeChanged(). It should look like

public void onCreate() {
    view.setLayoutRunnable(new Runnable() {
    @Override
    public void run() {
        //do layout stuff...
    }
});

and then do the following in your view.

public Runnable myRunnable;
public void setLayoutRunnable(Runnable runner) {
    myRunnable = runner;
}

@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (w > 0 && h > 0 && (w != oldw || h != oldh)) {
        if (myRunnable!= null) 
            myRunnable.run();
    }
}

Hope this helps.

ehartwell
  • 1,667
  • 19
  • 18
Shafi
  • 1,368
  • 10
  • 24
-1

When I try to make horizontally stacked buttons, I just give them the layout_width of wrap_parent however give them the layout_weight of 1.

This way they all try to occupy as much space as available equally distributed by the parent layout.

A sample code would be something like this and I hope it helps:

<LinearLayout 
    android:id="@+id/buttons"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    >
    <Button 
        android:id="@+id/okButton"
        android:layout_height="wrap_content" 
        android:layout_width="wrap_content"
        android:text="OK"
        android:layout_weight="1"
        />
    <Button 
        android:id="@+id/cancelButton"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content" 
        android:text="Cancel"
        android:layout_weight="1"
        />
</LinearLayout>

-serkan

serkanozel
  • 2,947
  • 1
  • 23
  • 27
-1

You can use iswindow(). Overide it. It will be called just before onPause().

Octavian Helm
  • 39,405
  • 19
  • 98
  • 102
-2

As per the Activity Life cycle, A layout is deemed complete when onResume() gets called. This is when the user is presented with the screen. So you can blindly do stuff with buttons from this function.

Royston Pinto
  • 6,681
  • 2
  • 28
  • 46