最近忙着学习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缓存视图的目的。
楼主你讲的是错的吧
如果错了还请指正啊,孰能无过,我只是说了我的理解,大家共同进步
他哪里错了 你得 指出了 而不是说 他错了 也许 你指出了 我们会帮你指正
好文一定要顶,支持一下