Sunday, June 24, 2012

Building lists with ListView


S. Gökhan Topçu
gtopcu@gmail.com

android.widget.ListView in Android is one of the most popular widgets and commonly used in email, calendar, shopping, chat, music player applications along with many others. It's used to display a list of items in an array, list or a database query in a scrollable fashion.

The easiest way to create a ListView is by extending a android.app.ListActivity instead of the regular Activity class if the list we need will be the major focus of our activity. A ListActivity will create a ListView for you so you don't even need any layout XMLs, and it mirrors some of the ListView's methods so you can call them directly from the activity.

What you do need to do though, is to supply your list with some data, and decide if you want to do anything if anybody clicks on an item in your list. The first task is done by using a concrete implementation of the interface android.widget.Adapter, and the second by overriding onListItemClick() method in your ListActivity. In Android, an adapter supplies both its data and view to several widgets including ListView, Spinner, GridView, AutoCompleteTextView and Gallery. The most common implementation of the Adapter interface is android.widget.ArrayAdapter. It has a variety of flavors for different use cases, but basically it expects a context (i.e. your activity), a resource containing a TextView to display the data, and an array or java.util.List containing the data to be displayed. It calls toString() method of each item in your array/List, and sets the text on the supplied TextView using setText() accordingly. 


Creating a simple ListView

Below is the code for a simple ListActivity which displays a few Lorem Ipsum words and pops up a simple Toast if an item in the list is clicked:

package com.ggit.android.blog;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class SampleActivity extends ListActivity {

 private static final String[] data = { "lorem", "ipsum", "dolor", "sit",
   "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel",
   "ligula", "vitae" };

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

  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_1, data);
  setListAdapter(adapter);
 }

 @Override
 protected void onListItemClick(ListView l, View v, int position, long id) {
  Toast.makeText(this, "You clicked on: " + data[position],
    Toast.LENGTH_LONG).show();
 }

}

And here is the result:


Figure 1: A simple ListView created with a ListActivity

You need to call getListView() method if you want a reference to the ListView created in the background by the ListActivity. Here, android.R.layout.simple_list_item_1 references a layout XML packaged with the Android open source project, along with several others. It includes a simple TextView with some of its attributes set by the Android style currently in use:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
    android:minHeight="?android:attr/listPreferredItemHeightSmall"
/>

Using your own layout files

If you do want to customize the layout and define your own XML but still use it with a ListActivity, you can do so as long as you use android:id="@android:id/list" for your ListView so that the ListActivity can identify the ListView to use:


main.xml:
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="#0000CC"
    android:dividerHeight="4dp"
    />
Important: match_parent was introduced with Android 2.2, for older versions, you need to use fill_parent instead.

code:
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
                      android.R.layout.simple_list_item_1,
                      data); 
        setListAdapter(adapter);
    }

This code is exactly same as the previous one, with the only difference of calling setContentView(R.layout.main); in onCreate(), which sets the activity's view to the layout we have defined in main.xml. The output is again a list of items with a custom blue divider we had specified in main.xml:


Figure 2: ListActivity with a ListView defined in a separate layout file


Lists with a choice mode

If you want a list where items in your list should be checkable, you need to specify a choice mode for your ListView either in your layout XML or in your Java code and use a compatible resource in the constructor of your Adapter. To define the choice mode in the layout, you need to declare the attribute android:choiceMode="singleChoice" or android:choiceMode="multipleChoice" for your ListView, depending on your wish for the user to be able to select one item only in the list, or multiple items simultaneously, respectively. You can do the same in your Java code as follows:

ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

assuming that you're creating your list with a ListActivity. Below is the layout, code and the output for a single choice list with minor modifications to our previous code:

main.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="#0000CC"
    android:dividerHeight="4dp"
    android:choiceMode="singleChoice"
    />

code:
@Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_single_choice, data);
  setListAdapter(adapter);
 }

Here we have added android:choiceMode="singleChoice" to our layout file, and also used a different resource, specifically android.R.layout.simple_list_item_single_choice for our adapter's constructor. This is another packaged layout included with the Android SDK which uses a CheckedTextView instead of a regular TextView:

<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeightSmall"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:checkMark="?android:attr/listChoiceIndicatorSingle"
    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
/>

The output of our single choice layout and code is a TextView with a radio button to show the current selection:

Figure 3: ListActivity with a ListView using android:choiceMode="singleChoice" and android.R.layout.simple_list_item_single_choice


