COMPLEX LIST VIEWS
MAKING A CUSTOM ADAPTER
All right, now comes the really interesting part. You need to create a custom Adapter to feed rows into the ListView.
Custom Adapters have four methods you are required to override, all of which allow the ListView to acquire information about your data set.
䊏 getCount() returns the number of rows currently in the set of information.
䊏 getItem(intposition) returns an object corresponding to a particular row position.
䊏 getItemId(intposition) returns the ID that corresponds to the item at a specific position. This is used often with Adapters that focus on Cursors (Android’s SQLite interfaces).
NOTE: Any changes to the Adapter’s data must take place on the main thread. Modifying the Adapter data counts as changing the UI, as far as
Android is concerned. As always, all changes to the user interface must be carried out on the main thread. Keep this is mind as you create your own adapter, especially if you’re fetching data from the network off the main thread.
ptg7794906
䊏 getView(intposition,ViewconvertView,ViewGroup parent) is where most of the Adapter’s work will take place. The ListView, in making this call, is essentially asking for the view at position. You must, in this method, return a correctly configured view for the data at position. More on exactly how this works in a minute.
As you can see by the get prefix on all the required methods, all that Android Adapters do is provide row content information to the ListView. The ListView, it would seem, is one very needy girlfriend (or boyfriend . . . I’m not sure how to assign gender to Android UI interfaces).
Let me show you the example before I talk about any more theory. Twitter’s API returns its information in the form of JSON-encoded objects. It doesn’t, at this point, make sense to translate it to some other data store, so I’ll design my custom adapter to use a JSONArray object as its data backer. This class is declared as an inner class definition in ListActivity.
private class TwitterJSONAdapter extends BaseAdapter { JSONArray data;
//Must be called on the main thread private void setData(JSONArray data){
this.data = data;
this.notifyDataSetChanged();
}
@Override
public int getCount() { if(data==null)
return 0;
else
return data.length();
}
COMPLEX LIST VIEWS 139
ptg7794906
@Override
public Object getItem(int position) { if(data==null)
return null;
try{
JSONObject element = (JSONObject)data.get(position);
return element;
}catch(Exception e){
return null;
} }
@Override
public long getItemId(int position) { return position;
}
This code, for the most part, wraps accessors to the JSON object. It handles getting an item from a position (which in this example is the index into the JSON array). If no data has been set, then the Adapter simply reports that there’s nothing to see. The only method in the example that doesn’t override a required function is the code that changes the data set. It also calls notifyDataSetChanged and, as a result of this method, must be called on the main thread. My class extends from BaseAdapter because it contains all the baseline methods that I need to build my custom adapter.
ptg7794906 BUILDING THE LISTVIEWS
At last you’ve come to the part where you get to build and return the individual custom list view elements. Here’s the code to do exactly that:
@Override
public View getView(int position, View convertView, p ViewGroup parent) {
JSONObject node = (JSONObject)getItem(position);
ViewGroup listView = null;
//Reduce, Reuse, Recycle!
if(convertView == null) listView =
(ViewGroup)getLayoutInflater().inflate (R.layout.twitter_list_item, null);
else
listView = (ViewGroup)convertView;
try{
boolean retweeted = node.getInt(“retweet_count”) > 0;
TextView tv =
(TextView)listView.findViewById(R.id.text_one);
tv.setText(node.getString(“text”));
if(retweeted)
tv.setTextColor(0xFFFF0000);
else
tv.setTextColor(0xFFFFFFFF);
tv = (TextView)listView.findViewById(R.id.text_two);
tv.setText(node.getString(“created_at”));
COMPLEX LIST VIEWS 141
ptg7794906 if(retweeted)
tv.setTextColor(0xFFFF0000);
else
tv.setTextColor(0xFFFFFFFF);
}catch(JSONException e){
Log.e(“TwitterView”,”Failed to set list item”,e);
}
return listView;
}
There are a couple of key points to consider in the getView code listing.
First, you need to figure out if the view can be recycled. If it can, you’ll reset all the visible values for it; otherwise, you’ll inflate a new row—by using the Layout Inflater—and configure it (more on how and why this works soon).
Second, you’ll detect, from the JSONObject, if the message has been retweeted by checking the retweet count. If it has, you’ll set the text color for both text views.
Last, you’ll pull both the text and created_at strings from the JSONObject and set them as the two text views. You might have noticed that I haven’t shown you what twitter_list_item.xml looks like. That is the view layout I’m creating (by calling the inflate method and passing in the layout).
THE CUSTOM LAYOUT VIEW
This layout has just two TextViews in it, with the very original IDs of text_one and text_two and can be found in res/layout/twitter_list_item:
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<TextView
ptg7794906
android:id=”@+id/text_one”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:padding=”6dp”/>
<TextView
android:id=”@+id/text_two”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:padding=”6dp”/>
</LinearLayout>
With this layout, you now have all the moving pieces you need to download, parse, and display a Twitter feed. Figure 5.2, at last, is what Peachpit’s Twitter feed looks like in ListView form.
FIGURE 5.2 The Twitter feed of Peachpit Press. Now in color!
COMPLEX LIST VIEWS 143
ptg7794906