Showing posts with label setContentView. Show all posts
Showing posts with label setContentView. Show all posts

Wednesday, October 14, 2009

Inflation is a Good Thing

Before diving into the topic of creating fancy lists in Android, we need to take a short detour into some background material. If you have written activities for Android, you are used to calling setContentView() with the resource ID of some XML layout you specified. Let’s take a peek at how that is probably implemented: via a technique called “view inflation”.

In this case, “inflation” means the act of converting an XML layout specification into the actual tree of View objects the XML represents. This is undoubtedly a tedious bit of code: take an element, create an instance of the specified View class, walk the attributes, convert those into property setter calls, iterate over all child elements, lather, rinse, repeat.

The good news is that the fine folk on the Android team wrapped all that up into a class called ViewInflate that we can use ourselves. When it comes to fancy lists, for example, we will want to inflate Views for each row shown in the list, so we can use the convenient shorthand of the XML layout to describe what the rows are supposed to look like.

For example, here is your typical do-nothing activity, except re-written to use ViewInflate instead of supplying the resource ID to setContentView():

public class InflaterDemo extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

setContentView(getViewInflate().inflate(R.layout.main, null, null));

// setContentView(R.layout.main);
}
}

Your activity can get an appropriate instance of ViewInflate via getViewInflate(). Then, to convert a resource ID into a View tree, just call inflate() with the resource ID in question. That gives you your View; in this case, we pass the View to setContentView(), as if we had instantiated the View manually.

Fancy ListViews, Part One

The classic Android ListView is a plain list of text — solid but uninspiring. we will see how to create a ListView with a bit more pizazz. Today, in particular, we will see two techniques for creating a ListView whose rows contain icons, in addition to text.

If you want rows to have a different look than the stock r ows, one way to accomplish this is to supply your own layout XML to be used for each row, telling Android to use your layout rather than one of the built-in ones. This gives you complete control over what goes in the row and how it is laid out.
For example, suppose you want a ListView whose entries are made up of an icon, followed by some text. You could construct a layout for the row that looks like this:

01.02. android:layout_width="fill_parent"
03. android:layout_height="wrap_content"
04. android:orientation="horizontal"
05.>
06. 07. android:id="@+id/icon"
08. android:layout_width="22px"
09. android:paddingLeft="2px"
10. android:paddingRight="2px"
11. android:paddingTop="2px"
12. android:layout_height="wrap_content"
13. android:src="@drawable/ok"
14. />
15. 16. android:id="@+id/label"
17. android:layout_width="wrap_content"
18. android:layout_height="wrap_content"
19. android:textSize="44sp"
20. />
21.

This layout uses a LinearLayout to set up a row, with the icon on the left and the text (in a nice big font) on the right.
By default, though, Android has no idea that you want to use this layout with your ListView. To make the connection, you need to supply your Adapter with the resource ID of your custom layout:

01.public class StaticDemo extends ListActivity {
02. TextView selection;
03. String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
04. "consectetuer", "adipiscing", "elit", "morbi", "vel",
05. "ligula", "vitae", "arcu", "aliquet", "mollis",
06. "etiam", "vel", "erat", "placerat", "ante",
07. "porttitor", "sodales", "pellentesque", "augue",
08. "purus"};
09.
10. @Override
11. public void onCreate(Bundle icicle) {
12. super.onCreate(icicle);
13. setContentView(R.layout.main);
14. setListAdapter(new ArrayAdapter(this,
15. R.layout.row, R.id.label,
16. items));
17. selection=(TextView)findViewById(R.id.selection);
18. }
19.
20. public void onListItemClick(ListView parent, View v,int position, long id) {
21. selection.setText(items[position]);
22. }
23.}
Displays a list of random words, and puts the currently-selected word in a TextView.

The key in this example is that you have told ArrayAdapter that you want to use your custom layout (R.layout.row) and that the TextView where the word should go is known as R.id.label within that custom layout.
The result is a ListView with icons down the left side. In particular, all the icons are the same.
This technique — supplying an alternate layout to use for rows — handles simple cases very nicely. However, it falls down when you have more complicated scenarios for your rows, such as:
• Not every row uses the same layout (e.g., some have one line of text, others have two)
• You need to configure the widgets in the rows (e.g., different icons for different cases)
In those cases, the better option is to create your own subclass of your desired Adapter, override getView(), and construct your rows yourself. The getView() method is responsible for returning a View, representing the row for the supplied position in the adapter data.
For example, let’s rework the above code to use getView(), so we can have different icons for different rows — in this case, one icon for short words and one for long words:

01.public class DynamicDemo extends ListActivity {
02. TextView selection;
03. String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
04. "consectetuer", "adipiscing", "elit", "morbi", "vel",
05. "ligula", "vitae", "arcu", "aliquet", "mollis",
06. "etiam", "vel", "erat", "placerat", "ante",
07. "porttitor", "sodales", "pellentesque", "augue",
08. "purus"};
09.
10. @Override
11. public void onCreate(Bundle icicle) {
12. super.onCreate(icicle);
13. setContentView(R.layout.main);
14. setListAdapter(new IconicAdapter(this));
15. selection=(TextView)findViewById(R.id.selection);
16. }
17.
18. public void onListItemClick(ListView parent, View v, int position, long id) {
19. selection.setText(items[position]);
20. }
21.
22. class IconicAdapter extends ArrayAdapter {
23. Activity context;
24.
25. IconicAdapter(Activity context) {
26. super(context, R.layout.row, items);
27.
28. this.context=context;
29. }
30.
31. public View getView(int position, View convertView, ViewGroup parent) {
32. ViewInflate inflater=context.getViewInflate();
33. View row=inflater.inflate(R.layout.row, null, null);
34. TextView label=(TextView)row.findViewById(R.id.label);
35.
36. label.setText(items[position]);
37.
38. if (items[position].length()>4) {
39. ImageView icon=(ImageView)row.findViewById(R.id.icon);
40.
41. icon.setImageResource(R.drawable.delete);
42. }
43.
44. return(row);
45. }
46. }
47.}
In our getView() implementation, we first get our hands on a ViewInflate object so we can use it to “inflate” our row layout XML and give us a View representing that row.
Then, we tailor that View to match our needs:
• We fill in the text label into our label widget, using the word at the supplied position
• We see if the word is longer than four characters and, if so, we find our ImageView icon widget and replace the stock resource with a different one
Now, we have a ListView with different icons based upon context of that specific entry in the list. Obviously, this was a fairly contrived example, but you can see where this technique could be used to customize rows based on any sort of criteria, such as other columns in a returned Cursor.