ListView中的分类getItemViewType和getViewTypeCount的使用详解

最近忙着学习IOS和公司安卓的培训一直没空弄博客,越到后面空闲时间越少了,上周给公司同事讲解ListView分类,自己都懵了,以前一直用的取巧的办法:在一个布局中定义item,然后在getView中动态控制item元素的显示和隐藏,知道有getItemViewType和getViewType方法,但都没静下心来研究,都是网上照葫芦画瓢,自己研究了发现其实网上的做法也不敢苟同,下面就详细的说下Android中分类的做法。

先看两张效果图:

     

第一张                               第二张

大家应该都知道ListView中我们为了提高效率经常会用到ViewHolder来缓存视图,但是在ListView分类中这种做法貌似就失灵了,我反正没想到方法,知道的朋友们不妨跟帖告知,感激不尽。先来看看我做的demo中Adapter类的实现。

ItemAdapter类:

/**
 * Copyright (c) www.longdw.com
 */
package com.ldw.listview;

import java.util.HashMap;
import java.util.List;

import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import com.ldw.listview.model.Item;
import com.ldw.listview.model.ItemView;

public class ItemAdapter extends BaseAdapter {

	private static final int DEFAULT_MAX_VIEW_TYPE_COUNT = 10;

	private static class TypeInfo {
		int count;
		int type;
	}

	private List<Item> mItems;
	private HashMap<Class<? extends Item>, TypeInfo> mTypes;
	private Context mContext;
	private int mMaxViewTypeCount;

	public ItemAdapter(Context context, List<Item> items) {
		this(context, items, DEFAULT_MAX_VIEW_TYPE_COUNT);
	}

	public ItemAdapter(Context context, List<Item> items, int maxViewTypeCount) {
		mContext = context;
		mItems = items;
		mTypes = new HashMap<Class<? extends Item>, TypeInfo>();
		mMaxViewTypeCount = Integer.MAX_VALUE;

		for (Item item : mItems) {
			addItem(item);
		}

		mMaxViewTypeCount = Math.max(1,
				Math.max(mTypes.size(), maxViewTypeCount));
	}

	private void addItem(Item item) {
		final Class<? extends Item> klass = item.getClass();
		TypeInfo info = mTypes.get(klass);

		if (info == null) {
			final int type = mTypes.size();
			if (type >= mMaxViewTypeCount) {
				throw new RuntimeException("This ItemAdapter may handle only "
						+ mMaxViewTypeCount + " different view types.");
			}
			final TypeInfo newInfo = new TypeInfo();
			newInfo.count = 1;
			newInfo.type = type;
			mTypes.put(klass, newInfo);
		} else {
			//记录某种类型的view有几个
			info.count++;
		}
	}

	@Override
	public int getCount() {
		return mItems.size();
	}

	@Override
	public Object getItem(int position) {
		return mItems.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public int getItemViewType(int position) {
//		System.out.println("===>>>"+mTypes.get(getItem(position).getClass()).type);
//		System.out.println("getItemViewType=============");
		return mTypes.get(getItem(position).getClass()).type;
	}

	@Override
	public boolean isEnabled(int position) {
		return ((Item) getItem(position)).enabled;
	}

	@Override
    public int getViewTypeCount() {
        return mMaxViewTypeCount;
    }

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
//		System.out.println("getView===========");
		final Item item = (Item) getItem(position);
        ItemView cell = (ItemView) convertView;

        if (cell == null) {
            cell = item.newView(mContext, null);
            cell.prepareItemView();
            System.out.println("======================"+cell);
        }

        System.out.println("----------------------"+cell);

        cell.setObject(item);

        return (View) cell;
	}

}

在上面的类中,我们先不要管具体的实现,我们先看看打印结果:

这里我只过滤出在getView中判断语句内部打印情况,大家可以看到一共执行了11次,也就是创建了11个item,而根据我们以往的经验,应该是9行才对,因为我们看第一张图片一屏只显示了9个item,那多出来的两个怎么解释呢。这里我先说下,我们在使用ListView分类的时候会用到两个方法getItemViewType和getViewTypeCount,getViewTypeCount这个方法告诉ListView我共有多少种item,getItemViewType方法告诉ListView每行该显示哪种item,并且该方法中返回的type类型必须为整数且不能大于getViewTypeCount返回的数。

