#PerfMatters building a custom ViewGroup — Part 3

If building a custom View does not suit your needs, a great option to improving performance may be to create a custom ViewGroup which reduces the measure and layout passes that are needed on your Views when a screen update happens.

Ali Muzaffar
AndroidPub
Published in
6 min readOct 5, 2015

--

In Part 1, we build a custom HorizontalBarView in order to simplify code and improve performance. In Part 2, we saw how the building blocks of custom ViewGroups work and now, we’re going to try to use what we know to build our own ViewGroup and see if this gives us any performance saving.

As a reminder, this is what the widget looks like:

Building our custom ViewGroup

In our custom ViewGroup, we are going to take 3 Views and lay them out in in order to build our widget. Readers of previous articles may remember that the default View only took 2 TextView inside a LinearLayout, however, our ViewGroup will take 3. It may seem, that we have additional complexity in our ViewGroup and, hence the approach may be inherently worse. To refresh your memory, the HorizontalBarView widget using default Views was built the following way:

This may intuitively look like it would be more efficient than using 3 Views, however the approach does have it’s limitations as we will see later.

While the HorizontalHarView building using the default Views only uses 2 child Views, these two Views are TextViews which have a fairly complex to calculate weight attribute set on them. In our custom ViewGroup, we’ll use a 2 plain and simple View’s to represent the bars and a TextView to show the text. The TextView has no complex attributes set on it and is measured and positioned by us.

We are also able to push details such as the text to display and the fill percentage on the custom ViewGroup and hence simplify our code for initializing and updating the View.

In order to build our Widget, our custom ViewGroup will assume that it has 3 children, first 2 can be a plain and simple View and the third will be a TextView (please note, that you can also draw the text yourself hence saving you a View).

Measure your children

The first step is to provide each child it’s width and height, based on the width and height of our ViewGroup. We will also take this opportunity to set the text and color of the bars.

We use setMeasuredDimension to set the width and height of our ViewGroup because it is faster than calling super.onMeasure() and super.onMeasure() will also measure the children which is something we don’t want.

We know the fill percentage and we are using it to calculate the width of the first child. Since we know the width of the first child, the second child which represents the empty area is going to be the total width minus the first width of the first child.

With the TextView, we can take 2 approaches, either use the Paint to calculate the width and the height of the text and set it yourself, or since it is a TextView, just tell it to measure itself using the AT_MOST attribute, which should effectively translate to wrap_content.

Laying out the children

Once we have the width of the first child, we know the starting and ending location of the first child as well. Since we know the width of the first child, the second child which represents the empty area is going to be the total width minus the first width of the first child.

When we calculate the width of the second child, we can calculate it’s starting location from the ending location of the first child and the ending location would be the width of the ViewGroup.

Finally, the TextView has to be position to the left, in the fill area, aligned right; if the fill area is larger than the text width, or else it has to be positioned on the right in the empty area, aligned left. We can take this moment to set the appropriate text color and position the TextView starting at the end of the fill area minus the TextView’s width.

You can see the entire code for the HorizontalBarViewGroup below.

View code on Gist

Measuring Performance

The custom ViewGroup on average for 5 updates, performs around the 3ms mark; whereas the widget created using the custom ViewGroups takes around 6ms. Below is an example to the layout and measure times.

The total time taken, is the time the top level view took (LinearLayout or HorizontalBarViewGroup) in its measure and layout passes plus the time taken on the Main thread for the calculations and operations to update the Views. The time displayed is cumulative of the number of measure and layout passes.

As you can see, with the default widget, there are far more layout and measure passes taking place. This is because we use the layout_weight attribute which usually requires at least 2 measure passes.

By calculating the measure and layout times ourselves, we have managed to half the amount of time it takes to layout the widget.

Other considerations & conclusion

The logic for displaying the text in the appropriate side proved to be surprisingly difficult. This is mostly because even after updating the weights for the fill and empty views, the API still returns the old measured width value because the layout pass had not taken place yet. As a result, we have to partially guess which side to display the text on. Take a look at how complex the logic is when compared to our custom View:

While using the custom ViewGroup may be a little more work, ultimately, it not only simplifies you code and significantly improve performance, it also lets you achieve a result which may not be possible using the default widgets.

This was the final part in my #PerfMatters custom Views and ViewGroups series. Do read Part 1, Custom Views and Part 2, introduction to custom ViewGroups if you haven’t already.

One final thought. I’d like to leave you with is something a friend of mine said “Over optimization is the number one cause of bugs”. While performance is extremely important, it’s equally important to write code that is easy to read, extensible and manageable. Knowing when and how much of one to sacrifice for the other requires real talent.

Yay! you made it to the end! We should hang out! feel free to follow me on LinkedIn, Google+ or Twitter.

--

--

Ali Muzaffar
AndroidPub

A software engineer, an Android, and a ray of hope for your darkest code. Residing in Sydney.