If you want to enable multiple selection, you will need to use android:choiceMode="multipleChoice"
along with a resource in your adapter that will support multiple selection, such as another pre-packaged resource android.R.layout.simple_list_item_multiple_choice. The output will be similar, with the possibility of multiple selections this time:

Figure 4: A multiple-choice ListView using android.R.layout.simple_list_item_multiple_choice

With both selection modes, you can use methods such as getCheckedItemPosition() and setItemChecked() from your ListView to manipulate the items and get user selections.



Using a custom layout for ListView items

You can also use another constructor in the ArrayAdapter to display a more customized list which points to  a resource ID in your custom layout XML which our ArrayAdapter will call setText() on to set its text according to the list data, along with the custom layout itself that you want to use with the adapter. Below is an example list with an icon on the left (taken from Android Action Bar Icons) and text. Icons rating_good.png and rating_bad.png are placed in the folder res/drawable for us to be able to reference them from our layout XML as android:src="@drawable/rating_good" and Java code as R.drawable.rating_good. Ideally, you would need to have different icons for different resolution sets (high resolution, medium resolution, low resolution and very high resolution) and place them in their respectable folders (res/drawable-hdpi, res/drawable-mdpi, res/drawable-ldpi, res/drawable-xhdpi), but for the sake of simplicity, we are using only a single set for our example:

row.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView 
android:id="@+id/icon"
android:padding="4dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/rating_good"
/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="center_vertical"
/>
</LinearLayout>

code:
package com.ggit.android.blog;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;

public class SampleActivity extends ListActivity {

 private static final String[] data = { "lorem", "ipsum", "dolor", "sit",
   "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel",
   "ligula", "vitae" };

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    R.layout.row, R.id.text, data);
  setListAdapter(adapter);
 }

 @Override
 protected void onListItemClick(ListView l, View v, int position, long id) {
  ImageView icon = (ImageView) v.findViewById(R.id.icon);
  icon.setImageResource(R.drawable.rating_bad);
 }

}

In our ArrayAdapter constructor, we supply our custom layout file (row.xml) and our TextView's ID (R.id.text). And here onListItemClick() method is overridden and switches the icon when an item on the list is selected. Notice that we're not providing any activity layouts here and so we don't call setContentView() inside onCreate(), so our ListActivity creates a default one for us. (However, we could also change the activity layout and customize the ListView itself as we wanted as shown in the previous example). The output for our custom row is as we would expect:

Figure 5: A default ListView with a custom XML for rows
Using this technique, you can create rather static ListViews where the list of widgets and thus the appearance is static and the same for all rows.


Creating a custom adapter

Although we can handle most of the ListView use cases with the examples provided above, if you really need dynamic lists with heavy customization and contents determined dynamically, you can create your own adapter by extending the ArrayAdapter. You will only need to override getView(), which is called for each row in the list and needs to return a View. Using the previous example, let's create our own custom adapter and override the getView() method so that it will return a "rating_good" icon for rows with a single position, and a "rating_bad" for double ones. You will only need to modify the parts of your Java code given below, our row.xml does not change, and we again do not provide a layout for the activity:

@Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setListAdapter(new CustomAdapter(this, R.layout.row, R.id.text, data));
 }

 private class CustomAdapter extends ArrayAdapter<String> {
  public CustomAdapter(Context context, int resource,
    int textViewResourceId, String[] objects) {
   super(context, resource, textViewResourceId, objects);
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   if (position % 2 == 0) {
    View row = super.getView(position, convertView, parent);
    ImageView img = (ImageView) row.findViewById(R.id.icon);
    img.setImageResource(R.drawable.rating_bad);
    return row;
   } else {
    return super.getView(position, convertView, parent);
   }
  }
 }

In our onCreate(), we instantiate our CustomAdapter and set it as the list adapter with the TextView's resource ID and row layout XML. Our CustomAdapter has the same constructor as the previous ArrayAdapter, and propagates the TextView's ID and row layout's ID to its superclass, ArrayAdapter, so that the ArrayAdapter creates a view for each row and sets the TextView's text. If we did not override the getView() method in our CustomAdapter or returned the View prepared by the ArrayAdapter for us by using:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
     return super.getView(position, convertView, parent);
}

then the output would be exactly the same as the previous example: a thumbs-up icon with text set from our data. Since we are only changing the icon for each row and our rows are still pretty similar looking, we can still rely on super.getView() method to supply us with a View which will be set-up by the parent ArrayAdapter implementation. Afterwards, we're checking if our row position is double, and if yes, we're using the View handed by the method parameters, which we know is an instance of our row.xml with an ImageView and a TextView, and changing the resource for the ImageView. If position is single, then we return the super.getView() as is. The output looks like this:


