Tuesday 27 September 2011

Android Annoyances: Finding a View Size

I find Android development a lot more time consuming than iOS development of similar complexity, and the two main reasons for this are the unintuitive API and poor documentation. Here is one simple example.

You can create an Android user user interface either declaratively, in an XML layout, or in code. This works fine in many cases, but at some point you’ll want to do something more creative, for example to modify an existing layout dynamically depending on some options. For this, you’ll probably need to know the sizes and positions of the already existing parts of the user interface.

Considering the wide variability of screen sizes and densities of Android devices, finding out where exactly a view is located and which exactly size does it have should be an easy task, shouldn’t it?

Let’s say, I want to know the height of a view in order to do some adjustments before the user sees the activity. If we check Android online documentation, we’ll find that every View has a method named getHeight(). So this is what we can use, right? The comment says: “Return the height of your view”. Sure, this is exactly what we want.

Let’s create a standard Android project and then modify it a little bit. Here is the layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  android:id="@+id/hello"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

And here is the code of the activity:

public class SizeTestActivity extends Activity 
{
    private View hello;

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        hello = findViewById(R.id.hello);

        Log.i("In onCreate", "" + hello.getHeight());
    }
}

If you run this code though, you will discover that the height of the hello view is exactly 0. Hmmmm… Okay, Android needs some time to complete the layout, it is probably just not ready to report a view’s dimensions yet. But surely, there is a life cycle method that runs a bit later and in which we should be able to find out the view’s height. Let’s look into the documentation again.

We’ll find that the next method that runs after onCreate is onStart, and it is “Called when the activity is becoming visible to the user.”. Surely, the layout should be completed by then, and we’ll certainly find the view’s height in that method. Let’s add it to the activity:

@Override
public void onStart()
{
    super.onStart();

    Log.i("In onStart", "" + hello.getHeight());
}

Run the app, and you’ll see that the view’s height is… 0.

Okay, but there is still one more lifecycle method that runs before the user sees the interface, it is called onResume, and the documentation says about it: “Called when the activity will start interacting with the user. At this point your activity is at the top of the activity stack, with user input going to it.”. Hey, there is no way the view’s dimensions will still be unknown at that point. Let’s try:

@Override
public void onResume()
{
    super.onStart();

    Log.i("In onResume", "" + hello.getHeight());
}

Guess what you’ll see in the log? 0.

Well, maybe there is something wrong with the view that I am trying to measure? Does it have any height at all? Let’s try this: add to the layout a button:

<Button 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:text="Check Size"
    android:onClick="checkSize"
    />

Then add to the activity a method to handle the button’s clicks:

public void checkSize(View v)
{
    Log.i("In checkSize", "" + hello.getHeight());
}

Click the button when the activity is running, and you’ll see that the view’s height is actually 29, not 0. So is there any possibility to know this before the user can interact with the activity? There is actually a solution. It’s a bit ugly, and you’ll need to spend some time searching the forums in order to find it, but it does exist. Modify the onCreate method like this:

@Override
public void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    hello = findViewById(R.id.hello);

    hello.post(new Runnable() 
    {   
        @Override
        public void run() {
            Log.i("In onCreate", "" + hello.getHeight());
        }
    });
}

You will see that the height is now reported properly, and that line of code in the Runnable runs a split of a second after onResume.

Well, this is exactly what I mean by unintuitive API and poor documentation.

4 comments:

  1. Thank you so much, it is really useful for me

    ReplyDelete
  2. The correct way is to call getViewTreeObserver().addOnGlobalLayoutListener which calls you back when the layout changes. This can theoretically happen multiple times.

    The documentation is pretty decent.. I've worked with a lot of APIs where you're working completely blind on this stuff.

    ReplyDelete
  3. Ahhh, thanks so much. I couldn't use getViewTreeObserver().addOnGlobalLayoutListener
    because of some AsyncTask from a framework, so I wasn't able to set dimensions on a ViewGroup of an other component.
    Found this after 2 days of searching. Thx a lot!

    ReplyDelete
  4. Alexander, you're correct. Android API organisation regarding the UI is super goofed up and unintuitive and also they have a poor poor documentation. Also, they keep on adding and removing methods every now and then, which gives a lot of deprecation errors.

    Also, if they have brought in the XML coding for UI ( which I think is just a waste ) just with the idea to separate the UI code from the functionality code, then why did these Android guys din't apply the same logic over their documentation too, I just wonder... They could have kept the XML docs and Code docs separate, instead of mingling both in each other.

    One of the funniest stuff I found in Android UI is that you have an addView() method only in a ViewGroup but not in a View. Now, if you want to a View inside a View and that inside another view, to create a chain of views, that you have to put each view into a super ViewGroup. Now, on the other hand Android guys say that you shouldn't be using too deep a ViewGroup tree. Lol, Android Guys, you API and your guidance are totally mutually exclusive.

    I also did a stress test comparison of 500 views added in a single superview on both android and iOS. And the results were -> IOS handles all the interactivty on the the views decently, but android jams on just 100 views added over the contentView of its activity. Really Android guys!! make me your CEO and I will bring organisation and peace in the whole android community over the world. :D :))

    ReplyDelete