Dumping WYSIWYG: Building Android User Interfaces at Run Time

A look at an alternative means of constructing user interface elements for Android developers

Breaking with Tradition

Developing Android applications ordinarily commences with a File -> New -> Android Project.

The new project wizard creates a simple Android application sporting a single TextView UI widget.

This UI widget is contained within a user interface file layout named main.xml.

The Android Developer Tools (ADT) include a modest WYSIWYG (What you see is what you get) layout tool that is used to work with these xml files. You know the drill: drop a new control on the screen, set properties and away you go.

As an alternative to using the WYSIWYG tools, an Android developer can edit the xml file directly. In we looked at a simple user interface to support our excursion into the Android Native Development Kit. Here is the xml used for that simple interface.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/TheLabel" android:text="LMJNI ..."/>

<Button android:text="Get String" android:id="@+id/btnGetString" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/number" android:text="10" android:numeric="integer"/>

<Button android:text="Add Number" android:id="@+id/btnAddNumber" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

</LinearLayout>

This kind of user interface is referenced at run-time by a call to an Activity’s setContentView() method. The xml layout is said to be “inflated” at run-time. Here’s a code snippet showing how that takes place.

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

Pretty simple.

But what if you don’t know ahead of time just what your UI is going to look like? For example, one of the applications I work on presents a dynamic user interface based on an xml configuration file downloaded from a server — new fields are added long after the code was written. In this type of scenario, we need a means to create user interface elements “on the fly”. In this article we will take a look at a couple of ways to accomplish this task.

Built from Scratch

Each of the UI widgets defined in the layout tools may also be created directly within code. Each Android Activity has a View, assigned with the method setContentView() as shown above. This method is overloaded — it can take a reference to a compiled XML layout file or it can take an instance of a View. And, to be more specific, it is ordinarily going to take an instance of a ViewGroup which is a special kind of View which acts as a “container” for other views.

Android contains a number of ViewGroups though what we ordinarily interact with its direct subclasses. These classes include AbsoluteLayout, LinearLayout, RelativeLayout, SlidingDrawer and others. It is common to see these layout classes used in conjunction with one another. For example you may have multiple LinearLayouts contained by another LinearLayout or perhaps a SlidingDrawer.

Most simple Android applications start with a LinearLayout with its child controls listed vertically down the screen — just like the main.xml file shown earlier. So, how would we go about making such a User Interface directly in code? Let’s have a look.

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout ll = new LinearLayout(this);

        final TextView tv = new TextView(this);
        tv.setText("sample text goes here");
        ll.addView(tv);

        final EditText et = new EditText(this);
        et.setText("edit me please");
        ll.addView(et);

	Button btn = new Button(this);
	btn.setText("Go!");
        btn.setOnClickListener(new Button.OnClickListener() {
	       	public void onClick(View v) {
	               tv.setText(et.getText().toString());

        	}
        });
        ll.addView(btn);
        setContentView(ll);
   }

In this code we are creating an instance of the LinearLayout class. We define a TextView, an EditText and a Button. Each of these is added to the LinearLayout and then the layout itself is set to be the content of our Activity. By default, the LinearLayout organizes any contained Views/widgets horizontally. Unfortunately, this doesn’t look too good — note how everything is scrunched up.

Horizontal Layout (default)
Horizontal Layout (default)

To get a more appealing user interface, we can set the orientation to “vertical”.

ll.setOrientation(android.widget.LinearLayout.VERTICAL);

This makes for a somewhat more acceptable user interface.

Vertical Layout (default)
Vertical Layout

The recipe is pretty straight-forward:

  • Create an instance of the desired layout.
  • Declare a variable of the widget type you want to work with.
  • Create a new instance of that type by calling one of its constructors and then set properties of the object.
  • Then simply “add” it to the desired layout.

That’s fine, but what if you want to get a little more sophisticated. Not a lot, just a little. You can make your own widget classes by extending the stock classes provided by Android. That is what we’re going to do next.

A Custom UI Widget

A lot of the code I have written over the years is for BlackBerry devices. One of the constructs the BlackBerry has is the ability to define a data input field along with its descriptive label. I thought that would be an interesting piece of functionality to replace, so I came up with a class called LMEditBox.

The purpose of LMEditBox is to define an EditText for data input, prefixed by a TextView which acts as a descriptive label. I wanted the two of these widgets to be aligned horizontally as is customary in the majority of user interface designs on most platforms. To accomplish this, I placed both of these widgets within a LinearLayout oriented horizontally. To build a complete user interface, I simply create 1 or more of these composite widgets and add them to a layout of some variety. In this example, they are contained in a vertical LinearLayout.

Here is a example of the LMEditBox in action in a simple UI screen used to capture first and last name.

Composite Widgets
Composite Widgets

Here is the code for LMEditBox.java

package com.msi.linuxmagazine.dynamicui;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.EditText;

public class LMEditBox extends LinearLayout {
	TextView label;
	EditText txtBox;

	public LMEditBox(Context context,String labelText,String initialText) {
		super(context);
		// create the label
		label = new TextView(context);
		label.setText(labelText);
		// create the edit box
		txtBox = new EditText(context);
		txtBox.setText(initialText);
		// tell this box to use as much space horizontally as it can
		txtBox.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
		// add the label to the UI
		this.addView(label);
		// add the edit box to the UI
		this.addView(txtBox);
	}

	public LMEditBox(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public String getValue()
	{
		// return the textual contents of our EditText control
		return txtBox.getText().toString();
	}
}

And here is the code which uses the LMEditBox.

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

        LinearLayout ll = new LinearLayout(this);
        ll.setOrientation(android.widget.LinearLayout.VERTICAL);

        final LMEditBox firstField = new LMEditBox(this,"First Name: ","Frank");
        ll.addView(firstField);

        final LMEditBox lastField = new LMEditBox(this,"Last Name: ", "Ableson");
        ll.addView(lastField);

        final TextView tv = new TextView(this);
        tv.setText("");
        ll.addView(tv);

        Button btn = new Button(this);
        btn.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT));

        ll.addView(btn);

        btn.setText("Go!");
        btn.setOnClickListener(new Button.OnClickListener() {
	       	public void onClick(View v) {
                	tv.setText(firstField.getValue() + " " + lastField.getValue());
	       }
	} );

	setContentView(ll);
    }

In this code snippet we are creating a LinearLayout and setting its orientation to VERTICAL. We add instances of our LMEditBox widget: one for the first name, and one for the second. We’ve also got a simple TextView and a Button to round out our demonstration UI.

Note the code in the Button’s OnClickListener — we use the getValue() method of our LMEditBox widget class to construct a string which is subsequently assigned to the TextView widget, as shown below.

Also, if you’re wondering why the classes are marked as “final” it is to enable the anonymous click handler to resolve the instances properly at run-time.

Extracting data from widgets
Extracting data from widgets

This is clearly just scratching the surface, but hopefully you can get a taste for what it takes to create your own UI layouts dynamically. And with that, you’re on your way to replacing some of those BlackBerry applications!

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62