Figure 6: ListView with a custom adapter

An even more customized adapter with Layout Inflation

If you come across a requirement to create your own rows by yourself and you don't want to use any of the constructors provided by ArrayAdapter, then you again need to create and return a View for each row item in the getView() method in your custom adapter implementation. If you do not need to create different set of widgets dynamically for each row, then you can define your base layout in a layout XML and use it as a starting point. Using the same row.xml, update the following parts in your Java code:

...
        
        @Override
        public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setListAdapter(new CustomAdapter());
        }

        ...

 private class CustomAdapter extends ArrayAdapter<String> {
  public CustomAdapter() {
   super(SampleActivity.this, R.layout.row, data);
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   View row = getLayoutInflater().inflate(R.layout.row, parent, false);
   TextView tw = (TextView) row.findViewById(R.id.text);
   tw.setText(data[position]);
   if (position % 2 == 0) {
    ImageView img = (ImageView) row.findViewById(R.id.icon);
    img.setImageResource(R.drawable.rating_bad);
    return row;
   } else {
    return row;
   }
  }
 }

We have now updated our CustomAdapter's constructor to a zero-argument one, and we "trick" the ArrayAdapter by calling any of its constructors since we're not going to use the views created by the default getView() implementation. In our getView() method, we now use getLayoutInflator() to get an instance of android.view.LayoutInflator, which is a very useful class that builds and returns (through inflate() method) a View object with attributes set from the layout XML file supplied, removing the need for us to instantiate our widgets/containers dynamically inside our code. Since we're not using ArrayAdapter's implementation of the getView() method (and not passing our TextView in the constructor),  we will need to set the text for our TextView as well. The output for this example is exactly the same as the previous one:

Figure 7: Same output, this time by inflating the rows ourselves

Add a little bit of cream on top: Performance Tweak

Working in a mobile environment, our applications generally has less CPU and memory power available. And every CPU instruction set drains the battery. So if you want your applications to be be "good citizens" of the Android ecosystem, you need to be careful not to consume more resources then you ever need to. Plus; your users will be thankful that your UI is more swift, and they don't have to charge their phones just because their favorite app's developer did not take additional steps to tweak the code.

In Android, every widget and container consumes valuable memory (around 2KB each), and every call to the LayoutManager.inflate() method requires the Android system to traverse your whole XML trees, and instantiate each widget and thus allocate memory; so you consume both memory and CPU for each row you inflate in the previous example. As you may already have noticed, getView() method gives you a View called convertView as one of its parameters, which is actually one of the rows created previously. When the user scrolls through the rows in the ListView, we could actually use this View and update its attributes for the current row instead of creating new rows each time. This is called "recycling" and Google statistics show that you will get up to %50 speed increase if you do "recycle" in your adapter implementation. You only need to change the parts shown in bold in your code from the previous example to enable recycling in this project:

@Override
        public View getView(final int position, View convertView, ViewGroup parent) {
  View row = convertView;
  if (row == null) {
   row = getLayoutInflater().inflate(R.layout.row, parent, false);
  }
  TextView tw = (TextView) row.findViewById(R.id.text);
  tw.setText(data[position]);
  ImageView img = (ImageView) row.findViewById(R.id.icon);
  if (position % 2 == 0) {
   img.setImageResource(R.drawable.rating_bad);
  } else {
   img.setImageResource(R.drawable.rating_good);
  }
  return row;
 }

We need to check if the convertView is not null before using it since our list could be creating the rows for the first time. But if we do get a non-null one, this is just one of the previous rows we had inflated, and we can use it for our row by updating its text and icon without having to inflate a new View first. Since we are now using our previous recycled rows, we need to set our icon each time as well as the text displayed. You can experiment with recycling and see it on action yourself, though you will probably need a larger number of rows to scroll through to see any visible difference. The output of this code will be the same, but it will run more efficiently, make the user happy, and us proud.


Even more tweaks: getTag() & setTag() methods and the Holder Pattern


There's yet one more thing we could do to make our code better. We're still calling findViewById() once for each row in the list with a single position, and twice for all rows with a double position to update the icon as well. This method is also a resource consumer and we could use something called a Holder Pattern.

Have you ever read anything about the Java Design Patterns? If not, now would be a good time to have an idea about why we ever needed them, since now you also need to start to get familiar about Android Design Patterns, both for UI design and the code (and check here for more candy). The Holder Pattern is all about creating a class with package-protected members which will be the widgets you are accessing often through findViewById() calls and then accessing them through this custom class. Best done than told, let me show you with an example using our previous project:

