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. Find the largest K numbers from array (找出数组中最大的K个值)

    Recently i was doing some study on algorithms. A classic problem is to find the K largest(smallest) ...

  2. ASP.NET Misconfiguration: Debug Information

    Abstract: Debugging messages help attackers learn about the system and plan a form of attack. Explan ...

  3. C语言的文法分析

    <程序>  ->   <声明> | <程序> <函数> <声明>  ->   #include<stdio.h>|# ...

  4. jsPlumb的简单使用

    jsPlumb概述jsPlumb是一个在dom元素之间绘制连接线的javascript框架,它使用svg技术绘制连接线. 基本概念很明显,一个连线主要要解决的问题包括谁和谁连,在哪里连(连接点在哪里) ...

  5. javascript验证香港身份证

    var assert = require('assert'); describe('test', function() { it('coc', function() { var testCard01 ...

  6. Linux文件查找

    Linux下查找文件的命令有两个; locate: find : locate这个命令对其生成的数据库进行遍历(生成数据库的命令:updatedb),这一特性决定了查 找文件速度很快,但是locate ...

  7. 转载:log4j.properties log4j.xml 路径问题

    自动加载配置文件: (1)如果采用log4j输出日志,要对log4j加载配置文件的过程有所了解.log4j启动时,默认会寻找source folder下的log4j.xml配置文件,若没有,会寻找lo ...

  8. ViewPager如下效果你研究过吗?

    1:ViewPager实现欢迎页面动画效果 ViewPager实现欢迎页面动画滑动切换view效果,页面切换添加优美的动画, //主要代码实现 public void animateSecondScr ...

  9. NGUI缓动函数

    缓动函数:http://easings.net/zh-cn 研究NGUI的博客:http://dsqiu.iteye.com/category/295721

  10. mvc 项目下 webservice 程序无法运行

    错误描述: 可以出现调用HelloWorld的界面 点击调用按钮报无法找到该资源 错误分析: 把webservice当成controller了. 解决: global中 添加  routes.Igno ...