我们有开发经验的朋友应该都知道ListView的缓存机制,我简单的介绍下,ListView显示的view从我们重写的getView方法创建,并且一共创建的个数是一屏显示的个数,比方说我们手机上一屏能显示9个item,那么ListView刚开始显示的时候就创建了9个ListView的item,而当我们向上滚动ListView的时候第一个item不可见,下面的第10个item并没有重新创建而实际上复用的是第一个item。这样设计的目的就是为我们节省了很大的内存开销。

回到上面的问题,为什么会多出两个item呢,这里我们需要明白ListView中如果我们重写了那两个get方法,那么ListView给我们初始创建的item的个数是一屏显示的个数+item的种类数(也就是getViewTypeCount返回的个数),多出来的这两个item会先放到缓存池中为我们接下来的复用做准备的。当我们滚动到让下面第10个item(不要被列表item上的文字混淆了,从上向下数第10个item)显示出来的时候,打印结果如下:

我们看到内存地址6557b68,其实用的就是上面多创建出来的item。我们可以这么来理解:ListView为我们创建了11个item,这11个item中有9个是显示在屏幕上的,另外两个item放入了一个缓存池,当我们滑动到让第10个item显示出来之前,首先会执行getItemViewType这个方法判断该行item的类型,然后再从缓存池中查看是否有该类型item的缓存给我们用,有的话就直接拿过来,没有就创建。为了验证我们所说的,我们再将ListView滑动到让第11个item(也就是Label9)显示出来,看下面的打印:

我们看到显示第11个item的时候是重新创建了一个item,因为此时缓存池中已经没有该类型的item缓存给我们来复用了。

再结合例子来讲解如何实现这种分类

工程结构如下:

先定义个基类Item,该类是所有ListView的item的基类

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.model;

import com.ldw.listview.view.ItemView;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;

public abstract class Item {

	/**
     * Set to true when this item is enabled
     */
    public boolean enabled;

    /**
     * Create a new item.
     */
    public Item() {
        // By default, an item is enabled
        enabled = true;
    }

    public abstract ItemView newView(Context context, ViewGroup parent);

	protected static ItemView createCellFromXml(Context context, int layoutID, ViewGroup parent) {
        return (ItemView) LayoutInflater.from(context).inflate(layoutID, parent, false);
    }

}

ItemView接口定义了两个方法:

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.view;

import com.ldw.listview.model.Item;

public interface ItemView {

	void prepareItemView();

	void setObject(Item item);

}

TextItem类实现了Item类并定义了自己的成员变量text

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.model;

import com.ldw.listview.view.ItemView;

import android.content.Context;
import android.view.ViewGroup;

public class TextItem extends Item {

    public String text;

    public TextItem() {
    }

    public TextItem(String text) {
        this.text = text;
    }

	@Override
	public ItemView newView(Context context, ViewGroup parent) {
		return null;
	}

}

SeparatorItem和ContentItem类继承自TextItem并重写了newView方法返回他们自己定义两个layout

SeparatorItem:

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.model;

import com.ldw.listview.R;
import com.ldw.listview.R.layout;
import com.ldw.listview.view.ItemView;

import android.content.Context;
import android.view.ViewGroup;

public class SeparatorItem extends TextItem {

    public SeparatorItem() {
        this(null);
    }

    public SeparatorItem(String text) {
        super(text);
        enabled = false;
    }

    @Override
    public ItemView newView(Context context, ViewGroup parent) {
        return createCellFromXml(context, R.layout.separator_item_view, parent);
    }

}

ContentItem:

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.model;

import android.content.Context;
import android.view.ViewGroup;

import com.ldw.listview.R;
import com.ldw.listview.view.ItemView;

public class ContentItem extends TextItem {

	public int drawableId;
	public String title;

	public ContentItem(int id, String title, String content) {
		this.drawableId = id;
		this.title = title;
		text = content;
	}

	@Override
    public ItemView newView(Context context, ViewGroup parent) {
        return createCellFromXml(context, R.layout.content_item_view, parent);
    }

}

再看看自定义的两个view,SeparatorItemView和ContentItemView

SeparatorItemView:

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.view;

import com.ldw.listview.model.Item;
import com.ldw.listview.model.TextItem;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;

public class SeparatorItemView extends TextView implements ItemView {

	public SeparatorItemView(Context context) {
        this(context, null);
    }

    public SeparatorItemView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SeparatorItemView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

