Creating Custom Layouts for Android

There are a lot of great articles on Android development on the web but one field that doesn’t feel explored enough is creating custom Layouts. For one thing, the Android framework does spoil you with a bunch of layouts that usually fit most of the usually designed interfaces. The problem comes when faced with slightly unconventional UIs. A basic knowledge of how layouts work may help you avoid creating a mess of nested layouts when a quick custom one may have sufficed. The design below for example was accomplished with one custom layout object.

I have to admit I am not a fan of some fundamental choices made on the UI architecture side. For one thing Layouts are Views (or more specifically ViewGroups) themselves and not only position and size the elements they are supposed to but also add them as children to themselves. This means that if you wanted to create an experience where you maybe started with a grid of photos and when the user clicks on one of them, lay the photos out in a row, you can’t really do that well since the photos have to be unparented from one View (the GridLayout) and then added as children to another. And don’t hope for any animations in between. In the coming months I hope to create an open source project for some custom layouts that separated a views parent from its layout (Adobe Flex went the same route between Flex 3 and Flex 4. Flex 3 had HBoxes, VBoxes etc but were deprecated in Flex 4 with Spark Layouts that could be attached dynamically. If you are a Flex software engineer, the Android architecture will look very familiar).

But this post explains the layout architecture as is. So lets begin. The attached code blocks are from a custom Layout example I wrote and is available on Github. Its a very simplistic LinearLayout that sizes its children equally. You can grab the github project here.

The base class for a Layout is a ViewGroup that basically extends a View and has hooks for things like addView etc. To create a custom ViewGroup, the only method you need to override is onLayout. The onLayout is triggered after the ViewGroup itself has finished laying itself out inside its own container ViewGroup and is now responsible for laying out its children. It should call the layout method on all of its children to now position and size them (the left and top parameters will determine the child view’s x and y and the right and bottom will determine its width (right – left) and height (top-bottom).

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
     int itemWidth = (r-l)/getChildCount();
     for(int i=0; i< this.getChildCount(); i++){
         View v = getChildAt(i);
	 v.layout(itemWidth*i, t, (i+1)*itemWidth, b);
         v.layout(itemWidth*i, 0, (i+1)*itemWidth, b-t); (Fixed bug mentioned by Nathaniel Wolf in the comments below)
     }
}

One thing to note is that at this point you are playing at the pixel level and not with the pixel abstraction units like dips. But besides that you should be fine.

The problem here is that while this will layout any simple views (i.e. Views that aren’t layouts themselves), any child layout objects aren’t visible at all. This is because the child layouts have no idea till that time how they should lay out their own children since they haven’t been measured at all and return a measured width and height of 0. To do a layout correctly, you need to make sure that you also override the onMeasure method in your layout and call the measure method on each of your children appropriately.

During measure, you need to first calculate your own measuredWidth and measuredHeight and then based on that tell your children how they need to size themselves. For example a horizontal LinearLayout might say “My measuredWidth is 100 plxels and I have two children so each must measure exactly 50 pixels. This it would do my passing a MeasureSpec object which defines how the child should interpret the measurement its receiving: either EXACTLY, AT MOST or UNSPECIFIED. The child view then uses those cues to create its own measuredWidth and measuredHeight (by usually calling setMeasuredDimension at some point in the onMeasure).

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){

     //At this time we need to call setMeasuredDimensions(). Lets just 
     //call the parent View's method 
     //(see https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/View.java) 
     //that does:
     //setMeasuredDimension(getDefaultSize(
     //                       getSuggestedMinimumWidth(), widthMeasureSpec),
     //                    getDefaultSize(
     //                       getSuggestedMinimumHeight(), heightMeasureSpec));
     //		

      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int wspec = MeasureSpec.makeMeasureSpec(
                  getMeasuredWidth()/getChildCount(), MeasureSpec.EXACTLY);
      int hspec = MeasureSpec.makeMeasureSpec(
                  getMeasuredHeight(), MeasureSpec.EXACTLY);
      for(int i=0; i<getChildCount(); i++){
         View v = getChildAt(i);
         v.measure(wspec, hspec);
      }
}

Note that the measuredWidth and measuredHeight are cues for the parent layout when its laying out the children. It might still decide to ignore the measured width and height and lay them out as it feels since its up to it to define the left, right, top, bottom values during onLayout but a good citizen of the layout world will probably not ignore them.

Also I am just getting into this so I may have missed something so please drop me a comment or let me know on Google+ and we can have a discussion.

[Update 1] I have written an extension to this post that includes adding custom attibutes and layoutparams to your layout class

[Update 2] Great talk on this topic from Google I/O 2013

Creating Custom Layouts for Android

17 thoughts on “Creating Custom Layouts for Android

  1. Aleksandr Sakharuk says:

    This is the only article about custom layouts in android in google top10. Even android site haven’t anything like this. Thanks for help!) It seems to be not very hard to write custom layouts. Will read your second arrticle too.

    Like

  2. Great blog and a lot more helpful than the android documentation, thanks a lot! 🙂

    But I’ve got a little patch for your example, because *this* is working already on api level 2 as expected [tested] and didn’t want to sign up on github:

    diff –git a/AndroidManifest.xml b/AndroidManifest.xml
    index 62c5f8c..0c5b5e4 100644
    — a/AndroidManifest.xml
    +++ b/AndroidManifest.xml
    @@ -4,7 +4,7 @@
    android:versionCode=”1″
    android:versionName=”1.0″ >


    +

    <application
    android:icon="@drawable/ic_launcher"

    Like

  3. Great blog and a lot more helpful than the android documentation, thanks a lot! 🙂

    But I’ve got a little patch for your example, because *this* is working already on api level 2 as expected [tested] and didn’t want to sign up on github:

    diff –git a/AndroidManifest.xml b/AndroidManifest.xml
    index 62c5f8c..0c5b5e4 100644
    — a/AndroidManifest.xml
    +++ b/AndroidManifest.xml
    @@ -4,7 +4,7 @@
    android:versionCode=”1″
    android:versionName=”1.0″ >


    +

    <application
    android:icon="@drawable/ic_launcher"

    Like

  4. in onLayout you are calling: 
    v.layout(itemWidth*i, t, (i+1)*itemWidth, b);but this will offset your view t pixels down from the top of your layout.This should be:v.layout(itemWidth*i, 0, (i+1)*itemWidth, b-t);It works in your example code because t == 0

    Like

  5. Anonymous says:

    Thanks a lot for this article! This helped me get my vertically centered text working (I hadn’t known about onMeasure).

    Like

  6. yeet says:

    nice article but I think you are wrong in onMeasure Part. Unless your measure specs are Measurespec.EXACTLY, you should measure yourself **after** measuring your children.

    assume you are given wrap content. that means your measurement will require measuring children first.
    so for onMeasure:
    you should first read your specs,
    create specs for children accordingly
    then set your own measurements.

    There is a great performance benefit on writing custom layouts even if you can accomplish the same UI using android layouts.
    During implementation, you know which view is where relative to others etc so you can avoid lots of unnecessary operations. (e.g. most android layouts need to measure their children twice)

    see relative layout’s on measure as an example. you’ll notice the call to setMeasuredDimensions at the end.
    http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3_r2.1/android/widget/RelativeLayout.java#RelativeLayout.onMeasure%28int%2Cint%29

    thanks for the article again and I agree Android (Google) has to guide developers to write their own layouts for better performance.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s