}
}
}
The theory is that we override getView()
and return rows based on which object is being displayed, where the object is indicated by a position index into the Adapter
. However, if you look at the implementation shown in the code here, you will see a reference to a LayoutInflater
class — and that concept takes a little bit of an explanation.
In this case, “inflation” means the act of converting an XML layout specification into the actual tree of View
objects the XML represents. This is undoubtedly a tedious bit of code: take an element, create an instance of the specified View
class, walk the attributes, convert those into property setter calls, iterate over all child elements, lather, rinse, repeat.
The good news is that the fine folk on the Android team wrapped all that up into a class called LayoutInflater
that we can use ourselves. When it comes to fancy lists, for example, we will want to inflate Views
for each row shown in the list, so we can use the convenient shorthand of the XML layout to describe what the rows are supposed to look like.
In the preceding example, we inflate the R.layout.row
layout we created in the previous section. This gives us a View
object that, in reality, is our LinearLayout
with an ImageView
and a TextView
, just as R.layout.row
specifies. However, rather than having to create all those objects ourselves and wire them together, the XML and LayoutInflater
handle the “heavy lifting” for us.
And Now, Back to Our Story
So we have used LayoutInflater
to get a View
representing the row. This row is “empty” since the static layout file has no idea what actual data goes into the row. It is our job to customize and populate the row as we see fit before returning it. So, we do the following:
• Put the text label into our label widget, using the word at the supplied position.
• See if the word is longer than four characters and, if so, find our ImageView
icon widget and replace the stock resource with a different one.
Now we have a ListView
with different icons based upon context of that specific entry in the list (see Figure 9-2).
Figure 9-2. The DynamicDemo application
This was a fairly contrived example, but you can see where this technique could be used to customize rows based on any sort of criteria, such as other columns in a returned Cursor
.
Better. Stronger. Faster.
The getView()
implementation shown previously works, but it is inefficient. Every time the user scrolls, we have to create a bunch of new View objects to accommodate the newly shown rows. And since the Android framework does not cache existing View objects itself, we wind up making new row View
objects even for rows we just created a second or two ago. This is bad.
It might be bad for the immediate user experience, if the list appears to be sluggish. More likely, though, it will be bad due to battery usage — every bit of CPU that is used eats up the battery. This is compounded by the extra work the garbage collector needs to do to get rid of all those extra objects you create. So the less efficient your code, the more quickly the phone’s battery will be drained, and the less happy the user will be. And you want happy users, right?
So, let us take a look at a few tricks to make your fancy ListView
widgets more efficient.
The getView()
method receives, as one of its parameters, a View
named, by convention, convertView
. Sometimes convertView
will be null. In those cases, you have to create a new row View
from scratch (e.g., via inflation), just as we did before.
However, if convertView
is not null, then it is actually one of your previously created Views
. This will be the case primarily when the user scrolls the ListView
— as new rows appear, Android will attempt to recycle the views of the rows that scrolled off the other end of the list, to save you having to rebuild them from scratch.
Assuming that each of your rows has the same basic structure, you can use findViewById()
to get at the individual widgets that make up your row and change their contents, then return convertView
from getView()
rather than create a whole new row.
For example, here is the getView()
implementation from last time, now optimized via convertView
(from the FancyLists/Recycling
project at http://apress.com/):
public classRecyclingDemo extendsListActivity {
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
publicvoid onCreate(Bundle icicle) {
super. onCreate(icicle);
setContentView(R.layout.main);
setListAdapter( new IconicAdapter( this));
selection = (TextView) findViewById(R.id.selection);
}
publicvoid onListItemClick(ListView parent, View v,
int position, long id) {
selection. setText(items[position]);
}
classIconicAdapter extendsArrayAdapter {
Activity context;
IconicAdapter(Activity context) {
super(context, R.layout.row, items);
this.context = context;
}
publicView getView(int position, View convertView,
ViewGroup parent) {
View row = convertView;
if(row== null) {
LayoutInflater inflater = context. getLayoutInflater();
row = inflater. inflate(R.layout.row, null);
}
TextView label = (TextView)row. findViewById(R.id.label);
label. setText(items[position]);
ImageView icon = (ImageView)row. findViewById(R.id.icon);
if(items[position]. length() > 4) {
icon. setImageResource(R.drawable.delete);
} else{
icon. setImageResource(R.drawable.ok);
}
return(row);
}
}
}
Here we check to see if the convertView
is null and, if so we then inflate our row — but if it is not null, we just reuse it. The work to fill in the contents (icon image, text) is the same in either case. The advantage is that if the convertView
is not null, we avoid the potentially expensive inflation step.
Читать дальше