Handling ListViews with Multiple Row Types

When you start writing Android Apps it isn’t long before you need to use ListViews. ListViews are easy to get started with, but it’s also very easy to write inefficient lists that wreak havoc on scrolling performance. There are lot of things you can do to improve scrolling performance. You should always use convertViews to reduce creating new views. You should use the ViewHolder pattern outlined in theAndroid API example to reduce lookup time in layouts. If you have images in your lists the same principles outlined in my quora answer for iOS UITableViewsapplies to Android ListViews.

But what happens when not every item in your list view looks the same? This is often the case when you want to build a ListView that’s presenting some type of feed where some rows have images, some have videos, and some are just text and they come in no particular order. You can and should still use all the methods mentioned above. But item view reuse suddenly becomes much more complicated. You definitely don’t want to go back to not reusing the convertView, just to handle multiple row types. This is where the view type methods in the Adapter come in.

Before moving on let’s do a quick review of how ListViews and their adapters interact. If you’ve used ListViews and Adapters before before you can probably skip this paragraph. When you set an adapter on a ListView by using the setAdapater(Adapter) method, you are telling the ListView to use the adapter to tell it what to show in the list. The two key methods for the Adapter are getCountand getView. The list view calls the adapter’s getCount to know how many items exist in the list. Then only for the rows that are visible on the screen it calls getView which returns the view to show at that item.

The two additional methods android Adapters provide for managing different row types are: getItemViewType(int position) and getViewTypeCount(). The list view uses these methods create different pools of views to reuse for different types of rows.

Pools? ConvertViews? Huh?

Let’s step back for a minute and take look at how the whole convertView business works. Internally ListViews try to do a lot of work to scroll smoothly. One of the things they do is to reuse view instances when scrolling. Everytime a ListView gets a view through the getView method in your adapter, the ListView keeps that view in a pool of views that can be reused. The convertView parameter in getView is a view from this pool. If you get a non-null convertView in the getView method of your adapter, that means the ListView is telling you: “Sup dawg, I heard you want to show something here. Instead of wasting time by creating a new view, just take the one I am giving you and change its contents.”

So what about those type methods?

getViewTypeCount() tells the ListView how many of these view pools to keep. And getItemViewType(int position) tells the ListView which of these pools the view at this position belongs to. That way the ListView can give you just the right type of view as the convertView for reuse in later getView calls.

A good way to think about these pools is that ListView keeps an array of pools. The getViewTypeCount is the size of this pool array, and getItemViewType gives the index of the pool to use. What this means is that it’s important to return 0 indexed numbers in getItemViewType. It’s not a tag, it’s an index into an array.

It may help to think of a negative case to understand how this works. If you return the wrong index in getItemViewType for a particular row, then the ListView will obligingly pass you the wrong view in a convertView when you scroll. Your reuse code will then look for something that doesn’t exist and KABLAMO! Your app will crash. So it’s important to be careful about passing the right view type.

That’s a lot of words yo. Gimme some code. Show me how it really works.

Ok. Let’s use an example to explain. Let’s say we are making an app which helps you learn about animals. Let’s call this app… aah… Animals. On its home screen the app shows you an eclectic collection of Animals. Ideally you want to show an image and the name, but sometimes you don’t have an image for the animal. In that case you want to show a completely different layout in your row. Instead of the image you want to give a short description of the animal.

Our core model for this class is a POJO called Animal which has three fields: imageId, name, and description. The adapter is given a list of these animals to display. Since we have two types of rows our getViewTypeCount method for the adapter simply returns 2.

public int getViewTypeCount() {
return 2;
} The getItemViewType method returns the right index based on the data: public int getItemViewType(int position) { //we have an image so we are using an ImageRow
if (animals.get(position).getImageId() != null) return 0; //we don't have an image so we are using a Description Row
else return 1;
}

Our getView method uses the same logic in getItemViewType and branch to a different way to fill up the view:

public View getView(int position, View convertView, ViewGroup parent) {
//first get the animal from our data model
Animal animal = animals.get(position); //if we have an image so we setup an the view for an image row
if (animal.getImageId() != null) {
ImageRowViewHolder holder;
View view; //don't have a convert view so we're going to have to create a new one
if (convertView == null) {
ViewGroup viewGroup =(ViewGroup)LayoutInflater.from(AnimalHome.this)
.inflate(R.layout.image_row, null); //using the ViewHolder pattern to reduce lookups
holder = newImageRowViewHolder((ImageView)viewGroup.findViewById(R.id.image),
(TextView)viewGroup.findViewById(R.id.title));
viewGroup.setTag(holder); view = viewGroup;
}
//we have a convertView so we're just going to use it's content
else {
//get the holder so we can set the image
holder = (ImageRowViewHolder)convertView.getTag(); view = convertView;
} //actually set the contents based on our animal
holder.imageView.setImageResource(animal.getImageId());
holder.titleView.setText(animal.getName()); return view;
}
//basically the same as above but for a layout with title and description
else {
DescriptionRowViewHolder holder;
View view;
if (convertView == null) {
ViewGroup viewGroup =(ViewGroup)LayoutInflater.from(AnimalHome.this)
.inflate(R.layout.text_row, null);
holder = newDescriptionRowViewHolder((TextView)viewGroup.findViewById(R.id.title),
(TextView)viewGroup.findViewById(R.id.description));
viewGroup.setTag(holder);
view = viewGroup;
} else {
view = convertView;
holder = (DescriptionRowViewHolder)convertView.getTag();
} holder.descriptionView.setText(animal.getDescription());
holder.titleView.setText(animal.getName()); return view;
}
}

That’s all there is to it. You now know how to use the view type methods to handle different row layouts in your lists. Class is over.

Umm wait… That’s some ugly lookin’ code. Can’t we do better?

Glad you asked! There are three things particularly ugly about this code. First we are using magic numbers for the values returned by our getItemViewType and getViewTypeCount methods. Second, we are repeating the same branching pattern in two different methods, getView and getItemViewType. Third, that getView method is long. All these things together make this code brittle and hard to maintain over the long term.

So how do we deal with all these problems? We introduce the concept of a Row object. You can think of a Row as a controller for each item in your list. It’s an interface that is implemented by the two different types of Rows in our example: ImageRow and DescriptionRow. When we construct our adapter we take the list of animals it’s given and create the right Row object for each animal.

AnimalAdapter(List<Animal> animals) {
rows = new ArrayList<Row>();//member variable for (Animal animal : animals) {
//if it has an image, use an ImageRow
if (animal.getImageId() != null) {
rows.add(new ImageRow(LayoutInflater.from(AnimalHome.this), animal));
} else {//otherwise use a DescriptionRow
rows.add(new DescriptionRow(LayoutInflater.from(AnimalHome.this), animal));
}
}
}

So what do these Row objects actually do? Well let’s take a look at the interface definition:

public interface Row {
public View getView(View convertView);
public int getViewType();
}

This probably looks very familiar. That’s because these methods look almost exactly like getView and getItemViewType methods from the Adapter interface we talked about earlier. In each of these methods of the adapter we hand off the work to relevant method in the Row object itself. So when you call getView it gets the Row object for that position and asks it to return the correct view. For an ImageRow it returns a row where you have a title and an image, and for a DescriptionRow it returns a row that has a title and a description. Here’s what the getView and the getItemViewType methods on the adapter look like:

public int getItemViewType(int position) {
return rows.get(position).getViewType();
} public View getView(int position, View convertView, ViewGroup parent) {
return rows.get(position).getView(convertView);
}
So what do view type methods actually return? We could just return 0 for ImageRows and 1 for DescriptionRows and when the the adapter’s getViewTypeCount method is called, return 2 and call it a day. But we wanted to avoid using magic numbers in our code so instead we use an Enum. public enum RowType {
IMAGE_ROW,
DESCRIPTION_ROW
}
 

So getViewType for ImageRow returns RowType.IMAGE_ROW.ordinal(), and for DescriptionRow it returns RowType.DESCRIPTION_ROW.ordinal(). getViewTypeCount on our adapter simply returns RowType.values().length.

All in all our adapter looks like this:

private class AnimalAdapter extends BaseAdapter {
final List<Row> rows; AnimalAdapter(List<Animal> animals) {
rows = new ArrayList<Row>();//member variable for (Animal animal : animals) {
//if it has an image, use an ImageRow
if (animal.getImageId() != null) {
rows.add(new ImageRow(LayoutInflater.from(AnimalHome.this), animal));
} else {//otherwise use a DescriptionRow
rows.add(new DescriptionRow(LayoutInflater.from(AnimalHome.this), animal));
}
}
} @Override
public int getViewTypeCount() {
return RowType.values().length;
} @Override
public int getItemViewType(int position) {
return rows.get(position).getViewType();
} public int getCount() {
return rows.size();
} public Object getItem(int position) {
return position;
} public long getItemId(int position) {
return position;
} public View getView(int position, View convertView, ViewGroup parent) {
return rows.get(position).getView(convertView);
}
}

As you can see our Adapter code is super simple! It’s because it passed all the hard work to the Row objects which have clear ownership of what their views look like and how they behave. Obviously you’re itching to see the code for the whole app and how it all works together. So you can download the whole Animals app here.

If you want to really understand how this pattern works do the following exercise with the downloaded code. Add a third type of row: ImageDescriptionRow. If you have all three pieces of data, image, title and description for an animal, then show the title and image just like the ImageRow but also show a description below spanning the width of the row.

