Programming and Personal Development

Android ListView Part 3

In part 2 of the series, we discussed how android’s ArrayAdapter class internally reuses views to optimize performance. However reusing views has the side effect of losing any state associated with the current data, as the view is recycled andupdated with new data. Lets say, we want to change the appearance of the text when the user clicks on a element within ListView. Just building that logic  as part of  the onListItemClick () method (as we did for the previous examples) won’t help because we lose this information as soon as the user scrolls the ListView.  So we need some place to store and retrieve this information based on the context.

Android ListView Demo for using custom adapters

ListView Demo using Custom Adapters

Android conveniently provides a getTag () and setTag () methods on all View objects, that will let you associate any arbitrary objects. We’ll see how to use this using a simple example.

Example

For this example, we’ll convert the text in the ListView element to uppercase and surround it in quotes as soon as the user clicks on the element using a custom adapter class. The input to our custom adapter is a dummy array of Strings labeled Test0 to Test14.
private static final String[] s_dummyItems = new String[15];
 
static {
for (int i = 0; i < s_dummyItems.length; i++) {
   s_dummyItems[i] = "Test " + i;
}
}

The fact that the user clicked on a particular element in the ListView is a state that must be stored and checked every time we try to render  the element on the screen.  We encapsulate this state in a separate class as shown below and associate it with the View object passed as argument to our custom adapter’s getView () method. It consists of a boolean array representing whether the element has been clicked or not and contains methods for reading and writing this state.
private class State {
   private boolean[] caps = new boolean[s_dummyItems.length];
 
   public void setCaps(int position) {
      if (position < caps.length) {
         caps[position] = true;
      }
   }
 
   public boolean isCaps(int position) {
      return caps[position];
   }
}
The Layout for our Custom Adapter class is again a simple linear layout that contains a single TextView element.
<?xml version="1.0" encoding="utf-8"?>
 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:orientation="vertical"
              android:layout_height="fill_parent">
 <TextView android:id="@+id/adapter_bold_text"
           android:text="Test"
           android:lines="3"
           android:textSize="20dip"
           android:gravity="center_horizontal"
           android:layout_gravity="center"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"/>
</LinearLayout>


Lets now look at the logic within the onListItemClick () callback method that gets called each time the user clicks on a ListView element.  The View argument to the method is our custom layout shown above. The first time its called the State is null. We create a new instance of State object, set the boolean value corresponding to the “position” argument to true and associate the State object with the View using the setTag() method. For subsequent calls, we modify the same state object. Thus for each item that is clicked we mark its state and keep track of it within the View object.

protected void onListItemClick(ListView l, View v, int position, long id) {
   State state = (State) v.getTag();
   if (state == null) {
      state = new State();
   }
   state.setCaps(position);
   v.setTag(state);
}

We then check this State object each time we want to render the  ListView element on the screen by overriding the getView () method within our custom adapter class.  The custom adapter class is a inner class within our Demo Activity. We pass the following elements in the constructor  of the Custom Adapter class:
  • Context, which is the Activity itself
  • Custom layout
  • TextView element within it and
  • Dummy input Strings that we initialized earlier.

Finally, in the getView() method, we check for the existence of State object within the View argument. If it not null, we get the reference to the TextView element within the View using the findViewById () method and change the case of the text and surround it with quotes. The same pattern can be used to do something more useful than changing the characters to uppercase.

private class BoldViewAdapter extends ArrayAdapter<String> {
   public BoldViewAdapter() {
 
      super(ListViewInteractDemo.this,
              R.layout.bold_text_adapter_view,
              R.id.adapter_bold_text, s_dummyItems);
   }
 
   @Override
   public View getView(int position, View convertView, ViewGroup parent) {
      View customView = super.getView(position, convertView, parent);
      State state = (State) customView.getTag();      TextView txtView = (TextView) customView.
              findViewById(R.id.adapter_bold_text);      String text = txtView.getText().toString();
      if (state != null && state.isCaps(position)) {
         txtView.setText("' " + text.toUpperCase() + " '");
      } else {         // undo if any changes you make to the parent view also      }      return customView;
   }
}

Also note the blank else block. This is a place holder to undo any changes done to the View object itself. Suppose, in addition to changing the case of the text, you also changed the background color of the TextView element itself, for the clicked item. Since the same View instance is reused, you might also notice the color change on the items where the user has not yet clicked which  is undesirable. You can undo such changes within the else block if necessary.
Web Analytics