Clickable ListView Items

package com.o1.android.view;

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

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

/**
 * This list adapter is derived from the "Efficient List Adapter"-Example of
 * API-Demos. It uses holder object to access the list items efficiently.
 * Additionally, click listeners are provided, which can be connected to the
 * arbitrary view items, e.g. customized checkboxes, or other clickable
 * Image/TextViews. Implement subclasses of them and add your listeners to your
 * "clickable" views.
 * 
 * @author poss3x
 */
public abstract class ClickableListAdapter extends BaseAdapter {
	private LayoutInflater mInflater;
	private List mDataObjects; // our generic object list
	private int mViewId;

	/**
	 * This is the holder will provide fast access to arbitrary objects and
	 * views. Use a subclass to adapt it for your personal needs.
	 */
	public static class ViewHolder {
		// back reference to our list object
		public Object data;
	}

	/**
	 * The click listener base class.
	 */
	public static abstract class OnClickListener implements
			View.OnClickListener {

		private ViewHolder mViewHolder;

		/**
		 * @param holder The holder of the clickable item
		 */
		public OnClickListener(ViewHolder holder) {
			mViewHolder = holder;
		}

		// delegates the click event
		public void onClick(View v) {
			onClick(v, mViewHolder);
		}

		/**
		 * Implement your click behavior here
		 * @param v  The clicked view.
		 */
		public abstract void onClick(View v, ViewHolder viewHolder);
	};

	/**
	 * The long click listener base class.
	 */
	public static abstract class OnLongClickListener implements
			View.OnLongClickListener {
		private ViewHolder mViewHolder;

		/**
		 * @param holder The holder of the clickable item
		 */
		public OnLongClickListener(ViewHolder holder) {
			mViewHolder = holder;
		}

		// delegates the click event
		public boolean onLongClick(View v) {
			onLongClick(v, mViewHolder);
			return true;
		}

		/**
		 * Implement your click behavior here
		 * @param v  The clicked view.
		 */
		public abstract void onLongClick(View v, ViewHolder viewHolder);

	};

	/**
	 * @param context The current context
	 * @param viewid The resource id of your list view item
	 * @param objects The object list, or null, if you like to indicate an empty
	 * list
	 */
	public ClickableListAdapter(Context context, int viewid, List objects) {

		// Cache the LayoutInflate to avoid asking for a new one each time.
		mInflater = LayoutInflater.from(context);
		mDataObjects = objects;
		mViewId = viewid;

		if (objects == null) {
			mDataObjects = new ArrayList<Object>();
		}
	}

	/**
	 * The number of objects in the list.
	 */
	public int getCount() {
		return mDataObjects.size();
	}

	/**
	 * @return We simply return the object at position of our object list Note,
	 *         the holder object uses a back reference to its related data
	 *         object. So, the user usually should use {@link ViewHolder#data}
	 *         for faster access.
	 */
	public Object getItem(int position) {
		return mDataObjects.get(position);
	}

	/**
	 * We use the array index as a unique id. That is, position equals id.
	 * 
	 * @return The id of the object
	 */
	public long getItemId(int position) {
		return position;
	}

	/**
	 * Make a view to hold each row. This method is instantiated for each list
	 * object. Using the Holder Pattern, avoids the unnecessary
	 * findViewById()-calls.
	 */
	public View getView(int position, View view, ViewGroup parent) {
		// A ViewHolder keeps references to children views to avoid uneccessary
		// calls
		// to findViewById() on each row.
		ViewHolder holder;

		// When view is not null, we can reuse it directly, there is no need
		// to reinflate it. We only inflate a new View when the view supplied
		// by ListView is null.
		if (view == null) {

			view = mInflater.inflate(mViewId, null);
			// call the user's implementation
			holder = createHolder(view);
			// we set the holder as tag
			view.setTag(holder);

		} else {
			// get holder back...much faster than inflate
			holder = (ViewHolder) view.getTag();
		}

		// we must update the object's reference
		holder.data = getItem(position);
		// call the user's implementation
		bindHolder(holder);

		return view;
	}

	/**
	 * Creates your custom holder, that carries reference for e.g. ImageView
	 * and/or TextView. If necessary connect your clickable View object with the
	 * PrivateOnClickListener, or PrivateOnLongClickListener
	 * 
	 * @param vThe view for the new holder object
	 */
	protected abstract ViewHolder createHolder(View v);

	/**
	 * Binds the data from user's object to the holder
	 * @param h  The holder that shall represent the data object.
	 */
	protected abstract void bindHolder(ViewHolder h);
}


// -------------------------------------------------------------
//                      E X A M P L E
// -------------------------------------------------------------

// LAYOUT FILE

<?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"
  android:gravity="center_vertical"
  >
  
<TextView android:text="Text" android:id="@+id/listitem_text"
			android:layout_weight="1" 
			android:layout_width="fill_parent" 
			android:layout_height="wrap_content"
			></TextView>
<ImageView android:id="@+id/listitem_icon"
			android:src="@drawable/globe2_32x32"
			android:layout_width="wrap_content" 
			android:layout_height="wrap_content"
			android:maxWidth="32px"
			android:maxHeight="32px"
			>
</ImageView>
</LinearLayout>



//--------------------------------------------------------------
// ACTIVITY
//--------------------------------------------------------------
package com.o1.android.view;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.o1.android.view.ClickableListAdapter;
import com.o1.android.view.ClickableListAdapter.ViewHolder;

