Android之ListView的getItemViewType和getViewTypeCount
PS:感觉这两个方法其实还是很容易理解的,也算是给我其他两个朋友写的吧,帮他们搞清楚这两个方法的用法和概念。同时还有一些小细节问题需要注意。
学习内容:
1.getItemViewType和getViewTypeCount
getItemViewType和getViewTypeCount是ListView中实现复杂列表的两个相关的方法,普通的ListView中Item是相同的,那么我们只需要实现Adapter中四个抽象方法即可,但是如果页面中Item长得比较的复杂呢?比如说这个。

比如说这个列表项,其实也不是很复杂,这种类型的Item也有其他的实现方式,比如说在Adapter中实现SectionIndexer也是可以实现的,但是我们就拿这个来说明一下问题,如果一个Item第一种类型是TextView,第二种类型是ImageView+Button+TextView呢,那么这样复杂的列表我们就需要使用getItemViewType()和getTypeViewCount()两个方法去实现了。这两个方法理解起来还是比较容易的,获取Item中Type的类型以及Item中Type的相关数量。废话就不多说了,直接说实现方式。
public class ListAdapter extends BaseAdapter {
/**
* Item类型,int值.必须从0开始依次递增.
* */
private static final int TYPE_TITLE = 0;
private static final int TYPE_CONTENT = 1;
/**
* Item Type 的数量
* */
private static final int TYPE_ITEM_COUNT = 2;
/**
* 数据
* */
private List<Company> mData = new ArrayList<>();
private Context context;
public ListAdapter(Context context,List<Company>mData){
this.context = context;
this.mData = mData;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
/**
* 不同类型的ViewHolder
* */
TitleViewHolder titleViewHolder = null;
CompanyViewHolder contentViewHolder = null;
/**
* 对类型进行判断,分别inflate不同的布局.
* */
switch (getItemViewType(position)){
case TYPE_TITLE:
titleViewHolder = new TitleViewHolder();
if(convertView == null){
convertView = View.inflate(context, R.layout.view_holder_company_index,null);
titleViewHolder.title = (TextView) convertView.findViewById(R.id.tv_title);
//setTag()
convertView.setTag(titleViewHolder);
}else{
//getTag();
titleViewHolder = (TitleViewHolder) convertView.getTag();
}
titleViewHolder.title.setText(mData.get(position).getName());
break;
case TYPE_CONTENT:
contentViewHolder = new CompanyViewHolder();
if(convertView == null){
convertView = View.inflate(context,R.layout.view_holder_company,null);
contentViewHolder.content = (TextView) convertView.findViewById(R.id.tv_content);
convertView.setTag(contentViewHolder);
}else{
contentViewHolder = (CompanyViewHolder) convertView.getTag();
}
contentViewHolder.content.setText(mData.get(position).getCode());
break;
}
return convertView;
}
/**
* 根据position获取Item的类型
* */
@Override
public int getItemViewType(int position) {
if(TextUtils.isEmpty(mData.get(position).getCode())){
return TYPE_TITLE;
}else{
return TYPE_CONTENT;
}
}
/**
* 返回Item Type的总数量
* */
@Override
public int getViewTypeCount() {
return TYPE_ITEM_COUNT;
}
static class TitleViewHolder{
TextView title;
}
static class CompanyViewHolder{
TextView content;
}
}
- 首先我们需要为不同的Item设置不同的数值,int值,因为getItemViewType返回的是int值,所以需定义成int,必须从0开始,依次递增。原因我后续会做出解释。
- 重写getItemViewType和getViewTypeCount方法,getViewTypeCount返回Item的类型总数,getViewTypeCount则需要进行判断,判断方式一般都是通过JavaBean中的相关字段来判断的,因此这块不需要过于纠结。只需要根据position获取Item的具体类型进行判断然后就返回就可以了。
- 定义ViewHolder,根据类型的不同需要定义多个ViewHolder,减少findViewById()的次数。
- 重写getView()中的相关方法,在getView中首先根据position获取Item的类型去加载不用的布局,这里同时会setViewType为不同类型的Item设置RecycleBin,解决ListView由于多个类型Item的复用问题。不清楚RecycleBin机制的读者可以去看下ListView的复用机制这里说到了RecycleBin,如果不懂这个机制是看不明白下面的解释的。
- 最后根据传递过来的数据setAdapter然后为Item进行赋值就完成了。
大体的一个思路就是这样实现的,这里需要说一下为什么定义Item的类型的时候必须要从0开始,依次递增,那么原因是什么呢?如果我们有三种类型,我们将Item定义成1,2,4,那么势必会出现ArrayIndexOutOfBoundsException,也就是所谓的数组越界,我上网查了很多资料都说会出现异常,并且Google也确实标明了,Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.但是看到这里就没有后续了,没人会去说明这个问题是怎样发生的,为什么要这样去定义。可能我就是闲的蛋疼的那种人,不弄明白确实感到不舒服。我在解释一下具体的原因:
其实发生这种情况一般都是我们在下拉的时候出现的问题,在第一次加载第一页的时候是不会直接出现崩溃现象的,那么心细的读者可能会明白这有可能是ListView在复用时出现的问题,其实却是就是ListView复用机制导致的。我们来看一下这个方法:
/**
*ListView在针对不同Item复用时会调用这个方法
*为每一种不同的Item设置一个RecycleBin,用于复用.
*/
public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
// noinspection unchecked
/**
* 根据viewTypeCount的数量设置一个ArrayList.
* 同时为每一个Item再设置一个ArrayList,用来存储ScrapView.
* 相当于一个二维数组来维护每一个Item的ScrapView数组.
* 这里也就相当于为不同的Item设置单独的RecycleBin.
*/
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
//保存ViewTypeCount,也就 = 2
mViewTypeCount = viewTypeCount;
//当前的Scrap是第一个Item的ScrapView数组.
mCurrentScrap = scrapViews[0];
//mScrapViews就保存了一个二维数组维护的RecycleBin.
mScrapViews = scrapViews;
}