摘抄自:http://logc.at/2011/10/10/handling-listviews-with-multiple-row-types/

ListView显示不同行以及数据重用的更多相关文章

  1. Android整理:SQlite数据库的使用以及通过listView显示数据

    前言:上个月与同学一起做了一个简单的Android应用,这段时间正好没有很多事情所以趁热整理一下学习到的知识,刚开始学习Android还有很多不懂的地方,继续努力吧! 作业中需要用到数据库,当然首选A ...

  2. XE7 & FMX 那些年我们一起上过的控件:ListView 之 (3) 加载数据时如何显示自定义样式

    本文介绍一下ListView下如何加载数据.及使用进度条反馈当前进度给用户. 注意: 原创作品,请尊重作者劳动成果,转载请注明出处!!!原文永久固定地址:http://www.cnblogs.com/ ...

  3. ListView显示Sqlite的数据美化版与性能优化

    在上一篇文章中,我们已经实现在listview显示数据库内容的.但是我们listview中,排版不是很好看,所以这篇文章呢,我们来对listveiw进行美化.哈哈,说白了,就是对listview添加一 ...

  4. ListView用法及加载数据时的闪烁问题和加载数据过慢问题

    ListView介绍及添加数据时的闪烁问题 1.     ListView类 1.1 ListView常用的基本属性: (1)FullRowSelect:设置是否行选择模式.(默认为false) 提示 ...

  5. 转 wince程序 中使用Listview显示图标问题 (C#) .

    思路: 1.窗体控件:lstaqgl [Listview控件名称]  imageList1[ImageList控件] 2.  图片路径添加到—imageList1——Listview显示图片从 ima ...

  6. Android简易实战教程--第十八话《ListView显示,简单的适配器SimpleAdapter》

    本篇介绍Listview的显示,对于listview有许多的适配器,如ArrayAdapter,BaseAdapter,SimpleAdapter等等.本篇先热身一下,介绍最简单的SimpleAdap ...

  7. android listView多层嵌套listView显示不全问题

    最近在做项目,需要用到listVIew多层嵌套listVIew的需求,先发现已下两个处理办法比较好用 第一种: public class ListViewNesting extends ListVie ...

  8. Android ListView显示不同样式的item

    先look图 我们再使用listview时,listview的item大多时候都是一种样式,在很多app中也很常见,但有时候根据需求,可能数据的数量不一样,同个类型的数据显示的位置不同,亦或者有的it ...

  9. Android_(控件)使用ListView显示Android系统中联系人信息

    使用ListView显示手机中联系人的姓名和电话号码 父类布局activity_main.xml,子类布局line.xml(一个文件的单独存放) 运行截图: (避免泄露信息对部分地方进行了涂鸦O(∩_ ...

随机推荐

  1. 美国安全公司HBGary——国家授命的黑客

         入侵电脑,窃听用户,假身份上网——美国安全公司HBGary是电脑防护和间谍软件的供应商.而其客户中就有美国的国家安全机构.现在,该公司被偷的电子邮件可以让我们对其数字化战争中的业务做一个初步 ...

  2. MVC Cookie的使用

    1.创建Cookies有两种方法: Response.Cookies["userName"].Value = "patrick"; Response.Cooki ...

  3. JS的封装

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAl8AAADiCAYAAABwdKKfAAAYLGlDQ1BJQ0MgUHJvZmlsZQAAWIWVeQ

  4. SpreadJS电子表格

    SpreadJS是wijmo旗下的一款HTML5电子表格控件. 官方网址:http://wijmo.com/products/spreadjs/ 在这里简单整理了SpreadJS几个比较简单的特点,如 ...

  5. 【原】相煎何太急——input的blur事件与button的click事件

    先来一段引子,最近在写手机h5页面,主要是一些登陆注册方面的,最绕不开的就是表单元素. 我想实现的是:在输入框后边有一个删除图标,当输入东西的时候触发事件,显示删除图标,点击该图标会删除之前输入的内容 ...

  6. linux autoload service create

    ---恢复内容开始--- EXEC="php-fpm" stop(){ echo "Stoping $EXEC ..." ps aux | grep " ...

  7. js中原型的概念

  8. DIOCP之编写第一个应用程序(一)

    Server 设计功能如下: 1.支持客户端登录 2.连接数据库进行操作 3.推送信息 4.限制文件上传大小 第一步:创建一个VCL-Forms Application(创建一个标准VCL程序) 第二 ...

  9. 仿QQ的底部选项

    效果图: item_add.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout ...

  10. c# NPOI 导出EXCEL

    需要引入dll文件 也可以在NuGet里面管理(推荐) 比较方便 . using System; using System.Collections.Generic; using System.Linq ...