最近忙着学习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>
这里是源码,有兴趣的可以下载看看。






很不错的文章。
要不要这么叼
你的item的布局和label的布局是不同的,比如item的布局是relativelayout,而label是线性布局,只要判断一下,instanceof relativelayout就可以避免复用带来的问题。
如果两个item的布局都是同一种类型呢?那样耦合性太大
学习了,貌似这种方法对同类型的视图(itemview)是可以复用的。子视图作为成员变量跟itemview绑定在一起了,也达到了ViewHolder缓存视图的目的。
楼主你讲的是错的吧
如果错了还请指正啊,孰能无过,我只是说了我的理解,大家共同进步
他哪里错了 你得 指出了 而不是说 他错了 也许 你指出了 我们会帮你指正
好文一定要顶,支持一下