这是具体的数据结构,简单理解就是每一个Item都有对应的ScrapView数组。这里其实并不是出问题的地方,我们都知道Item一旦被移出了屏幕,首先会Detach掉,然后被加入到mScrapView数组中(废弃View池),那么在addScrapView的时候就会出现异常.
void addScrapView(View scrap) {
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
// Don't put header or footer views or views that should be ignored
// into the scrap heap
int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
removeDetachedView(scrap, false);
}
return;
}
/**
*核心代码就是这块,由于我们mViewTypeCount != 1 的,因此if条件不成立.
*因此会执行else代码.
*/
if (mViewTypeCount == 1) {
dispatchFinishTemporaryDetach(scrap);
mCurrentScrap.add(scrap);
} else {
dispatchFinishTemporaryDetach(scrap);
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
问题就在于这个else代码当中,我们可以看到mScrapViews[viewType].add(scrap)代码执行了,我们前面图片上显示的,mScrapViews[]是很据Item的种类数量new出来的,由于我们Item总数是两种类型,那么mScrapViews[].length = 2,但是这里是mScrapView[viewtype],viewtype是什么,其实就是我们getItemViewType的返回值,如果我们将类型定义成2和3,那么他会访问mScrapView[2]和mScrapView[3],可想而知,一定会出现ArrayIndexOutOffBoundsException.这就是数组越界的真正原因。
基本就解释完了,贴上一个从GitHub荡下来的源代码:Demo下载
Android之ListView的getItemViewType和getViewTypeCount的更多相关文章
- Android ListView Adapter的getItemViewType和getViewTypeCount多种布局
<Android ListView Adapter的getItemViewType和getViewTypeCount多种布局> 在Android的ListView中.假设在一个Lis ...
- Android一个ListView列表之中插入两种不同的数据
http://www.cnblogs.com/roucheng/ Android一个ListView列表之中插入两种不同的数据 代码如下: public class ViewHolder{ Butto ...
- Android为ListView的Item设置不同的布局
MainActivity如下: package cc.testlistview; import java.util.ArrayList; import java.util.HashMap; impor ...
- Android开发-Listview中显示不同的视图布局
1. 使用场景 在重写ListView的BaseAdapter时,我们常常在getView()方法中复用convertView,以提高性能.convertView在Item为单一的同种类型布局时,能够 ...
- android自定义listview实现header悬浮框效果
之前在使用iOS时,看到过一种分组的View,每一组都有一个Header,在上下滑动的时候,会有一个悬浮的Header,这种体验觉得很不错,请看下图: 上图中标红的1,2,3,4四张图中,当向上滑动时 ...
- Android开发——ListView使用技巧总结(一)
)还有一点就是要控制异步任务的执行频率,因为当用户频繁的上下滑动,会瞬间产生上百个异步任务,会带来无意义的大量的UI更新操作,因此可以考虑在列表滑动时停止进行异步任务,直到列表停下来. //判断列表的 ...
- Android—万能ListView适配器
ListView是开发中最常用的控件了,但是总是会写重复的代码,浪费时间又没有意义. 最近参考一些资料,发现一个万能ListView适配器,代码量少,节省时间,总结一下分享给大家. 首先有一个自定义的 ...
- Android中ListView实现图文并列并且自定义分割线(完善仿微信APP)
昨天的(今天凌晨)的博文<Android中Fragment和ViewPager那点事儿>中,我们通过使用Fragment和ViewPager模仿实现了微信的布局框架.今天我们来通过使用Li ...
- Android之ListView性能优化——一行代码绑定数据——万能适配器
如下图,加入现在有一个这样的需求图,你会怎么做?作为一个初学者,之前我都是直接用SimpleAdapter结合一个Item的布局来实现的,感觉这样实现起来很方便(基本上一行代码就可以实现),而且也没有 ...
随机推荐
- 让IE6/IE7/IE8浏览器支持CSS3属性
让IE6/IE7/IE8浏览器支持CSS3属性 一.下载 您可以狠狠地点击这里:ie-css3.htc,这个玩意儿是让IE浏览器支持CSS3表现的关键东东. 二.上面的是什么东西 首先说说.htc文件 ...
- SDOI 2016 游戏
树链剖分 线段树维护区间最小值,区间最大值 更新,对于每一个区间,找到当前区间的最小值的最大值,和要更新的值比较,如果比最大值还大,则此数对于以后的询问无任何贡献,直接返回即可,若有贡献,则一直递归到 ...
- IT人生知识分享:博弈论的理性思维
背景: 昨天看了<最强大脑>,由于节目比较有争议性,不知为什么,作为一名感性的人,就想试一下如果自己理性分析会是怎样的呢? 过程是这样的: 中国队(3人)VS英国队(4人). 1:李建东( ...
- 移动Web触控事件总结
移动web风风火火几多年,让我这个在Pc端漂流的前端er不免心生仰慕,的确入行几多年,也该是时候进军移动web了.移动web中踩到的第一个坑就是事件问题,所以在吸取众大神的经验后,特作总结以示后来者. ...
- C# Random生成多个不重复的随机数万能接口
C#,Radom.Next()提供了在一定范围生成一个随机数的方法,我现在有个业务场景是给其他部门推送一些数据供他们做抽样检查处理,假设我的数据库里面有N条数据,现在要定期给其随机推送数据,我需要先拿 ...
- ASP.NET MVC 路由(二)
ASP.NET MVC路由(二) 前言 在上一篇中,提及了Route.RouteCollection对象的一些信息,以及它们的结构所对应的关系.按照处理流程走下来还有遗留的疑问没有解决这个篇幅就来讲 ...
- 新人入职100天,聊聊自己的经验&教训
这篇文章讲了什么? 如题,本屌入职100天之后的经验和教训,具体包含: 对开发的一点感悟. 对如何提问的一点见解. 对Google开发流程的吐槽. 如果你 打算去国外工作. 对Google的开发流程感 ...
- CSS效果集锦(持续更新中)
高亮光弧效果 使用CSS3实现的一个高亮光弧效果,当鼠标hover到某一个元素上时,一道光弧从左向右闪过,效果如下: 代码如下: <!DOCTYPE html> <html lang ...
- Java中instanceof和isInstance区别详解
一次性搞定instanceof和isInstance,instanceof和isInstance长的非常像,用法也很类似,先看看这两个的用法: obj.instanceof(class) 也就是说这 ...
- 探讨Js的事件的冒泡阶段
近来看到了一个新的知识点叫Js的事件冒泡,因此决定自己来研究一番. 大家应该都知道,Js中的事件处理分为三个阶段,1:事件的捕获阶段,2:处于目标阶段,3:事件的冒泡阶段.那么什么是事件的捕获和冒泡呢 ...