/**
 * An example how to implement the ClickableListAdapter for a list view layout containing
 * a TextView and an ImageView.
 * @author poss3x
 */
public class ClickableListItemActivity extends ListActivity {

	/**
	 * Our data class. This data will be bound to 
	 * MyViewHolder, which in turn is used for the 
	 * ListView. 
	 */
	static class MyData {
		public MyData(String t, boolean e) {
			text = t;
			enable = e;
		}

		String text;
		boolean enable;
	}
	
	/**
	 * Our very own holder referencing the view elements
	 * of our ListView layout
	 */
	static class MyViewHolder extends ViewHolder {

		public MyViewHolder(TextView t, ImageView i) {
			text = t;
			icon = i;
		}
		TextView text;
		ImageView icon;
	}

	/**
	 * The implementation of ClickableListAdapter
	 */
	private class MyClickableListAdapter extends ClickableListAdapter {
		public MyClickableListAdapter(Context context, int viewid,
				List<MyData> objects) {
			super(context, viewid, objects);
			// nothing to do
		}

		protected void bindHolder(ViewHolder h) {
			// Binding the holder keeps our data up to date.
			// In contrast to createHolder this method is called for all items
			// So, be aware when doing a lot of heavy stuff here.
			// we simply transfer our object's data to the list item representatives
			MyViewHolder mvh = (MyViewHolder) h;
			MyData mo = (MyData)mvh.data; 
			mvh.icon.setImageBitmap(
					mo.enable ? ClickableListItemActivity.this.mIconEnabled
							: ClickableListItemActivity.this.mIconDisabled);
			mvh.text.setText(mo.text);

		}

		@Override
		protected ViewHolder createHolder(View v) {
			// createHolder will be called only as long, as the ListView is not filled
			// entirely. That is, where we gain our performance:
			// We use the relatively costly findViewById() methods and
			// bind the view's reference to the holder objects.
			TextView text = (TextView) v.findViewById(R.id.listitem_text);
			ImageView icon = (ImageView) v.findViewById(R.id.listitem_icon);
			ViewHolder mvh = new MyViewHolder(text, icon);

			// Additionally, we make some icons clickable
			// Mind, that item becomes clickable, when adding a click listener (see API)
			// so, it is not necessary to use the android:clickable attribute in XML
			icon.setOnClickListener(new ClickableListAdapter.OnClickListener(mvh) {

				public void onClick(View v, ViewHolder viewHolder) {
					// we toggle the enabled state and also switch the icon
					MyViewHolder mvh = (MyViewHolder) viewHolder;
					MyData mo = (MyData) mvh.data;
					mo.enable = !mo.enable; // toggle
					ImageView icon = (ImageView) v;
					icon.setImageBitmap(
							mo.enable ? ClickableListItemActivity.this.mIconEnabled
									: ClickableListItemActivity.this.mIconDisabled);
				}
			});

			// for text we implement a long click listener
			text.setOnLongClickListener(new ClickableListAdapter.OnLongClickListener(mvh) {

				@Override
				public void onLongClick(View v, ViewHolder viewHolder) {
					
					MyViewHolder mvh = (MyViewHolder) viewHolder;
					MyData mo = (MyData)mvh.data;
					
					// we toggle an '*' in our text element
					String s = mo.text;
					if (s.charAt(0) == '*') {
						mo.text = s.substring(1);
					} else {
						mo.text = '*' + s;
					}
					mvh.text.setText(mo.text);
				}

			});

			return mvh; // finally, we return our new holder
		}

	}

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

		// preloading our icons
		mIconEnabled = BitmapFactory.decodeResource(this.getResources(),
				R.drawable.globe2_32x32);
		mIconDisabled = BitmapFactory.decodeResource(this.getResources(),
				R.drawable.globe2_32x32_trans);
		
		// fill list with some items...
		// to demonstrate the performance we create a bunch of data objects
		for (int i = 0; i < 250; ++i) {
			mObjectList.add(new MyData("Some Text " + i, true));
		}
		//here we set our adapter
		setListAdapter(new MyClickableListAdapter(this,
				R.layout.clickablelistitemview, mObjectList));

	}

	// --------------- field section -----------------
	private Bitmap mIconEnabled;
	private Bitmap mIconDisabled;

	private List<MyData> mObjectList = new ArrayList<MyData>();

}

8 Comments

#
profete162 - October 29, 2009 at 9:33 a.m.

Seems to be very interesting.. I'll have a look!

#
Lars - November 2, 2009 at 11:53 p.m.

Brilliant! Just what I was looking for!

Thanks!

#
Nithin - November 17, 2009 at 11:03 a.m.

superb.. gud one..

#
jimmy - November 29, 2009 at 8:31 p.m.

Great code...how do you refresh the list when an entry is added or removed? Thanks.

#
Jonny Larsson - December 3, 2009 at 4:57 p.m.

Nice! But how is R.layout.clickablelistitemview defined in the xml file ??

#
sathya - December 16, 2009 at 1:13 p.m.

Hi jimmy,
Did u get any idea about refresh the list after adding or deleting entry?

Thanks

#
vijay Bhushan Tamanam - December 20, 2009 at 11:55 p.m.

Hi Satya, Try to extend the simpleCursor Adapter and do the same thing that was shown above, and you will see things much better, also your problem will be solved. When you add to the database, the cursor will have a new dataset and the list will get updated.

#
Ashish - February 19, 2010 at 10:07 a.m.

its good code..

Add a Comment