Wednesday, 6 May 2015

Lesson 9 List with custom objects and adapter ---- 3

List with custom objects and adapter

NewsEntryAdapter

Create a new class that extends ArrayAdapter<NewsEntry>. You’ll need to provide a constructor; we’ve selected the (Context, int) signature for our purposes. Additionally we want to override the getView() method to customize how we’ll build each view.
Here is the final class we built. Don’t worry, we’ll dissect this class to understand everything it’s doing.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
package com.learnandroid.listviewtutorial.adapter;
 
import java.text.DateFormat;
 
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
 
/**
* Adapts NewsEntry objects onto views for lists
*/
public final class NewsEntryAdapter extends ArrayAdapter<NewsEntry> {
 
private final int newsItemLayoutResource;
 
public NewsEntryAdapter(final Context context, final int newsItemLayoutResource) {
super(context, 0);
this.newsItemLayoutResource = newsItemLayoutResource;
}
 
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
// We need to get the best view (re-used if possible) and then
// retrieve its corresponding ViewHolder, which optimizes lookup efficiency
final View view = getWorkingView(convertView);
final ViewHolder viewHolder = getViewHolder(view);
final NewsEntry entry = getItem(position);
// Setting the title view is straightforward
viewHolder.titleView.setText(entry.getTitle());
// Setting the subTitle view requires a tiny bit of formatting
final String formattedSubTitle = String.format("By %s on %s",
entry.getAuthor(),
DateFormat.getDateInstance(DateFormat.SHORT).format(entry.getPostDate())
);
viewHolder.subTitleView.setText(formattedSubTitle);
// Setting image view is also simple
viewHolder.imageView.setImageResource(entry.getIcon());
return view;
}
 
private View getWorkingView(final View convertView) {
// The workingView is basically just the convertView re-used if possible
// or inflated new if not possible
View workingView = null;
if(null == convertView) {
final Context context = getContext();
final LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
workingView = inflater.inflate(newsItemLayoutResource, null);
} else {
workingView = convertView;
}
return workingView;
}
private ViewHolder getViewHolder(final View workingView) {
// The viewHolder allows us to avoid re-looking up view references
// Since views are recycled, these references will never change
final Object tag = workingView.getTag();
ViewHolder viewHolder = null;
if(null == tag || !(tag instanceof ViewHolder)) {
viewHolder = new ViewHolder();
viewHolder.titleView = (TextView) workingView.findViewById(R.id.news_entry_title);
viewHolder.subTitleView = (TextView) workingView.findViewById(R.id.news_entry_subtitle);
viewHolder.imageView = (ImageView) workingView.findViewById(R.id.news_entry_icon);
workingView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) tag;
}
return viewHolder;
}
/**
* ViewHolder allows us to avoid re-looking up view references
* Since views are recycled, these references will never change
*/
private static class ViewHolder {
public TextView titleView;
public TextView subTitleView;
public ImageView imageView;
}
}
The constructor we’ve specified below receives a Context and an int.
public NewsEntryAdapter(final Context context, final int newsItemLayoutResource) {
 super(context, 0);
 this.newsItemLayoutResource = newsItemLayoutResource;
}
We must specify the Context to our parent class, ArrayAdapter, and the newsItemLayoutResource tells us which layout we should use for each news item. We’ve already created one at res/layout/news_entry_list_item.xml, so the resource we’ll pass into this constructor will be R.layout.news_entry_list_item.

getView()

Here we’re overriding the getView() method of ArrayAdapter.
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
 
 // We need to get the best view (re-used if possible) and then
 // retrieve its corresponding ViewHolder, which optimizes lookup efficiency
 final View view = getWorkingView(convertView);
 final ViewHolder viewHolder = getViewHolder(view);
 final NewsEntry entry = getItem(position);
 
 // Setting the title view is straightforward
 viewHolder.titleView.setText(entry.getTitle());
 
 // Setting the subTitle view requires a tiny bit of formatting
 final String formattedSubTitle = String.format("By %s on %s",
  entry.getAuthor(),
  DateFormat.getDateInstance(DateFormat.SHORT).format(entry.getPostDate())
 );
 
 viewHolder.subTitleView.setText(formattedSubTitle);
 
 // Setting image view is also simple
 viewHolder.imageView.setImageResource(entry.getIcon());
 
 return view;
}
There’s a lot going on here, so let’s break it down. First we’ll talk about the method’s arguments (specifically convertView), the workingView, and then viewHolder.
The primary functionality of the adapter starts at getView. This is what is called each time the ListView needs something to display.
The position argument tells us which item in the array of objects we added is being rendered (which can be very useful to know.)
The convertView argument is our possibly-recycled view. If this is non-null, then this is a view that’s no longer visible and should be used to conserve view objects. If it is null, then we’ll go ahead and create a new view. The resulting view (either reused or newly created) is what we call the working view.
The parent argument is a reference to the parent view containing ours (ListView.) It’s not necessary for our purposes here.
The following lines determine our working view and retrieve a ViewHolder plus the corresponding NewsEntry object.
 // We need to get the best view (re-used if possible) and then
 // retrieve its corresponding ViewHolder, which optimizes lookup efficiency
 final View view = getWorkingView(convertView);
 final ViewHolder viewHolder = getViewHolder(view);
 final NewsEntry entry = getItem(position);
