Fancy ListViews, Part Six: Custom Widget PDF Print E-mail

In this, the last and longest of our Fancy ListView posts, we’ll cover what it takes to wrap up the logic from the ChecklistDemo from a previous post and turn it into a reusable CheckListView that can serve as a drop-in replacement for ListView.

Before I go much further, though, please bear in mind that the next version of the Android SDK may have a similar component built into the framework. If so, I heartily encourage you to use the official one, for ease of long-term maintenance, and so your application is that much smaller. But, until then, or if you want to use the techniques shown here for some other custom ListView subclass, read on!

What we’d really like is to be able to create a layout like this:

 
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout 
  3.     xmlns:android="http://schemas.android.com/apk/res/android" 
  4.     android:orientation="vertical" 
  5.     android:layout_width="fill_parent" 
  6.     android:layout_height="fill_parent" > 
  7.     <TextView 
  8.         android:id="@+id/selection" 
  9.         android:layout_width="fill_parent" 
  10.         android:layout_height="wrap_content"/> 
  11.     <com.commonsware.android.fancylists.eight.CheckListView 
  12.         android:id="@android:id/list" 
  13.         android:layout_width="fill_parent" 
  14.         android:layout_height="fill_parent" 
  15.         android:drawSelectorOnTop="false" 
  16.         /> 
  17. </LinearLayout> 

where, in our code, almost all of the logic that might have referred to a ListView before “just works” with the CheckListView we put in the layout:

 
  1. public class CheckListViewDemo extends ListActivity { 
  2.     TextView selection; 
  3.     String[] items={"lorem", "ipsum", "dolor", "sit", "amet"
  4.                     "consectetuer", "adipiscing", "elit", "morbi", "vel"
  5.                     "ligula", "vitae", "arcu", "aliquet", "mollis"
  6.                     "etiam", "vel", "erat", "placerat", "ante"
  7.                     "porttitor", "sodales", "pellentesque", "augue"
  8.                     "purus"}; 
  9.  
  10.     @Override 
  11.     public void onCreate(Bundle icicle) { 
  12.         super.onCreate(icicle); 
  13.         setContentView(R.layout.main); 
  14.  
  15.         setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, items)); 
  16.         selection=(TextView)findViewById(R.id.selection); 
  17.     } 
  18.  
  19.     public void onListItemClick(ListView parent, View v, int position, long id) { 
  20.         selection.setText(items[position]); 
  21.     } 

The CheckListView might offer some additional methods, such as getCheckedPositions() to get a list of position indexes that were checked, or getCheckedObjects() to get the actual objects that were checked.

Where things get a wee bit challenging is when you stop and realize that, in all our previous work with fancy ListViews, never were we actually changing the ListView itself. All our work was with the adapters, overriding getView() and inflating our own rows, and whatnot.

So if we want CheckListView to take in any ordinary ListAdapter and “just work”, putting checkboxes on the rows as needed, we are going to need to do some fancy footwork. Specifically, we are going to need to wrap the “raw” ListAdapter in some other ListAdapter that knows how to put the checkboxes on the rows and track the state of those checkboxes.

