IT tutorials
 
Mobile
 

Android : Getting Fancy with Lists - Interactive Rows

2/25/2013 6:32:34 PM
- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire

Lists with pretty icons next to them are all fine and well. But, can we create ListView widgets whose rows contain interactive child widgets instead of just passive widgets like TextView and ImageView? For example, there is a RatingBar widget that allows users to assign a rating by clicking on a set of star icons. Could we combine the RatingBar with text to allow people to scroll a list of, say, songs and rate them right inside the list? There is good news and bad news.

The good news is that interactive widgets in rows work just fine. The bad news is that it is a little tricky, specifically when it comes to taking action when the interactive widget's state changes (e.g., a value is typed into a field). We need to store that state somewhere, since our RatingBar widget will be recycled when the ListView is scrolled. We need to be able to set the RatingBar state based on the actual word being viewed as the RatingBar is recycled, and we need to save the state when it changes so it can be restored when this particular row is scrolled back into view.

What makes this interesting is that, by default, the RatingBar has absolutely no idea which item in the ArrayAdapter it represents. After all, the RatingBar is just a widget, used in a row of a ListView. We need to teach the rows which item in the ArrayAdapter they are currently displaying, so when their RatingBar is checked, they know which item's state to modify.

So, let's see how this is done, using the activity in the FancyLists/RateList sample project. We will use the same basic classes that we used in our previous example. We are displaying a list of nonsense words, which can then be rated. In addition, words given a top rating are put in all caps.

package com.commonsware.android.fancylists.six;

import android.app.Activity;
import android.os.Bundle;
import android.app.ListActivity;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.RatingBar;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;

public class RateListDemo extends ListActivity {
  private static final String[] items={"lorem", "ipsum", "dolor",
          "sit", "amet",
          "consectetuer", "adipiscing", "elit", "morbi", "vel",
          "ligula", "vitae", "arcu", "aliquet", "mollis",
          "etiam", "vel", "erat", "placerat", "ante",
          "porttitor", "sodales", "pellentesque", "augue", "purus"};

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

    ArrayList<RowModel> list=new ArrayList<RowModel>();

    for (String s : items) {
      list.add(new RowModel(s));
    }

    setListAdapter(new RatingAdapter(list));
  }

  private RowModel getModel(int position) {
    return(((RatingAdapter)getListAdapter()).getItem(position));
  }

  class RatingAdapter extends ArrayAdapter<RowModel> {
    RatingAdapter(ArrayList<RowModel> list) {
      super(RateListDemo.this, R.layout.row, R.id.label, list);
    }

    public View getView(int position, View convertView,
                       ViewGroup parent) {
      View row=super.getView(position, convertView, parent);
      ViewHolder holder=(ViewHolder)row.getTag();

      if (holder==null) {
        holder=new ViewHolder(row);
        row.setTag(holder);

        RatingBar.OnRatingBarChangeListener l=
                    new RatingBar.OnRatingBarChangeListener() {
          public void onRatingChanged(RatingBarratingBar,
                                       float rating,
                                       booleanfromTouch)  {
            Integer myPosition=(Integer)ratingBar.getTag();
            RowModel model=getModel(myPosition);

            model.rating=rating;

            LinearLayout parent=(LinearLayout)ratingBar.getParent();
            TextView label=(TextView)parent.findViewById(R.id.label);

            label.setText(model.toString());
          }
        };

					  

holder.rate.setOnRatingBarChangeListener(l);
      }

      RowModel model=getModel(position);

      holder.rate.setTag(new Integer(position));
      holder.rate.setRating(model.rating);

      return(row);
    }
  }

  class RowModel {
    String label;
    float rating=2.0f;

    RowModel(String label) {
      this.label=label;
    }

    public String toString() {
      if (rating>=3.0) {
        return(label.toUpperCase());
      }

      return(label);
    }
  }
}

					  

The following explains what is different in this activity and getView() implementation from before:

  • We are still using String[] items as the list of nonsense words, but instead of pouring that String array straight into an ArrayAdapter, we turn it into a list of RowModel objects. RowModel is the mutable model: it holds the nonsense word plus the current checked state. In a real system, these might be objects populated from a database, and the properties would have more business meaning.

  • We updated utility methods such as onListItemClick() to reflect the change from a pure-String model to use a RowModel.

  • The ArrayAdapter subclass (RatingAdapter), in getView(), lets ArrayAdapter inflate and recycle the row, and then checks to see if we have a ViewHolder in the row's tag. If not, we create a new ViewHolder and associate it with the row. For the row's RatingBar, we add an anonymous onRatingChanged() listener that looks at the row's tag (getTag()) and converts that into an Integer, representing the position within the ArrayAdapter that this row is displaying. Using that, the rating bar can get the actual RowModel for the row and update the model based on the new state of the rating bar. It also updates the text adjacent to the RatingBar when checked, to match the rating bar state.

  • We always make sure that the RatingBar has the proper contents and has a tag (via setTag()) pointing to the position in the adapter the row is displaying.

The row layout is very simple, just a RatingBar and a TextView inside a LinearLayout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"
>
  <RatingBar
    android:id="@+id/rate"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:numStars="3"
    android:stepSize="1"
    android:rating="2" />
  <TextView
    android:id="@+id/label"
    android:padding="2dip"
    android:textSize="18sp"
    android:layout_gravity="left|center_vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>
</LinearLayout>

The ViewHolder is similarly simple, just extracting the RatingBar out of the row View for caching purposes:

package com.commonsware.android.fancylists.six;

import android.view.View;
import android.widget.RatingBar;

class ViewHolder {
  RatingBar rate=null;

  ViewHolder(View base) {
    this.rate=(RatingBar)base.findViewById(R.id.rate);
  }
}

And the result is what you would expect, visually, as shown in Figure 1.

Figure 1. The RateListDemo application, as initially launched

Figure 2 shows a toggled rating bar turning its word into all caps.

Figure 2. The same application, showing a top-rated word
 
Others
 
- Android : Getting Fancy with Lists - Better. Stronger. Faster.
- Windows Phone 7 : Designing the Game Framework (part 3) - The GameHost Class
- Windows Phone 7 : Designing the Game Framework (part 2) - The TextObject Class
- Windows Phone 7 : Designing the Game Framework (part 1) - The GameObjectBase Class, The SpriteObject Class
- Windows Phone 7 : Getting Started with XNA - Other Graphics Options
- iPad : Your Calendar - Adding New Calendar Appointments, Events (part 2)
- iPad : Your Calendar - Adding New Calendar Appointments, Events (part 1)
- Java ME on Symbian OS : Handling Diversity - Using Adaptive Code and Flexible Design to Handle Diversity
- Java ME on Symbian OS : Handling Diversity - Detecting Diversity using Properties
- BlackBerry Bold 9700 and 9650 Series : Email Set Up - Sync Google Contacts Using Email Setup
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
programming4us programming4us
 
Popular tags
 
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8 BlackBerry Android Ipad Iphone iOS