Friday, March 19, 2010

Tricks to Layout Tricks #1

I spent a couple of days learning things about creating custom list rows and decided to share with the Android dev world.

When I needed to create a custom list item, with an icon to the left and 2 rows of text to the right, I came across Android Layout Tricks #1.  Using a relative layout for better performance sounded nice.  Defining elements relative to each other so that they automagically look right on various screen sizes sounded really nice.  But I wanted to avoid the hard coded row height.  That just didn't seem right.

Like many other developers, I track a bit on the obsessive side.  I was sure there must be some way to arrange my elements without any hard coded values so I spent a good couple of days trying out all sorts of attribute combinations.  I got the name and number too high, too low, too far apart, rendered in the same space, and one visible but not the other.  

But at the end of the day, I found that you couldn't entirely avoid hard coding.  The solution I settled on was to hard code the margins -- this seemed slightly better than a hard coded row height.

Final lesson: damn the torpedoes, use hard coded margins


Here are a few other things I ran into which should be useful.

Tidbit 1: One thing to note about the code in the Android blog post is its use of layout_alignParentTop together with layout_alignParentBottom to center an element.  layout_centerVertical is a shorter and more reliable way of achieving the same effect.

Tidbit 2: Use LayoutInflater.inflate(layoutId, parent, false) with a non-null value for parent.

Unmentioned in the blog post is the use of LayoutInflater.inflate.  When you create your own ArrayAdapter, you need to use the version that takes (int resource, ViewGroup parent, boolean attachToRoot).  Even though you should set attachToRoot to false, that doesn't mean there shouldn't be a root.  Pass through the ViewGroup parent value and don't set it to null.  I discovered this in a forum post.

Tidbit 3: Stick with one row view for the list.

One failed line of research was to use two row views.  One view for when both rows of text in the list item were present and one for when only the bottom row was present.  Picking the correct view in ArrayAdapter.getView was a snap and everything seemed copacetic.  Done and ship.

Bug report, uh oh.  It turned out that one of my users was having issues when there were more than N items in the list.  I had tested and it looked ok; so I tested it again and it still looked ok.  It was tempting to disregard this bug report, but it was written in complete sentences so that gave it a fair bit of credence.

With yet another round of testing, I paid careful attention to the data I was entering and found the issue.  Before, I had only really checked the number of list items and the order of the phone numbers.  For the names, I usually entered "a".  Adding different names for each number revealed an Android optimization technique for rendering lists and the cause of the bug.  The first page of the list always looked ok, but after scrolling, the subsequent list item would show names that had already been displayed when it should have been using the "no name" view.  Whaaa?

Object creation and garbage collection are expensive operations, so it looks like the Android engineers decided to optimize things by 1. only instantiating as many row objects as needed to show the first page of items and then 2. reusing row objects that moved off the top and putting them at the bottom.  The assumption here is that each row view is the same kind of object.  If you use more than one kind of row view in a list, then the recycling algorithm will give you one kind of view when your code has asked for another.  So if your list might scroll, and of course it will, stick to one row view.