ListView显示不同行以及数据重用

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显示不同行以及数据重用

时间: 2024-08-26 01:51:44

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

XE7 &amp; FMX 那些年我们一起上过的控件:ListView 之 (2) 加载数据时如何显示进度条

本文介绍一下ListView下如何加载数据.及使用进度条反馈当前进度给用户. 注意: 原创作品,请尊重作者劳动成果,转载请注明出处!!!原文永久固定地址:http://www.cnblogs.com/weii/p/4190694.html 我们先来看看效果图: 进度条需要这样用的,以下为本文参考代码: procedure TForm1.Button2Click(Sender: TObject); begin TThread.CreateAnonymousThread( procedure() v

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

本文介绍一下ListView下如何加载数据.及使用进度条反馈当前进度给用户. 注意: 原创作品,请尊重作者劳动成果,转载请注明出处!!!原文永久固定地址:http://www.cnblogs.com/weii/p/4190719.html 我们先来看看效果图: FMX异常强大,我们可以发挥想像,自定义进度样式,以下为本文参考代码: procedure TForm1.Button3Click(Sender: TObject); var pe: TPie; //扇形作进度 rc: TRoundRec

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

在上一篇文章中,我们已经实现在listview显示数据库内容的.但是我们listview中,排版不是很好看,所以这篇文章呢,我们来对listveiw进行美化.哈哈,说白了,就是对listview添加一个布局文件,然后把布局文件转化为view对象显示出来. 这里代码我们不贴全部,就贴上新增加的布局文件和有做修改的方法. 先看图片吧. 然后看listview的布局文件,其实从这个例子,大家可以知道,listview的项目可以显示很复杂的东西,只要是view对象都是可以显示的额,只要你的布局文件写好了

ListView显示不同样式的item

先look图 我们再使用listview时,listview的item大多时候都是一种样式,在很多app中也很常见,但有时候根据需求,可能数据的数量不一样,同个类型的数据显示的位置不同,亦或者有的item需要图片,有的不需要,但是这些又必须在同一个listview中显示,这时我们就需要在listview中显示多种样式的item,首先我们需要考虑的是如何将不同数量的数据装载到ArrayList<~>中呢,先看看下面的listViewItem,. 1 package com.example.ker

四大适配器控制ListView显示

ArraryAdapter.SimpleCursorAdapter.SimpleAdapter.MyAdapter四大适配器的实现在android开发中ListView是比较常用的组件,它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示.抽空把对ListView的使用做了整理,并写了个小例子,如下图. 列表的显示需要三个元素:1.ListVeiw 用来展示列表的View.2.适配器 用来把数据映射到ListView上的中介.3.数据    具体的将被映射的字符串,图片,或者基本组件.根

28.悬浮窗、listview显示不同条目

APP管理界面 布局 用户程序那一行一直在最上面,拖动到系统程序时又显示系统多少个 <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:id="@+id/ll_loading" android:layout_width="fill_parent"

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

本篇介绍Listview的显示,对于listview有许多的适配器,如ArrayAdapter,BaseAdapter,SimpleAdapter等等.本篇先热身一下,介绍最简单的SimpleAdapter适配器. 对于安卓界面的显示. 首先在主界面布局文件main.xml加入如下代码: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http

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

ListView介绍及添加数据时的闪烁问题 1.     ListView类 1.1 ListView常用的基本属性: (1)FullRowSelect:设置是否行选择模式.(默认为false) 提示:只有在Details视图该属性才有意义. (2) GridLines:设置行和列之间是否显示网格线.(默认为false)提示:只有在Details视图该属性才有意义. (3)AllowColumnReorder:设置是否可拖动列标头来对改变列的顺序.(默认为false)提示:只有在Details视

Android开发中ScollView嵌套ListView显示不全问题解决

大多数时候,我们用ListView来加载数据的页面不需要在ListView的外面再套上一个ScollView,因为ListView本身可以滚动显示数据.有时我们页面中除要用ListView显示列表数据之外还要显示其它数据,这时候就需要在整个页面最个层套上一个Scollview,否则显示就可能出现问题(比如在ListView上面已经有很多其它数据,显示在手机上直接导致ListView看不见了,这时就要在整个屏幕布局加ScollView实现滑动界面),用过ScollView嵌套ListView的朋友