We’ll take a look at getWorkingView() and getViewHolder() soon enough. The getItem() simply returns to us the NewsEntry we’re currently trying to render for the ListView to display.
The remaining lines perform the actual decoration of the view:
 // Setting the title view is straightforward
 viewHolder.titleView.setText(entry.getTitle());
 
 // Setting the subTitle view requires a tiny bit of formatting
 final String formattedSubTitle = String.format("By %s on %s",
  entry.getAuthor(),
  DateFormat.getDateInstance(DateFormat.SHORT).format(entry.getPostDate())
 );
 
 viewHolder.subTitleView.setText(formattedSubTitle);
 
 // Setting image view is also simple
 viewHolder.imageView.setImageResource(entry.getIcon());
 
 return view;
The title is set simply to the NewsEntry title. The subtitle requires a little bit of date formatting, but isn’t altogether hard. Like the title, the image is set simply to the NewsEntryIcon.
Finally we return the view at the end of getView(), which will cause it to be recycled later on when possible.

getWorkingView()

Here we define our own method getWorkingView().
private View getWorkingView(final View convertView) {
 // The workingView is basically just the convertView re-used if possible
 // or inflated new if not possible
 View workingView = null;
 
 if(null == convertView) {
  final Context context = getContext();
  final LayoutInflater inflater = (LayoutInflater)context.getSystemService
       (Context.LAYOUT_INFLATER_SERVICE);
 
  workingView = inflater.inflate(newsItemLayoutResource, null);
 } else {
  workingView = convertView;
 }
 
 return workingView;
}
The working view is the result of whether we have a recycled view or forced to create a new one. This should be pretty straightforward.

getViewHolder()

Here we define our own method getViewHolder().
private ViewHolder getViewHolder(final View workingView) {
 // The viewHolder allows us to avoid re-looking up view references
 // Since views are recycled, these references will never change
 final Object tag = workingView.getTag();
 ViewHolder viewHolder = null;
 
 if(null == tag || !(tag instanceof ViewHolder)) {
  viewHolder = new ViewHolder();
 
  viewHolder.titleView = (TextView) workingView.findViewById(R.id.news_entry_title);
  viewHolder.subTitleView = (TextView) workingView.findViewById(R.id.news_entry_subtitle);
  viewHolder.imageView = (ImageView) workingView.findViewById(R.id.news_entry_icon);
 
  workingView.setTag(viewHolder);
 
 } else {
  viewHolder = (ViewHolder) tag;
 }
 
 return viewHolder;
}
 
/**
 * ViewHolder allows us to avoid re-looking up view references
 * Since views are recycled, these references will never change
 */
private static class ViewHolder {
 public TextView titleView;
 public TextView subTitleView;
 public ImageView imageView;
}
The ViewHolder is a custom class defined out of convenience, efficiency, and some type safety. The primary benefit of the ViewHolder relies on the fact that, when stored as a view’s tag, it is recycled along with the view itself. A view can store any object, known as its tag, for convenient referencing. In our case, we will store an object that already has direct references to the subviews within the item layout. This avoids having to re-lookup (through findViewById()) each subview each time it’s re-drawn. As the number of getView() calls increase, the number of times findViewById() is invoked remains the same at a certain point. We can also cast it to our specific types of views (TextView, ImageView) and never have to cast it again.

Layouts, Objects, Adapter Complete

Now that we’re done with our layouts, objects, and adapter, it’s time to make use of the new tool we have. On the next and final page, we’ll put it all together and conclude the tutorial.

No comments:

Post a Comment