[Android] Adding Selection Functionality to the Table View

Lately, I was looking at how to present data in a table format, where each row would be data from an object, and allow users to select a particular row. One way of doing it is writing a ListAdapter and using it with a ListView. However, the way I tried to do it was using the TableLayout.

Admittedly, using the TableLayout looked like it is more complicated than using the ListView, although I have yet to try using ListView. Using the TableLayout, the view is programatically created (without using an XML file). In order to implement this, the implementing class needs to keep track of a number of things.


  1 package com.android.selectable;
  2
  3 import java.util.Collection;
  4
  5 import android.content.Context;
  6 import android.graphics.Color;
  7 import android.util.Log;
  8 import android.view.MotionEvent;
  9 import android.view.View;
 10 import android.widget.TableLayout;
 11 import android.widget.TableRow;
 12
 13 /**
 14  * Manages the display of the watch list.
 15  *  
 16  * @author Kah
 17  */
 18 public class ListDisplay<T>
 19 {
 20   /**
 21   * Reference to the list of stocks that are in the
 22   * watch list.
 23   */
 24   private final Collection<T> itemsCollection;
 25   
 26   /**
 27    * Reference to the view that displays the watch
 28    * list on the user interface. This needs to
 29    * inflated later, since the instance of the
 30    * layout inflater is required to create it.
 31    */
 32   private final TableLayout display;
 33
 34   private final Context appContext;
 35
 36   private final DisplayAdapter<T> dataAdapter;
 37
 38   private SelectionReceiver<T> selectionObserver;
 39   
 40   private T highlighted;
 41   

The DisplayAdapter is used to convert the objects into the data that gets displayed. A SelectionReceiver is notified when a item is selected by clicking on it.

The constructor creates the TableLayout, which may be set as the content view or may be added to another view.
 67   /**
 68    * Retrieves the display component to display the
 69    * contents of the watch
 70    * list on the user interface.
 71    *
 72    * @return The display view for viewing the contents
 73    *         of the watch list.
 74    */
 75   public View getDisplay() 
 76   {
 77     return display;
 78   }
 79
 80   /**
 81    * Retrieves the last item that was selected, but not
 82    * necessarily "clicked".
 83    *
 84    * @return The item that is currently at the cursor.
 85    */
 86   public T getSelected() 
 87   {
 88     return highlighted;
 89   }
 90

Next, the data from the list needs to be loaded into the table. This is done by adding a series table rows. Each row needs to be made focusable. Listeners are added to row to handle the highlighting and when the user clicks on a row. For highlighting, the OnFocusChangeListener and OnTouchListener are required. Clicking is handled by OnClickListener.

 91   /**
 92    * Loads the current contents of the list to the
 93    * displayed list. 
 94    */
 95   private void initialiseDisplay() 
 96   {
 97     // All rows will have the same parameters, so just
 98     // create it once.
 99     final TableRow.LayoutParams rowParams = new 
100       TableRow.LayoutParams(
101         TableRow.LayoutParams.FILL_PARENT,
102         TableRow.LayoutParams.WRAP_CONTENT);
103     
104     
105     // Go through the list and create table entries for
106     // each one.
107     for (T item: itemsCollection) 
108     {
109       TableRow newRow = new TableRow(appContext);
110       newRow.setLayoutParams(rowParams);
111       
112       View[] content = dataAdapter.getContent(item);
113       for (int index = 0; index < content.length; index++) 
114       {
115         newRow.addView(content[index]);
116       }
117       
118       newRow.setFocusable(true);
119       newRow.setFocusableInTouchMode(true);
120       newRow.setOnFocusChangeListener(
121         new RowHighlighter(item));
122       newRow.setOnClickListener(new RowSelector(item));
123
124       display.addView(newRow);
125     }
126   }
127   

Notifications about which item the user clicks is sent via the SelectionReceiver. This is an external class that we define. Of course, there needs to be a way of setting which instance of the SelectionReceiver will receive the notification.

128   /**
129    * Changes the receiver that is notified when an item
130    * is "clicked".
131    *
132    * @param receiver
133    *      The receiver that will be notified of changes.
134    */
135   public void setSelectionReceiver(
136    SelectionReceiver<T> receiver)
137   {
138     this.selectionObserver = receiver;
139   }
140   

The definition for the highlighting class:

141   /**
142    * This focus change listener handles the changing the
143    * background colour for highlighting the focused row.
144    * It also updates the variable keeping track the
145    * highlighted item.
146    */
147   private class RowHighlighter implements 
148     View.OnFocusChangeListener, View.OnTouchListener
149   {
150
151     /**
152      * The item that the highlighter is associated with. 
153      */
154     private final T association;
155     
156     public RowHighlighter(T association) 
157     {
158       this.association = association;
159     }
160     
161     /**
162      * {@inheritDoc}
163      */
164     @Override
165     public void onFocusChange(View v, boolean hasFocus)
166     {
167       int bgColour = Color.TRANSPARENT;
168       if (hasFocus == true
169       {
170         bgColour = Color.RED;
171         highlighted = association;
172       }
173
174       v.setBackgroundColor(bgColour);
175     }
176
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     public boolean onTouch(View v, MotionEvent event)
182     {
183       if (event.getAction() != MotionEvent.ACTION_UP) 
184       {
185         v.requestFocus();
186       }
187       return false;
188     }
189     
190   }
191   

Finally, the internal class for updating the row that is selected.

192   /**
193    * Handles the "clicking" on a row.
194    *
195    * @author Kah
196    */
197   private class RowSelector implements View.OnClickListener
198   {
199     private final T association;
200     
201     public RowSelector(T association) 
202     {
203       this.association = association;
204     }
205     
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     public void onClick(View v)
211     {
212       if (selectionObserver != null) {
213         selectionObserver.itemSelected(association);
214       }
215     }
216   }
217 }
218  
219

If you wish to download the source code, you can get it from here. As mentioned previously, this seems a long way of achieving a selectable list of items with three columns. Using a ListView, the item selection and item highlighting is taken care of. But how to use ListView is left for another day.

Advertisements

8 Responses to [Android] Adding Selection Functionality to the Table View

  1. kahgoh says:

    In reply to:

    Hi Kah:

    I am new to Android programming.
    I am trying to get “Adding Selection Functionality to the Table View” to work and I can’t.
    I tried to download it and I get an error, so I copied it from the web page.

    Currently, I am getting cannot be resolved to a type for:
    dataAdapter
    SelectionReceiver

    Also:
    The type ListDisplay.RowHighlighter must implement the inherited abstract method
    View.OnTouchListener.onTouch(View, MotionEvent)

    I feel there is something missing (like the data to initialize the display).

    Can you send me/post a completed working version?

    thanks in advance.

    Sandy

    Hi Sandy,
    A lot of my Android tutorials are quite old, so a lot of them are most likely to be out of date. In any case, I’ve just tried downloading my own source files and importing them with the latest version of the SDK (2.1) and I was able to set it up. These are the steps I followed to get it working in Eclipse, starting from a workspace:

    Firstly, you need to set an Android project in Eclipse.
    1. In the Java perspective, right click in the Package Explorer panel and select New -> Project ….
    2. In the New Project dialog, select Android -> Android Project. Click on the Next button.
    3. Enter a name for your project. Under Build Target ensure that you have an option selected.
    4. Under Properties at the bottom of the dialog, for Application Name enter “.selectable.test.Selectable“and for Package name enter “com.android“. Untick Create Activity and click on Finish.
    5. Open up an explorer window (or My Computer) and use that to copy the src folder from the source folder over the projects src folder on your computer’s file system.
    6. Back in Eclipse right click on your project and select Refresh.

    Now open your run configurations to create a configuration for running the example.

  2. zapping says:

    Is there a code download available for the tutorials?

  3. Joel Parker says:

    How would I add an onClickListener for only the first row of a static TableView ? I want to click on the first row and have it bring up a dialog but only the first row no others. Code sample would be helpful.

    • kahgoh says:

      Try tracking the first row. You can do this with a simple boolean flag. Something like this:

           ...
           boolean isFirst = true;
           for (T item: itemsCollection) 
           {
             TableRow newRow = new TableRow(appContext);
             ...
             if (isFirst) 
             {      
                newRow.setOnClickListener(new RowSelector(item));
                isFirst = null;
             }
             ...
          }
          ...
      
  4. Kenneth F Kristensen says:

    Hi I have implemented your code with great succes 🙂

    However I would like to be to able to capture the “event” that the user clicks twice on the same row.

    I’m trying a solution with saving the v.getId() variable.

    I have tried out several metodes, but none seems to capture each click, hope you have some input

    • kahgoh says:

      The first method that I think of is using an OnClickListener that tracks the last time the view was clicked. You could do this simply with a Map<View, Long> or by tagging the View (use thegetTag and setTag methods to do this). When it gets clicked, it compares the “current” time against the “last” time it was clicked and decide whether a double click has occurred. I’ll see if I can make up an example soon.

      • Kenneth F Kristensen says:

        Hi, sorry about not getting back to you for such a long time. I got it to work and forgot all about my post here. What I did was create an int variable. In onFocusChange I save the views getID value in the int variable. In the rowSelectors onClick I check if its view getID value is the same as the one stored in my variable. Might not be the best solution, but it works 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: