Salzburg Research Blog

Custom Android LayoutManager

In order to lay out your views in an Android App, Android provides you with a number of different Layout Managers. RelativeLayout, for example, allows Views to be placed in relation to the bounds of their parents, as well as in relation to each other. TableLayout and GridLayout present Views in Rows and Columns, with TableLayout allowing views to span multiple columns, and GridLayout to span multiple rows and columns. These pre-defined Layout Managers cover a large number of everyday situations. However, if you cannot find a layout fulfilling your needs, it is actually quite simple to define your own.

For one of our applications I needed a layout similar to GridLayout: Views put into rows and columns, with the option to span multiple rows and columns. However, each row and column should have the same height and width, respectively. So, if the full width available to a layout is x pixel, and the layout has n columns, each column should be exactly x/n pixel. The following image shows a simple example.

So, why did I not use GridLayout mentioned above? GridLayout aligns Views in rows and columns and allows them to span multiple cells. Views are laid out according to the layout parameters specified for each view. The Layout is also dealing with excess space. However, it is not quite as simple to define a layout where each row and column is exactly the same size. Well, that was what I needed so I defined my own layout that I named FixedCellGridLayout.

A Layout in Android consists of:

Styling Attributes for the layout class, specified using a <declare-styleable> resource element

The layout class, a (direct or indirect) subclass of android.view.ViewGroup

And, if you want to use custom layout parameters for the layout:

The layout parameters class, a subclass of ViewGroup.LayoutParameter

Styling attributes for the layout parameters

Styling Attributes

These are custom attributes for your view that are used in the specification in a layout xml file. For the FixedCellGridLayout the following definition is used:

<declare-styleable name="FixedCellGridLayout">

<!-- The number of rows to create. -->
<attr name="rowCount" format="integer" />
<!-- The number of columns to create. -->
<attr name="columnCount" format="integer" />

</declare-styleable>

This means that our new Layout defines two additional attributes: rowCount and columnCount, i.e. the number of rows and columns in the layout.

Layout class implementation

The base class for a layout (ViewGroup) does much of the basic work of the layout as a View, so in general it is enough to implement two methods[1]:

onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
Called by the layouting process to determine the eventual size of the view that is rendered, and used by a Layout to request all child views to measure themselves. In other words, the layout has to calculate the place available to itself, and, in this special case, to calculate the size of each of its children. The first part is easy: since the layout does not have to do anything special it simply forwards the call to super.onMeasure – letting the Viewgroup implementation do its work. Using the results of the call (getMeasuredWidth and getMeasuredHeight) it can now start to measure all the children, taking the padding for this view, and the layout margins for the children into account. The important part: since this layout manager forces the layout on the child views, it needs them to measure themselves with the exact specification calculated (indicated by MeasureSpec.EXACTLY).

onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)
This is the final pass of the layouting process. Here, the exact coordinates for the borders of the child views are calculated, again taking layout margins of its children into account.

Custom Layout Parameters

Since I allow margins for the child views in the Layout, FixedCellGridLayout.LayoutParams extends ViewGroup.MarginLayoutParams. The implementation itself is quite simple: a constructor, and four additional properties which specify the rows and columns the view should be put into. Similarly to our base GridLayout, this layout allows a row and column index, and a row and column span, but handles it a bit differently, as specified above. Additionally, we provide default values for layout_width and layout_height, since this implementation ignores those parameters.

You’ll notice when looking at the layout xml file, that the custom attributes defined for the Layout and the Layout parameters are in a namespace different then “http://schemas.android.com/apk/res/android” (the “android” namespace alias). This namespace contains all attributes predefined by the system. Using this namespace for custom attributes would show up as an error and would not actually build. To tell the system that the parameters are custom, you have to use the ”http://schemas.android.com/apk/res-auto” namespace (the namespace alias is actually up to you – most of the examples for custom styling use the “app” alias, so I used it too), but only if you are using gradle.

If you are not using gradle to build the app, you’ll actually have to provide a more detailed namespace for the attributes that also includes the package name of the custom library used, for example:

[1] All code examples are implemented using Kotlin, but should be understandable even if you do not know the language.

Teilen:

Harald Rieser

Harald Rieser is a researcher and software developer at the Mobile and Web-based Information Systems (MOWI) group at Salzburg Research.
If he finds the time, he develops software prototypes and tools for the Android platform, primarely for AAL themed projects.