Introducing Android to the concept of ItemRenderers

Project With Source Now Posted, See: http://hat6.com/2009/11/27/itemrenderers-updated/

Coming from the Flex world, it’s strange not to see the idea of “ItemRenderers” in Android.  Customizing the way a List Item looks is much more manual than in Flex, so I’ve come up with a ListAdapter that allows for some of the flexibility/framework-ness that I enjoy in Flex.

I started from this tutorial and modified it to be more Flex-esque.  I’ve tried to keep things as simple and generic as possible.  This is my first draft, so it’s pretty bare-bones, but it does what is needed initially.

To have a List in an Android App, we extend the ListActivity class for a new Activity (I’m only focusing on Activities for now).  From this Activity, we declare and set an Adapter.  The sample code shows using and ArrayAdapter, while this post shows using a SimpleCursorAdapter.  Both of these examples show how to use layouts distributed with Android, but don’t allow much in the way of customization.  They’re poorly documented and searches for “simple_list_item_1″ returns more questions than answers.

So I’ve written my own Adapter that allows you to pass a class to the adapter.  Simply implement IDataRenderer and provide the following constructor: public MyDataRenderer(Context context)

com.hatsix.ListRenderer.RenderedListAdapter.java:

package com.hatsix.ListRenderer;

/*
*
* Copyright 2009 Dusty Jewett
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class RenderedListAdapter extends BaseAdapter {

/** Remember our context so we can use it when constructing views. */
private Context mContext;
private Class itemRenderer;

private List mItems;

public RenderedListAdapter(Context context, List items, Class renderer) {
mContext = context;
mItems = items;
itemRenderer = renderer;
}

public void setListItems(List list) { mItems = list; }

/** @return The number of items in the */
public int getCount() { return mItems.size(); }

public Object getItem(int position) { return mItems.get(position); }

public boolean areAllItemsSelectable() { return false; }

public boolean isSelectable(int position) {
/*
try{
return mItems.get(position).isSelectable();
}catch (IndexOutOfBoundsException aioobe){
//return super.isSelectable(position);
return false;
}
*/
return true;
}

/** Use the array index as a unique id. */
public long getItemId(int position) {
return position;
}

/** @param convertView The old view to overwrite, if one is passed
* @returns IDataRenderer View that has had it's data assigned to it */
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
try {
View idr;
Class[] rendArgsClass = new Class[] {Context.class};
Object[] rendArgs = new Object[] {mContext};
Constructor rendererConstructor = itemRenderer.getConstructor(rendArgsClass);
idr = (View) createObject(rendererConstructor, rendArgs);
if(idr instanceof IDataRenderer){
((IDataRenderer) idr).setData(mItems.get(position));
}

} catch (NoSuchMethodException e) {
e.printStackTrace();
}//(mContext, mItems.get(position));
}
return convertView;
}
public static Object createObject(Constructor constructor, Object[] arguments) {
Object object = null;
try {
object = constructor.newInstance(arguments);
System.out.println ("Object: " + object.toString());
return object;
} catch (InstantiationException e) {
System.out.println(e);
} catch (IllegalAccessException e) {
System.out.println(e);
} catch (IllegalArgumentException e) {
System.out.println(e);
} catch (InvocationTargetException e) {
System.out.println(e);
}
return object;
}
}

The Interface is super simple, just a setData method:

com.hatsix.ListRenderer.IDataRenderer

package com.hatsix.ListRenderer;

public interface IDataRenderer {
void setData(Object rawData);
Object getData();
}

This class is easy to instantiate in your application:

package com.hatsix;

import java.util.ArrayList;
import java.util.List;

import com.hatsix.ListRenderer.RenderedListAdapter;
import com.hatsix.constantreminder.Reminder;
import com.hatsix.constantreminder.ReminderListItemRenderer;

import android.app.ListActivity;
import android.os.Bundle;

public class RemindersList extends ListActivity {
/** Called when the activity is first created. */
private List<Reminder> reminders;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
reminders = new ArrayList<Reminder>();
// Add four items
reminders.add(new Reminder("Remember Once","today", "tomorrow", getResources().getDrawable(R.drawable.finger) ));
reminders.add(new Reminder("Bell Reminder","today", "tomorrow", getResources().getDrawable(R.drawable.bell)));
reminders.add(new Reminder("Laundry Reminder","today", "tomorrow", getResources().getDrawable(R.drawable.laundry)));
reminders.add(new Reminder("Task Reminder","today", "tomorrow", getResources().getDrawable(R.drawable.task)));

// Display it
RenderedListAdapter rla = new RenderedListAdapter(this, reminders, ReminderListItemRenderer.class);
setListAdapter(rla);
}
}

And the ItemRenderer is really easy. We instantiate our controls, and set the values in setData():
com.hatsix.constantreminder.ReminderListItemRenderer

package com.hatsix.constantreminder;

import com.hatsix.ListRenderer.IDataRenderer;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ReminderListItemRenderer extends LinearLayout implements IDataRenderer {
private Object data;
private Reminder theReminder;
private TextView mTitle;
private TextView mDescription;
private ImageView mIcon;
private LinearLayout mTextLayout;

public ReminderListItemRenderer(Context context, Object rawData) {
super(context);
this.setOrientation(HORIZONTAL);
/* We're setting up a view that has one icon on the left, and two lines
* of text on the right.
*
* First the Icon
**/
mIcon = new ImageView(context);
mIcon.setPadding(0, 2, 5, 0); // 5px to the right
// left, top, right, bottom (Can't we ever standardize TRBL?)
addView(mIcon,  new LinearLayout.LayoutParams(32,32));

/**
*  Now we do the text lines
*/
mTextLayout = new LinearLayout(context);
mTextLayout.setOrientation(VERTICAL);
addView(mTextLayout);

/* Title */
mTitle = new TextView(context);
mTextLayout.addView(mTitle, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

/* Description */
mDescription = new TextView(context);
mTextLayout.addView(mDescription, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
setData(rawData);
}

public void setData(Object rawData){
data = rawData;
if(rawData.getClass() == Reminder.class){
theReminder = (Reminder) data;
mIcon.setImageDrawable(theReminder.getIcon());
mTitle.setText(theReminder.title);
mDescription.setText(theReminder.getDescription());
}
}
}

And finally, the POJO used to pass this data around:

com.hatsix.constantreminder.Reminder

package com.hatsix.constantreminder;

import android.graphics.drawable.Drawable;

public class Reminder {
public String title;
public String start;
public String end;
private Drawable mIcon;
private boolean mSelectable = true;

public Reminder(String t, String s, String e, Drawable icon){
title = t;
start = s;
end = e;
mIcon = icon;
}
public String getDescription(){
return start + " - " + end;
}
public Drawable getIcon(){
return mIcon;
}

}

2 comments ↓

#1 Tweets that mention Introducing Android to the concept of ItemRenderers — h6 – HatSix -- Topsy.com on 11.22.09 at 11:30 pm

[...] This post was mentioned on Twitter by CT, Adobelovenews. Adobelovenews said: Introducing Android to the concept of ItemRenderers: Coming from the Flex world, it’s strange not to see .. http://bit.ly/08Bepv3 [...]

#2 Android DataServices — h6 – HatSix on 12.05.09 at 6:34 pm

[...] fetch all Reminders, then pass the ArrayList over to our ItemRenderers via the RenderedListAdapter (discussed here). It isn’t as generic as a full-on ORM, but it’s very [...]