The Listview in android could need a little getting used to if you are a newbie. I first met the listview while looking to design a custom list of items, which exhibits a unique behaviour classified into 2 categories,

1. All of items in the list would have a radiobutton and would be uniquely selectable.

2. A specific set of items would have an edit button(along with the radiobutton) & the item title would become editable if the edit button is clicked.

All in all I wanted my layout to look like,

list1

Above Image is a collection of those items which dos not have an edit button. Other set of that this list contains would look like,

list2

On selecting a particular item and then pressing the button, user would be greeted with an EditText Dialog as shown below,

list3

Let’s see step by step that how one should go about achieving this functionality

The primary requirement of the list in question is a listview. We can define a simple ListView in a listlayout.xml file,

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:choiceMode="singleChoice"
android:id="@+id/triggerList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"
/>

Now the listview contains several row items, so to accomplish this we would be needing a row template which would define the layout of each row item. This can be defined in a file listrowtemplate.xml(Both the xml files will go inside the layout folder),

<?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:padding="5dip" android:orientation="horizontal"
	android:gravity="center_vertical">
	<RadioButton android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:id="@+id/btn_radio" />

	<TextView android:id="@+id/triggerLabel" android:layout_width="fill_parent"
		android:layout_height="wrap_content" style="@style/BodyTextBigGrey"
		android:padding="5dip" android:gravity="center_horizontal|center_vertical"
		android:textSize="20dip" android:layout_weight="1" />
	<Button android:id="@+id/buttonEdit" android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:text="Edit"
		android:layout_gravity="right" />
</LinearLayout>

Now,To take this further to the next level, One must know that a ListView exhibits an arcane property called as View Recycling i.e. The rows that are not getting displayed are recycled to save memory. Although sounding simple, this can sometimes become a bit dicey especially for custom lists(the one like ours) as we will need to maintain the state of our selected radio button if it undergoes the recycling process.

There can be numerous approaches to tackle this problem. We discuss one such approach here. We will need a model to main tain the current state of our rows & this is where the developer meets the Trigger class(Shakespere said, What’s in a Name?). The Trigger class is our model to maintain the state of various trigger rows added to our list. This is what the trigger class looks like,(Please mind that I’m posting the development code here and not the release one so one can always encounter a few trivial redundancies),

public class Trigger {

    private String triggerName;
    private boolean triggerStatus;
    protected TextView text;
    protected RadioButton radiobutton;
    protected Button button;

    public String getTriggerName() {
        return triggerName;
    }
    public void setTriggerName(String triggerName) {
        this.triggerName = triggerName;
    }
    public boolean getStatus() {
        return triggerStatus;
    }
    public void setStatus(boolean triggerStatus) {
        this.triggerStatus = triggerStatus;
    }
}

Now, Our ListView can maintain an ArrayList of these Triggers and we can also take care of the View Recycling process using the various getters and setters defined in our Trigger Class.
The bond between the ListView and its Data(Trigger items) is forged by something called as an Adapter. We would be needing a custom adapter class, to override its its getView method which has a signature like public View getView(final int position, View convertView, ViewGroup parent). Doing this would give us a fine control over the view recycling process because if a view has been recycled then the android system call the getView with convertView as null and this is how we would come to know that we need to reinstantiate that view and also restore it to its previous state i.e. as it was before recycling. Considering this, below is the implementation of our Trigger Adapter,

public class TriggerAdapter extends ArrayAdapter<Trigger> {

	private ArrayList<Trigger> items = null;
	private Context context = null;
	private String tracker = null;
	private ArrayList<Boolean> itemChecked = new ArrayList<Boolean>();

