December 21, 2014

Diagnosing Layout Problems

Yesterday, a post came over the transom on the [android-developers] Google Group, citing a problem in getting a RelativeLayout to work properly as a row in a ListView. Since I screwed up my initial response to that post, I figured I’d document and write up a blow-by-blow account of the diagnostic steps I went through to in order to determine what is going wrong.

To give you a clue as to the final outcome, the subtitle of this post should be “How I Learned to Stop Worrying And Love the Box Model”…

First, here is his original row layout (with only some reformatting applied):

[sourcecode lang=”xml”]

apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:text="Left Center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
android:text="Upper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
android:text="Lower"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
[/sourcecode]

I put together my own test scaffold activity to use this row:

[sourcecode lang=”java”]
public class DiagnosticDemo extends ListActivity {
String[] items={“lorem”, “ipsum”, “dolor”};
static int ROW_TO_TEST=R.layout.row_original;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setListAdapter(new TestAdapter(this));
}

class TestAdapter extends ArrayAdapter {
Activity context;

TestAdapter(Activity context) {
super(context, ROW_TO_TEST, items);

this.context=context;
}

public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater=context.getLayoutInflater();
View row=inflater.inflate(ROW_TO_TEST, null);

return(row);
}
}
}
[/sourcecode]

That gave me the following visual result:

The original layout that needs fixing

Since that seemed to line up with what the author was getting, I felt I had reproduced the problem, and set about getting it to work. This eventually took 13 different test layouts. Since this post would be a mile long if I documented each and every one of them, I’ll pick out the highlights.

First, I added background colors, so we could better see what widget is taking up what space on-screen:

[sourcecode lang=”xml”]

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#880000"
>
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:text="Left Center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#008800"
/>

android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#000088"
>
android:text="Upper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#880088"
/>
android:text="Lower"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#888800"
/>
[/sourcecode]

The same layout, with ugly colors

While ugly, the colors do help to demonstrate what is what. The RelativeLayout is brick red, the LinearLayout is blue (of which only a hint can be seen), and the three TextViews are green, mustard yellow,… and the third is not visible in the layout.

Eventually, I tried switching to a full box model, trying to figure out why the two stacked TextView widgets were appearing on top of each other:

[sourcecode lang=”xml”]

android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#880000"
>
android:text="Left Center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#008800"
/>

android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#000088"
>
android:text="Upper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#880088"
/>
android:text="Lower"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#888800"
/>
[/sourcecode]

Switched top-level layout to be LinearLayout

Now, we see the first TextView (“Upper”) in purple and no lower one.

Thinking that the problem might be one of layout weights, I added android:layout_weight=”1″ to each of the stacked TextView widgets, which got me:

After adding layout_weight to the stacked TextViews

You see that they are now somewhat stacking, but their full height is not being properly utilized, so they are somewhat squashed.

On a whim, I commented out the “Left Center” TextView, and got:

After removing the Left Center TextView

So now they’re stacking. This suggests that, somehow, the height of the layout was being dictated by the initial TextView more than the stacked pair of TextView widgets.

I then started trying to work this back into a RelativeLayout overall parent, yielding:

[sourcecode lang=”xml”]

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#880000"
>

android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#000088"
>
android:text="Upper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#880088"
android:layout_weight="1"
/>
android:text="Lower"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#888800"
android:layout_weight="1"
/>
[/sourcecode]

After returning to a RelativeLayout and layout_centerHorizontal=true

Note the use of layout_centerHorizontal="true" rather than layout_centerInParent="true". One would think they should be equivalent in a row whose height is being dictated by the to-be-centered widget. In practice, it appears layout_centerHorizontal works better in this case. Needless to say, we now have our stacked TextView widgets properly positioned.

So, we uncomment our “Left Center” TextView and…

After restoring the Left Center TextView

On the plus side, it did not foul up our Upper and Lower TextView widgets. On the other hand, the Left Center TextView is still misbehaving.

If we change the Left Center TextView to use layout_alignParentBottom="true" instead of layout_centerVertical="true", we see:

After setting the Left Center TextView to align with the parent's bottom

That should have the Left Center TextView‘s bottom aligned with the parent’s bottom. Combine this with the previous result, and it would appear that the RelativeLayout knows the proper height, but for some reason is translating the position upwards by 50%, which is fouling up the layout.

And at this point, I surrendered. This could be a bug. This could be the ways of RelativeLayout being inscrutable.

If this were my application, I’d dump the RelativeLayout and stick to a “box model”, using nested LinearLayouts:

[sourcecode lang=”xml”]

android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
android:text="Left Center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
/>

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
>
android:text="Upper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
/>
android:text="Lower"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
/>
[/sourcecode]

Dumping the whole thing and rebuilding it via LinearLayout

Technically, this is not the same as the original. In the original, the author wanted the Upper/Lower TextView pair to be centered across the row. In this implementation, they are centered across the part of the row not used by the Left Center TextView. The difference is subtle to the naked eye, and it may be the replacement will suffice for the application’s business needs.



  • http://piggybackmobile.com Chris

    hi

    instead of using a background color , you could have used Hierarchyviewer tool that proved invaluable to us to try to undestand what happend inside our views.

    Chris

  • http://commonsware.com Mark Murphy

    Ah, that was added in 0.9, and I haven’t reviewed all the new SDK tools yet. Good point!

  • http://www.curious-creature.org Romain Guy

    HierarchyViewer, available in the SDK, was developed exactly for this kind of issue. I was getting tired of always doing that kind of debugging :)

  • http://commonsware.com Mark Murphy

    I’ll blog about it tomorrow!

  • Jeremy

    All of the images in this post seem to have disappeared.

  • Christian Kirsch

    The images are pointing to the wrong domain. Instead of androidguys.net it is androidguys.com

  • Pingback: babhan botryopterid father

  • Pingback: amulla anisomelus advocate