2011-10-27

Stretching LinearLayouts - Using weights correctly

Android applications may run on a huge set of devices, each with their own specifications. Screens in particular are a very important component as they're the bridge between the user and the application. Making sure your application looks good on different screens is critical to its success. In this post I'll write about a technique that will help you achieving that: layout stretching.

Choosing the correct layout
You should know the differences between the available layouts, because that can save you a lot of time - you can get some specific behaviour just by using the correct one. Performance issues also take place on this decision. So make sure you stop by the Android's official documentation to have a look at many of these differences. This post will focus on LinearLayout.

LinearLayout
From the official documentation: "LinearLayout aligns all children in a single direction — vertically or horizontally, depending on how you define the orientation attribute. All children are stacked one after the other, so a vertical list will only have one child per row, no matter how wide they are, and a horizontal list will only be one row high (the height of the tallest child, plus padding). A LinearLayout respects margins between children and the gravity (right, center, or left alignment) of each child."

Stretching the LinearLayout
There are a some attributes that can be used with LinearLayout, that help out managing the stretching strategy you'd like to use. Some of them, like layout_width and layout_height are pretty straightforward, while others like layout_weight and weightSum are a little bit more complicated. Let's try to understand them in depth.'

layout_width and layout_height
These attributes are common to all layout managers and are required to be declared for every view of your layout (remember that every layout is also a View). You may use one of the following values for both of them:
  • FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which means that the view wants to be as big as its parent (minus padding);
  • WRAP_CONTENT, which means that the view wants to be just big enough to enclose its content (plus padding);
  • an exact number followed by an unit, like "100dp".
Let's play with these attributes and values to see what happens. The following layout has some buttons with different dimension settings. Try to figure out how they will appear before seeing the screenshot with the result below.
<xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">
 <Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="B1"/>
 <Button
  android:layout_width="100dp"
  android:layout_height="wrap_content"
  android:text="B2"/>
 <Button
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="B3"/>
 <Button
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:text="B4"/>
</LinearLayout>
And the result:


When fill_parent is not enough
Cool. So now let's try to make something more complex. Check out the following sketch:


The content area (blue) should grow horizontally and vertically, but leaving some space at the bottom for an action bar (green) with a fixed height. Let's try to solve this with the following layout.
<xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
 android:layout_height="fill_parent" 
 android:orientation="vertical">
 <LinearLayout
  android:id="@+id/content"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="#CED9EB"
  android:orientation="vertical"/>
 <LinearLayout
  android:id="@+id/actionBar"
  android:layout_width="fill_parent"
  android:layout_height="50dp"
  android:background="#D9EBCE"
  android:orientation="horizontal"/>
</LinearLayout>
And the result:

Where is the action bar? The Hierarchy View tool gives us the following tree:


The action bar is there, but the problem is that the content area took all the space available, leaving the action bar out of the screen. Instead of telling the content area to fill the space vertically, we need to tell it to grow dynamically. We achieve that by using weights.

Using weights
From the official documentation: "LinearLayout also supports assigning a weight to individual children. This attribute assigns an importance value to a view, and allows it to expand to fill any remaining space in the parent view. Child views can specify an integer weight value, and then any remaining space in the view group is assigned to children in the proportion of their declared weight. Default weight is zero." Now we can use weights to solve our problem. The following layout shows how it's done:
<xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">
 <LinearLayout
  android:id="@+id/content"
  android:layout_width="fill_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:background="#CED9EB"
  android:orientation="vertical"/>
 <LinearLayout
  android:id="@+id/actionBar"
  android:layout_width="fill_parent"
  android:layout_height="50dp"
  android:background="#D9EBCE"
  android:orientation="horizontal"/>
</LinearLayout>
And the result:

Now let's understand the weight attributes used on the Content Area:
  • android:layout_weight="1" - Tells the parent layout that the view should grow dynamically, taking 100% of the space available; note that this attribute does not specify any orientation.
  • android:layout_height="0dp" - Tells the parent layout that the vertical orientation should be used when growing dynamically.
  • android:layout_width="fill_parent" - Tells the parent that all available horizontal space should be used. Using "0dp" on this field is an error, because it was already used on the layout_height attribute.

Some words on performance
Layout stretching can be achieved with other layout managers. Using just LinearLayouts in you application may lead to undesired performance issues. This interesting post on Android Developers Blog states: "The Android UI toolkit offers several layout managers that are rather easy to use and, most of the time, you only need the basic features of these layout managers to implement a user interface. Sticking to the basic features is unfortunately not the most efficient way to create user interfaces. A common example is the abuse of LinearLayout, which leads to a proliferation of views in the view hierarchy. Every view, or worse every layout manager, you add to your application comes at a cost: initialization, layout and drawing become slower. The layout pass can be especially expensive when you nest several LinearLayout that use the weight parameter, which requires the child to be measured twice."

More examples using the weight attributes
In order to have a feel of the weight attributes, I suggest you play with them and try to figure out what will appear on the screen. Some tips:
Content Area and Action Bar each taking 50% of the available vertical space:
<xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">
 <LinearLayout
  android:id="@+id/content"
  android:layout_width="fill_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:background="#CED9EB"
  android:orientation="vertical"/>
 <LinearLayout
  android:id="@+id/actionBar"
  android:layout_width="fill_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:background="#D9EBCE"
  android:orientation="horizontal"/>
</LinearLayout>
Content Area taking 50% of all the available space, and Action Bar taking 25% of the available space. An empty area will appear at the bottom, with 25% height:
<xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical"
 android:weightSum="4">
 <LinearLayout
  android:id="@+id/content"
  android:layout_width="fill_parent"
  android:layout_height="0dp"
  android:layout_weight="2"
  android:background="#CED9EB"
  android:orientation="vertical"/>
 <LinearLayout
  android:id="@+id/actionBar"
  android:layout_width="fill_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:background="#D9EBCE"
  android:orientation="horizontal"/>
</LinearLayout>
Thanks for reading this post! Please leave a comment if you have a question.

1 comment:

  1. while we using more number of buttons vertically placed they are not displayed consistently on all the frames of nexus ?

    ReplyDelete