First, we need to establish the pattern of one ListAdapter augmenting another. Here is the code for AdapterWrapper, which takes a ListAdapter and delegates all of the interface’s methods to the delegate:

 
  1. public class AdapterWrapper implements ListAdapter { 
  2.     ListAdapter delegate=null
  3.  
  4.     public AdapterWrapper(ListAdapter delegate) { 
  5.         this.delegate=delegate; 
  6.     } 
  7.  
  8.     public int getCount() { 
  9.         return(delegate.getCount()); 
  10.     } 
  11.  
  12.     public Object getItem(int position) { 
  13.         return(delegate.getItem(position)); 
  14.     } 
  15.  
  16.     public long getItemId(int position) { 
  17.         return(delegate.getItemId(position)); 
  18.     } 
  19.  
  20.     public int getNewSelectionForKey(int currentSelection, int keyCode, KeyEvent event) { 
  21.         return(delegate.getNewSelectionForKey(currentSelection, keyCode, event)); 
  22.     } 
  23.  
  24.     public View getView(int position, View convertView, ViewGroup parent) { 
  25.         return(delegate.getView(position, convertView, parent)); 
  26.     } 
  27.  
  28.     public void registerDataSetObserver(DataSetObserver observer) { 
  29.         delegate.registerDataSetObserver(observer); 
  30.     } 
  31.  
  32.     public boolean stableIds() { 
  33.         return(delegate.stableIds()); 
  34.     } 
  35.  
  36.     public void unregisterDataSetObserver(DataSetObserver observer) { 
  37.         delegate.unregisterDataSetObserver(observer); 
  38.     } 
  39.  
  40.     public boolean areAllItemsSelectable() { 
  41.         return(delegate.areAllItemsSelectable()); 
  42.     } 
  43.  
  44.     public boolean isSelectable(int position) { 
  45.         return(delegate. isSelectable(position)); 
  46.     } 

We can then subclass AdapterWrapper to create CheckableWrapper, overriding the default getView() but otherwise allowing the delegated ListAdapter to do the “real work”:

 
  1. public class CheckableWrapper extends AdapterWrapper { 
  2.     Context ctxt=null
  3.     boolean[] states=null
  4.  
  5.     public CheckableWrapper(Context ctxt, ListAdapter delegate) { 
  6.         super(delegate); 
  7.  
  8.         this.ctxt=ctxt; 
  9.         this.states=new boolean[delegate.getCount()]; 
  10.  
  11.         for (int i=0;i<delegate.getCount();i++) { 
  12.             this.states[i]=false
  13.         } 
  14.     } 
  15.  
  16.     public List getCheckedPositions() { 
  17.         List result=new ArrayList(); 
  18.  
  19.         for (int i=0;i<delegate.getCount();i++) { 
  20.             if (states[i]) { 
  21.                 result.add(new Integer(i)); 
  22.             } 
  23.         } 
  24.  
  25.         return(result); 
  26.     } 
  27.  
  28.     public List getCheckedObjects() { 
  29.         List result=new ArrayList(); 
  30.  
  31.         for (int i=0;i<delegate.getCount();i++) { 
  32.             if (states[i]) { 
  33.                 result.add(delegate.getItem(i)); 
  34.             } 
  35.         } 
  36.  
  37.         return(result); 
  38.     } 
  39.  
  40.     public View getView(int position, View convertView, ViewGroup parent) { 
  41.         ViewWrapper wrap=null
  42.         View row=convertView; 
  43.  
  44.         if (convertView==null) { 
  45.             LinearLayout layout=new LinearLayout(ctxt); 
  46.             CheckBox cb=new CheckBox(ctxt); 
  47.             View guts=delegate.getView(position, null, parent); 
  48.  
  49.             layout.setOrientation(LinearLayout.HORIZONTAL);  
  50.  
  51.             cb.setLayoutParams(new LinearLayout.LayoutParams( 
  52.                         LinearLayout.LayoutParams.WRAP_CONTENT, 
  53.                         LinearLayout.LayoutParams.FILL_PARENT)); 
  54.             guts.setLayoutParams(new LinearLayout.LayoutParams( 
  55.                         LinearLayout.LayoutParams.FILL_PARENT, 
  56.                         LinearLayout.LayoutParams.FILL_PARENT)); 
  57.  
  58.             cb.setOnCheckedChangeListener( 
  59.                 new CheckBox.OnCheckedChangeListener() { 
  60.                     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 
  61.                         states[(Integer)buttonView.getTag()]=isChecked; 
  62.                     } 
  63.             }); 
  64.  
  65.             layout.addView(cb); 
  66.             layout.addView(guts); 
  67.  
  68.             wrap=new ViewWrapper(layout); 
  69.             wrap.setGuts(guts); 
  70.             layout.setTag(wrap); 
  71.  
  72.             cb.setTag(new Integer(position)); 
  73.             cb.setChecked(states[position]); 
  74.  
  75.             row=layout; 
  76.         } 
  77.         else
  78.             wrap=(ViewWrapper)convertView.getTag(); 
  79.             wrap.setGuts(delegate.getView(position, wrap.getGuts(), parent)); 
  80.             wrap.getCheckBox().setTag(new Integer(position)); 
  81.             wrap.getCheckBox().setChecked(states[position]); 
  82.         } 
  83.  
  84.         return(row); 
  85.     } 