	public TriggerAdapter(Context context, int textViewResourceId,
			ArrayList<Trigger> items) {
		super(context, textViewResourceId, items);
		this.context = context;
		this.items = items;
		for (int i = 0; i < this.items.size(); i++) {
			itemChecked.add(i, false); // initializes all items value with false
		}
	}

public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
	LayoutInflater vi = (LayoutInflater) context
	.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	v = vi.inflate(R.layout.listrowtemplate, null);
}
final Trigger trigger = items.get(position);
if (trigger != null) {
	trigger.text = (TextView) v.findViewById(R.id.triggerLabel);
	trigger.radiobutton = (RadioButton) v.findViewById(R.id.btn_radio);
	trigger.button = (Button) v.findViewById(R.id.buttonEdit);
	if (trigger.text != null) {

		trigger.text.setText(trigger.getTriggerName());
	}
	if (trigger.button != null) {
		if (position > 10) {
			trigger.button.setVisibility(View.VISIBLE);
		} else {
			trigger.button.setVisibility(View.INVISIBLE);
		}
	trigger.button.setOnClickListener(new OnClickListener() {
		public void onClick(View v) {
			final Trigger trigger = items.get(position);
			if (!trigger.getStatus()) {
				return;
			}
			AlertDialog.Builder alert = new AlertDialog.Builder(
					context);
			alert.setTitle("Title");
			alert.setMessage("Message");
			// Set an EditText view to get user input
			final EditText input = new EditText(context);
			alert.setView(input);
			alert.setPositiveButton("Ok",
					new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog,
						int whichButton) {
					String value = input.getText()
					.toString();
					// Do something with value!
					final Trigger trigger = items
					.get(position);
					trigger.setTriggerName(value);
					trigger.text.setText(value);
					items.remove(position);
					items.add(position, trigger);
				}
			});
			alert.setNegativeButton("Cancel",
					new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog,
						int whichButton) {
					// Canceled.
				}
			});

			alert.show();
			}
		});
	}
	if (trigger.radiobutton != null) {
		trigger.radiobutton.setChecked(trigger.getStatus());
		trigger.radiobutton.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {
		     for (int i = 0; i < items.size(); i++) {
			final Trigger trig = items.get(i);
			if (trig.getStatus()) {
			trig.setStatus(false);
			trig.radiobutton.toggle();
			trig.radiobutton.invalidate();
			ListView list = (ListView) (((LinearLayout) v
						.getParent()).getParent());
			for (int j = 0; j < list.getChildCount(); j++) {
			LinearLayout l = (LinearLayout) list
			.getChildAt(j);
			RadioButton rd = (RadioButton) l
			.getChildAt(0);
			if (rd.isChecked()) {
				rd.setChecked(false);
			}
			}
			}
		}
				((RadioButton) v).setChecked(true);
				final Trigger trigger = items.get(position);
				trigger.setStatus(true);
				trigger.radiobutton.setChecked(true);
				items.remove(position);
				items.add(position, trigger);
			}
		});
	}
}
return v;

}

All in all, our list is now complete and we just need an activity with a button to display it(It actually depends on your choice how you want to display it), Mine is a simple activity with a button and on the click of the button the list is displayed,

On the click of the button, we actually create an AlertDialog and set the ListView as its content after populating it,

public class CustomListActivity extends Activity {

	private ArrayList<Trigger> mTriggers = null;
	private TriggerAdapter mAdapter = null;
	ListView list = null;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		((Button) findViewById(R.id.btn_list))
		.setOnClickListener(mClickListener);
	}

	protected void onStart() {
		super.onStart();
		String[] trigger_name = getResources().getStringArray(R.array.triggers);
		mTriggers = new ArrayList<Trigger>();
		for (int i = 0; i < trigger_name.length; i++) {
			final Trigger trigger = new Trigger();
			trigger.setTriggerName(trigger_name[i]);
			trigger.setStatus(false);
			mTriggers.add(trigger);
		}
		list = (ListView) getLayoutInflater()
		.inflate(R.layout.listlayout, null);
		list.setItemsCanFocus(true);
		list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

	};

	OnClickListener mClickListener = new OnClickListener() {
		@Override
		public void onClick(final View view) {
			final AlertDialog.Builder builder = new AlertDialog.Builder(view
					.getContext());
			builder.setTitle(getResources().getString(R.string.trigger));
			mAdapter = new TriggerAdapter(CustomListActivity.this,
					R.layout.listrowtemplate, mTriggers);
			list.setAdapter(mAdapter);
			builder.setView(list);
			LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
					LinearLayout.LayoutParams.FILL_PARENT,
					LinearLayout.LayoutParams.WRAP_CONTENT);
			builder.setView(list);
			final AlertDialog alert = builder.create();
			if (!alert.isShowing()) {
				alert.show();
			}
		}
	};
}

So this was a bit about creating Custom Lists in android. Hope you enjoyed it.

If you have any questions/doubts/improvements, please feel free to post those.