CustomHolder.java
package com.ggit.android.blog;

import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

class CustomHolder {
 ImageView icon;
 TextView text;

 CustomHolder(View row) {
  this.icon = (ImageView) row.findViewById(R.id.icon);
  this.text = (TextView) row.findViewById(R.id.text);
 }
}
Every android.view.View object has a setTag(Object obj) method that you can use to attach any objects to this view, and an Object getTag() method which will return the object attached using the previous one. We could set our TextView objects using setTag() method to our rows, but since we're also changing ImageView resources dynamically, we need to instantiate and use a Holder class which will hold both and tag it to our rows. Here's what needs to be changed in our Java code to switch to using the Holder pattern:

@Override
      public View getView(final int position, View convertView, ViewGroup parent) {
  View row = convertView;
  CustomHolder holder;
  if (row == null) {
   row = getLayoutInflater().inflate(R.layout.row, parent, false);
   holder = new CustomHolder(row);
   row.setTag(holder);
  }
  holder = (CustomHolder) row.getTag();

  TextView tw = holder.text;
  ImageView img = holder.icon;
  tw.setText(data[position]);
  if (position % 2 == 0) {
   img.setImageResource(R.drawable.rating_bad);
  } else {
   img.setImageResource(R.drawable.rating_good);
  }
  return row;
      }

As you can see, we now also check if there's a Holder class attached already with our row, if not, we create one and attach it, Afterwards, we access the widgets through the Holder class, instead of calling findViewById() method every time. This is again another reduction to our CPU consumption (and a plus for the speed of our application - around %25 according to Google stats) without any changes to the UI. We will have every right to boost ourselves as a top Android developer once we make it a habit to utilize this pattern with our adapters whenever it makes sense to do so.

Saving row states

Apart from the 5th example where our thumbs-ip icon changed to a thumbs-down one when clicked, widgets in our rows did not change and were pretty much static. However, we could have other widgets such as an EditText, RatingBar, ToggleButton, etc. inside our rows which would change state based on the user input. Our rows do not yet care about the state of the widgets inside them, and if there was any, then these would be lost while new rows are created and the old ones recycled. To see it for yourself, just change the TextView in your row.xml layout to the one below, replacing it with an EditText widget. Run your project again, and change the contents of any EditText and scroll until the row you've changed is out of view, and scroll back to it. You will see that any changes you have done are lost, and the row is again in its initial state.

<EditText
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:layout_gravity="center_vertical"
        android:inputType="text"
        android:imeOptions="actionDone"
        />

As EditText is a subclass of TextView, our holder class can still do its job. To be able to save widget states though, we need to teach our widgets which row they represent in the list. Continuing from our previous example, update your CustomHolder.java and SampleActivity.java as follows to save the text inside EditText widgets:

CustomHolder.java
class CustomHolder {
 ImageView icon;
 EditText text;

 CustomHolder(View row) {
  this.icon = (ImageView) row.findViewById(R.id.icon);
  this.text = (EditText) row.findViewById(R.id.text);
 }
}

SampleActivity.java
@Override
public View getView(int position, View convertView, ViewGroup parent) {
  View row = convertView;
  CustomHolder holder;
  if (row == null) {
   row = getLayoutInflater().inflate(R.layout.row, parent, false);
   row.setTag(new CustomHolder(row));
  }
  holder = (CustomHolder) row.getTag();

  EditText text = holder.text;
  text.setText(data[position]);
  text.setTag(new Integer(position));
  text.setOnEditorActionListener(new TextView.OnEditorActionListener() {
   @Override
   public boolean onEditorAction(TextView v, int actionId,
     KeyEvent event) {
    if (actionId == EditorInfo.IME_ACTION_DONE) {
     int index = (Integer) v.getTag();
     data[index] = v.getText().toString();
     return true;
    }
    return false;
   }
  });

  ImageView img = holder.icon;
  if (position % 2 == 0) {
   img.setImageResource(R.drawable.rating_bad);
  } else {
   img.setImageResource(R.drawable.rating_good);
  }
  return row;
 }

Once you compile and run the project, change the text of any of the EditText widgets, click on Done in your soft keyboard, and again try scrolling back and forth. You will now see that the widget does keep its text indeed:

Figure 8: Saving row states

As you can see, we are now teaching our EditText widgets which position they will be representing in the list (with the line text.setTag(new Integer(position));) and registering a listener on them which updates our String array once their contents have changed. 


Using custom row models

