Êíèãà: Beginning Android
…And Checking It Twice
…And Checking It Twice
The rating list in the previous section works, but implementing it is very tedious. Worse, much of that tedium would not be reusable except in very limited circumstances. We can do better.
What we’d really like is to be able to create a layout like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<com.commonsware.android.fancylists.seven.RateListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
where, in our code, almost all of the logic that might have referred to a ListView
before “just works” with the RateListView
we put in the layout:
public class RateListViewDemo extends ListActivity {
TextView selection;
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);
setContentView(R.layout.main);
setListAdapter(new ArrayAdapterString(this,
android.R.layout.simple_list_item_1, items));
selection = (TextView)findViewById(R.id.selection);
}
public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
}
Things get a wee bit challenging when you realize that in everything up to this point in this chapter, 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 RateListView
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 (from the FancyLists/RateListView
sample project at http://apress.com/):
public class AdapterWrapper implements ListAdapter {
ListAdapter delegate = null;
public AdapterWrapper(ListAdapter delegate) {
this.delegate = delegate;
}
public int getCount() {
return(delegate.getCount());
}
public Object getItem(int position) {
return(delegate.getItem(position));
}
public long getItemId(int position) {
return(delegate.getItemId(position));
}
public View getView(int position, View convertView,
ViewGroup parent) {
return(delegate.getView(position, convertView, parent));
}
public void registerDataSetObserver(DataSetObserver observer) {
delegate.registerDataSetObserver(observer);
}
public boolean hasStableIds() {
return(delegate.hasStableIds());
}
public boolean isEmpty() {
return(delegate.isEmpty());
}
public int getViewTypeCount() {
return(delegate.getViewTypeCount());
}
public int getItemViewType(int position) {
return(delegate.getItemViewType(position));
}
public void unregisterDataSetObserver(DataSetObserver observer) {
delegate.unregisterDataSetObserver(observer);
}
public boolean areAllItemsEnabled() {
return(delegate.areAllItemsEnabled());
}
public boolean isEnabled(int position) {
return(delegate.isEnabled(position));
}
}
We can then subclass AdapterWrapper
to create RateableWrapper
, overriding the default getView()
but otherwise allowing the delegated ListAdapter
to do the “real work:”
public class RateableWrapper extends AdapterWrapper {
Context ctxt = null;
float[] rates = null;
public RateableWrapper(Context ctxt, ListAdapter delegate) {
super(delegate);
this.ctxt = ctxt;
this.rates = new float[delegate.getCount()];
for(int i=0; idelegate.getCount(); i++) {
this.rates[i]=2.0f;
}
}
public View getView(int position, View convertView,
ViewGroup parent) {
ViewWrapper wrap = null;
View row = convertView;
if (convertView==null) {
LinearLayout layout = new LinearLayout(ctxt);
RatingBar rate = new RatingBar(ctxt);
rate.setNumStars(3);
rate.setStepSize(1.0f);
View guts = delegate.getView(position, null, parent);
layout.setOrientation(LinearLayout.HORIZONTAL);
rate.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.FILL_PARENT));
guts.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));
RatingBar.OnRatingBarChangeListener l =
new RatingBar.OnRatingBarChangeListener() {
public void onRatingChanged(RatingBar ratingBar,
float rating, boolean fromTouch) {
rates[(Integer)ratingBar.getTag()] = rating;
}
};
rate.setOnRatingBarChangeListener(l);
layout.addView(rate);
layout.addView(guts);
wrap = new ViewWrapper(layout);
wrap.setGuts(guts);
layout.setTag(wrap);
rate.setTag(new Integer(position));
rate.setRating(rates[position]);
row = layout;
} else {
wrap = (ViewWrapper)convertView.getTag();
wrap.setGuts(delegate.getView(position, wrap.getGuts(),
parent));
wrap.getRatingBar().setTag(new Integer(position));
wrap.getRatingBar().setRating(rates[position]);
}
return(row);
}
}
The idea is that RateableWrapper
is where most of our rate-list logic resides. It puts the rating bars on the rows and it tracks the rating bars’ states as they are adjusted by the user. For the states, it has a float[]
sized to fit the number of rows that the delegate says are in the list.
RateableWrapper
’s implementation of getView()
is reminiscent of the one from RateListDemo
, except that rather than use LayoutInflater
, we need to manually construct a LinearLayout
to hold our RatingBar
and the “guts” (that is, whatever view the delegate created that we are decorating with the checkbox). LayoutInflater
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 what we saw in RateListDemo
:
class ViewWrapper {
ViewGroup base;
View guts = null;
RatingBar rate = null;
ViewWrapper(ViewGroup base) {
this.base = base;
}
RatingBar getRatingBar() {
if (rate==null) {
rate = (RatingBar)base.getChildAt(0);
}
return(rate);
}
void setRatingBar(RatingBar rate) {
this.rate = rate;
}
View getGuts() {
if (guts==null) {
guts = base.getChildAt(1);
}
return(guts);
}
void setGuts(View guts) {
this.guts = guts;
}
}
With all that in place, RateListView
is comparatively simple:
public class RateListView extends ListView {
public RateListView(Context context) {
super(context);
}
public RateListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RateListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public void setAdapter(ListAdapter adapter) {
super.setAdapter(new RateableWrapper(getContext(), adapter));
}
}
We simply subclass ListView
and override setAdapter()
so we can wrap the supplied ListAdapter
in our own RateableWrapper
.
Visually, the results are similar to the RateListDemo
, albeit without top-rated words appearing in all caps (see Figure 9-5).
Figure 9-5. The RateListViewDemo sample application
The difference is in reusability. We could package RateListView
in its own JAR and plop it into any Android project where we need it. So while RateListView
is somewhat complicated to write, we have to write it only once, and the rest of the application code is blissfully simple.
Of course, this RateListView
could use some more features, such as programmatically changing states (updating both the float[]
and the actual RatingBar
itself), allowing other application logic to be invoked when a RatingBar
state is toggled (via some sort of callback), etc.
- CHAPTER 9 Getting Fancy with Lists
- Checking for the Availability of the Loopback Interface
- Adding Some Error Checking
- 3.3.4 Spellchecking on the Fly with Flyspell
- Checking Hardware Compatibility
- 9.2.2. Checking File System Integrity
- Checking the ARP Tables
- Installing and checking for a new drive
- Checking the status, speed, and activity for network connections
- Checking IP address assignment
- Checking the properties of documents in the printer
- Force IRQL Checking (Îáÿç. ïðîâåðêà IRQL)