The idea is that CheckableWrapper is where most of our checklist logic resides. It puts the checkboxes on the rows and it tracks the checkboxes’ states as they are checked and unchecked. For the states, it has a boolean[] sized to fit the number of rows that the delegate says are in the list.

CheckableWrapper’s implementation of getView() is reminiscent of the one from ChecklistDemo, except that rather than use ViewInflate, we need to manually construct a LinearLayout to hold our CheckBox and the “guts” (a.k.a., whatever view the delegate created that we are decorating with the checkbox). ViewInflate is designed to construct a View from raw widgets; in our case, we don’t know in advance what the rows will look like, other than that we need to add a checkbox to them. However, the rest is similar to the one from ChecklistDemo, including using a ViewWrapper (below), hooking onCheckedChanged() to have the checkbox update the state, and so forth:

 
  1. class ViewWrapper { 
  2.     ViewGroup base; 
  3.     View guts=null
  4.     CheckBox cb=null
  5.  
  6.     ViewWrapper(ViewGroup base) { 
  7.         this.base=base; 
  8.     } 
  9.  
  10.     CheckBox getCheckBox() { 
  11.         if (cb==null) { 
  12.             cb=(CheckBox)base.getChildAt(0); 
  13.         } 
  14.  
  15.         return(cb); 
  16.     } 
  17.  
  18.     void setCheckBox(CheckBox cb) { 
  19.         this.cb=cb; 
  20.     } 
  21.  
  22.     View getGuts() { 
  23.         if (guts==null) { 
  24.             guts=base.getChildAt(1); 
  25.         } 
  26.  
  27.         return(guts); 
  28.     } 
  29.  
  30.     void setGuts(View guts) { 
  31.         this.guts=guts; 
  32.     } 

CheckableWrapper also has implementations of getCheckedPositions() and getCheckedObjects() that blend the state information with the delegate’s data to return the selections as indexes or objects.

With all that in place, CheckListView is comparatively simple:

 
  1. public class CheckListView extends ListView { 
  2.     public CheckListView(Context context) { 
  3.         super(context); 
  4.     } 
  5.  
  6.     public CheckListView(Context context, AttributeSet attrs, Map inflateParams) { 
  7.         super(context, attrs, inflateParams); 
  8.     } 
  9.  
  10.     public CheckListView(Context context, AttributeSet attrs, Map inflateParams, int defStyle) { 
  11.         super(context, attrs, inflateParams, defStyle); 
  12.     } 
  13.  
  14.     public void setAdapter(ListAdapter adapter) { 
  15.         super.setAdapter(new CheckableWrapper(getContext(), adapter)); 
  16.     } 
  17.  
  18.     public List getCheckedPositions() { 
  19.         return(((CheckableWrapper)getAdapter()).getCheckedPositions()); 
  20.     } 
  21.  
  22.     public List getCheckedObjects() { 
  23.         return(((CheckableWrapper)getAdapter()).getCheckedObjects()); 
  24.     } 

We simply subclass ListView and override setAdapter() so we can wrap the supplied ListAdapter in our own CheckableWrapper. We also surface the getCheckedPositions() and getCheckedObjects() to complete the encapsulation, so users of CheckListView have no idea that there is a wrapper in use.

Visually, the results are similar to the ChecklistDemo:

A demo of the CheckListView in action

The difference is in reusability. We could package CheckListView in its own JAR and plop it into any Android project where we need it. So while CheckListView is somewhat complicated to write, we only have to write it once, and the rest of the application code is blissfully simple.

Of course, this CheckListView could use some more features, such as programmatically changing states (updating both the boolean[] and the actual CheckBox itself), allowing other application logic to be invoked when a CheckBox state is toggled (via some sort of callback), etc. These are left as exercises for the reader.

This concludes the Fancy ListView blog post series. After the next SDK is released, we will revisit these and other Building ‘Droids posts, to let you know what all has changed that affects the code samples you’ve seen.

Next time, we’ll talk about doing something in Android that Apple is doing its level best to prevent in the iPhone: on-device scripting.

 

Source: http://androidguys.com/2008/07/31/fancy-listviews-part-six-custom-widget/

Comments (13)Add Comment
tiffany jewelry
written by tiffany jewelry , May 22, 2010
Next time, we’ll talk about doing something in Android that Apple is doing its level best to prevent in the iPhone: on-device scripting.
report abuse
vote down
vote up
Votes: +0
birkenstock shoes
written by birkenstock , May 29, 2010
When you are on a holiday in some seaside resort where you will hike and walk on cliffs, Birckenstock is one of the brands to which you turn your attention to find some slippers or flip-flops that make a holiday in comfort and without pain in the feet after a romantic walk on the rocks.Birkenstocks is the well-known German footwear brand: Birkenstock shoes, Birkenstock sandals and Birkenstock clogs.Birkenstock Gizeh is a flattering flip-flop style with a leather upper that will look great with shorts, jeans or dresses, at the same time, it has an irresistible air between the tourist and hiking, and you can be sure that the evening will have no pain, given the high quality these shoes.
report abuse
vote down
vote up
Votes: +0
...
written by jordan shoes , May 31, 2010
would a little more money make us a little happier? Many of us smirk and nod. There is, we believe, some connection between fiscal fitness and feeling fantasti air jordan shoes
. Most of us would say that, yes, we would like to DC. michael jordan shoes
Three in four American collegians now consider it.
report abuse
vote down
vote up
Votes: +0
...
written by mbt , June 18, 2010
contests administered by SUN, ACM, and IBM. He also had co-authored U.S.
report abuse
vote down
vote up
Votes: +0
...
written by mbt , June 18, 2010
"list" is a reference to an object implementing the List interface. Each element in this list is an object implementing the Map
report abuse
vote down
vote up
Votes: +0
I heartily encourage...
written by coach factory outlet , June 19, 2010
I heartily encourage you to use the official one, for ease of long-term maintenance, and so your application is that much smaller.
report abuse
vote down
vote up
Votes: +0
Cheap San Francisco 49ers Jerseys
written by sexy bikinis , June 24, 2010
It is not the NFL Jerseys critic who counts,not the man who points out how the strong man stumbles,the doer of deeds could have sexy bikinis sale done them better. The credit belongs to the man who is actually in the arens,whose face is marred by dust and sweat and blood.
report abuse
vote down
vote up
Votes: +0
mbt
written by mbt shoes , June 29, 2010
ugg boots
The credit belongs to the man who is actually in the arens,whose face is marred by dust and sweat and blood.
report abuse
vote down
vote up
Votes: +0
Blu Ray video
written by han , July 01, 2010
Want to get MP4 files from Blu Ray video? Here I recommend blu ray ripper
report abuse
vote down
vote up
Votes: +0
shopping
written by fashion , July 05, 2010
the sun Gucci handbagstarts shining.
plants start growing.
flowers Y-3 shoestartopening.
birds start singing
And making Prada handbagtheir nests.
spring is here.
the hot Vibram rock shoesun'shining.
we start having fun.
we start Nike Air max shoesswimming.
we start eating ice cream.
the holidaysArmani are coming.
summer is here.
the wind Gucci shoesstarts blowing.
kites start flying.
leaves start Alife shoefalling.
we start having barbecues.
the holidays LV T-shirt menare ending.
autumn is here.
the snow Chanl shoestarts falling.
animals start sleeping.
dark days Armani jeansare coming.
the temperature's dropping.
we startBurberry shoe shivering.
winter is here.
report abuse
vote down
vote up
Votes: +0
...
written by nfl jerseys , July 27, 2010
ghd hair straighteners
ed hardy clotheswansantg1zxl
report abuse
vote down
vote up
Votes: +0
Famous Hats
written by Famous Hats , July 28, 2010
New Era Hats
New Era Caps
report abuse
vote down
vote up
Votes: +0
...
written by Abercrombie fitch outlet , July 29, 2010
now we have a playlist ListActivity and a Service to handle playing the music, but we still have no way to control the music. In the next section we will create a graphical user interface for the controls, introducing ImageViews and Animation...


report abuse
vote down
vote up
Votes: +0

Write comment
quote
bold
italicize
underline
strike
url
image
quote
quote
smile
wink
laugh
grin
angry
sad
shocked
cool
tongue
kiss
cry
smaller | bigger

security code
Write the displayed characters


busy
 

 

This domain is for sale! Please contact me via contact page!