This will be our last and probably the most complex pattern for creating lists in Android. In complex scenarios where your data is not as simple as an array/list of Strings and you need to keep track of multiple widgets' data as well as their states, you can create a row model class which will serve as a data model for your list, and use it as the generics argument to the array adapter:


ArrayAdapter<MyCustomRowModel> aa = new ArrayAdapter<MyCustomRowModel>(...);


Once we initialize the adapter (or our own custom adapter for that matter) with a custom row model, the adapter constructors and methods such as add(), remove(), replace(), etc. all start to work with our custom row model. Below is the updated code from previous example which uses a custom row model for a custom ArrayAdapter (you do not need to change row.xml and CustomHolder.java):

package com.ggit.android.blog;

import java.util.ArrayList;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;

public class SampleActivity extends ListActivity {

 private static final String[] data = { "lorem", "ipsum", "dolor", "sit",
   "amet", "consectetur", "adipiscing", "elit", "nullam", "tempus",
   "suscipit", "nunc", "a", "fringilla", "aliquam", "sed", "nisi",
   "semper", "magna", "auctor", "condimentum", "at", "non", "nunc",
   "aenean", "et", "quam", "non", "leo", "accumsan", "semper", "eu",
   "in", "ligula", "fusce", "vulputate", "sollicitudin", "leo", "nec",
   "dictum", "maecenas", "ac", "lacus", "eget", "sem", "egestas",
   "tempus", "pellentesque", "tincidunt", "nisl" };

 private ArrayList<RowModel> list;

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

  list = new ArrayList<RowModel>();
  for (String s : data) {
   list.add(new RowModel(s));
  }
  setListAdapter(new CustomAdapter());
 }

 private RowModel getModel(int position) {
  return ((ArrayAdapter<RowModel>) getListAdapter()).getItem(position);
 }

 private class CustomAdapter extends ArrayAdapter<RowModel> {
  public CustomAdapter() {
   super(SampleActivity.this, R.layout.row, list);
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   View row = convertView;
   CustomHolder holder;
   if (row == null) {
    row = getLayoutInflater().inflate(R.layout.row, parent, false);
    row.setTag(new CustomHolder(row));
   }
   holder = (CustomHolder) row.getTag();

   EditText text = holder.text;
   text.setText(getModel(position).str);
   text.setTag(new Integer(position));
   text.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId,
      KeyEvent event) {
     if (actionId == EditorInfo.IME_ACTION_DONE) {
      int index = (Integer) v.getTag();
      getModel(index).str = v.getText().toString();
      return true;
     }
     return false;
    }
   });

   ImageView img = holder.icon;
   if (position % 2 == 0) {
    img.setImageResource(R.drawable.rating_bad);
   } else {
    img.setImageResource(R.drawable.rating_good);
   }
   return row;
  }
 }

 private class RowModel {
  private String str;

  private RowModel(String str) {
   this.str = str;
  }

  @Override
  public String toString() {
   return str;
  }
 }
}

The output does not change, but now we have a more sophisticated data model than a plain array/list of String objects which we could add other widgets very easily.



And finally, ListView and Fragments: ListFragment

There's something called "Fragment" (android.app.Fragment) which was introduced with Android 3.0 (API 11) that let's you group your widgets logically and inject them into your Activities as needed. Using a support library though, Google has made them available down to Android 1.6 (API 4). They are mainly used for making developer's lives easier for dealing with multiple screen sizes, especially while trying to design a GUI for an application that needs to run nicely on a phone, but should be able to supply enough content to fill a tablet screen as well. For example, you could define two fragments for the phone version of your app, where one would be inside the main activity and the second shown in a separately launched activity as a response to a user action. But in a tablet, you can display both fragments side by side as you have enough screen estate. Fragments basically lets you divide your UI into logical parts, and reuse & bind together later.

If you're planning to use fragments (which you really should if you are also designing for small and large screens at the same time, or using the same logical set of widgets in different parts of your app), there's a specialized fragment called the android.app.ListFragment which works much like the ListActivity, with the only exception of being a Fragment instead of an Activity, again providing you with a ListView without you having to define one in your layout XML. So you can still use setListAdapter() and getListView() methods, and define all the customizations we have applied to a ListView in this blog.


Further Reading:
API Guides: ListView
API Guides: Fragments
android.app.ListActivity
android.widget.ListView
android.widget.ArrayAdapter
android.view.LayoutInflator
android.app.Fragment
android.app.ListFragment


Related Google I/O Sessions:


No comments:

Post a Comment

Please leave your feedback below if you found this blog useful, thanks.