	@Override
	public void prepareItemView() {
	}

	@Override
	public void setObject(Item object) {
		TextItem item = (TextItem) object;
		setText(item.text);
	}

}

ContentItemView:

/**
 * Copyright (c) www.longdw.com
*/
package com.ldw.listview.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.ldw.listview.R;
import com.ldw.listview.model.ContentItem;
import com.ldw.listview.model.Item;

public class ContentItemView extends RelativeLayout implements ItemView {

	private TextView titleTv;
	private TextView contentTv;
	private ImageView iv;

	public ContentItemView(Context context) {
        this(context, null);
    }

    public ContentItemView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ContentItemView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

	@Override
	public void prepareItemView() {
		titleTv = (TextView) findViewById(R.id.content_title_tv);
		contentTv = (TextView) findViewById(R.id.content_tv);
		iv = (ImageView) findViewById(R.id.content_iv);
	}

	@Override
	public void setObject(Item object) {
		ContentItem item = (ContentItem) object;

		titleTv.setText(item.title);
		contentTv.setText(item.text);
		iv.setImageResource(item.drawableId);
	}

}

MainActivity:

package com.ldw.listview;

import java.util.ArrayList;
import java.util.List;

import com.ldw.listview.model.ContentItem;
import com.ldw.listview.model.Item;
import com.ldw.listview.model.SeparatorItem;

import android.app.Activity;
import android.app.ActionBar;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.os.Build;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		List<Item> list = new ArrayList<Item>();

//		list.add(new SeparatorItem("Label1"));
		list.add(new ContentItem(R.drawable.class1, "第一个item", "第一个item的内容"));

//		list.add(new SeparatorItem("Label2"));
		list.add(new ContentItem(R.drawable.class2, "第二个item", "第二个item的内容"));

		list.add(new SeparatorItem("Label3"));
		list.add(new ContentItem(R.drawable.class3, "第三个item", "第三个item的内容"));

		list.add(new SeparatorItem("Label4"));
		list.add(new ContentItem(R.drawable.class3, "第四个item", "第四个item的内容"));

//		list.add(new SeparatorItem("Label5"));
		list.add(new ContentItem(R.drawable.class3, "第五个item", "第五个item的内容"));

//		list.add(new SeparatorItem("Label6"));
		list.add(new ContentItem(R.drawable.class3, "第六个item", "第六个item的内容"));

//		list.add(new SeparatorItem("Label7"));
		list.add(new ContentItem(R.drawable.class3, "第七个item", "第七个item的内容"));

		list.add(new SeparatorItem("Label8"));
		list.add(new SeparatorItem("Label9"));
		list.add(new ContentItem(R.drawable.class3, "第八个item", "第八个item的内容"));

//		list.add(new SeparatorItem("Label9"));
		list.add(new ContentItem(R.drawable.class3, "第九个item", "第九个item的内容"));

//		list.add(new SeparatorItem("Label10"));
		list.add(new ContentItem(R.drawable.class3, "第十个item", "第十个item的内容"));

		ItemAdapter adapter = new ItemAdapter(this, list);

		ListView listview = (ListView) findViewById(R.id.listview);
		listview.setAdapter(adapter);

	}

}

separator_item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<com.ldw.listview.view.SeparatorItemView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/separator_tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="8.0dip"
    android:textColor="#00ff00" />

content_item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<com.ldw.listview.view.ContentItemView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dip">

    <ImageView android:id="@+id/content_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"/>

    <TextView android:id="@+id/content_title_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/content_iv"
        android:layout_marginLeft="10dip"/>

    <TextView android:id="@+id/content_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/content_iv"
        android:layout_below="@id/content_title_tv"
        android:layout_marginTop="10.0dip"
        android:layout_marginLeft="10dip"/>

</com.ldw.listview.view.ContentItemView>

 

这里是源码,有兴趣的可以下载看看。

 

《ListView中的分类getItemViewType和getViewTypeCount的使用详解》上有10条评论

  1. 你的item的布局和label的布局是不同的,比如item的布局是relativelayout,而label是线性布局,只要判断一下,instanceof relativelayout就可以避免复用带来的问题。

  2. 学习了,貌似这种方法对同类型的视图(itemview)是可以复用的。子视图作为成员变量跟itemview绑定在一起了,也达到了ViewHolder缓存视图的目的。

评论已关闭。