ListView实现不同item的方法和原理分析

一问题抛出
Listview是android里面的重要组件,用来显示一个竖向列表,这个没有什么问题;但是有个时候列表里面的item不是一样的,如下图,列表里面应该有3种类型的item

 
1. 头像在左边的气泡Item ,比如”今天下午我就不出来了,...”
2. 头像在右边的气泡Item,比如”那就等着我发你好吧”
3. 单张图片显示圆角图片item
几种Item的风格是完全不同的,那么怎么实现呢?

二实现方法
实现的方法我这里可以列举出两种
1. 每个Item的布局文件包含气泡,左右头像和圆角图片,然后根据不同的条件做不同的逻辑判断,控制不同Item的显示和隐藏。
比如如果是接受信息,而且是文字的话,就显示左图片和文字;隐藏图片和右边图片,等等。
显而易见,这种方法很繁琐,很笨重,而且会导致item的布局文件很大,从而影响listview的效率(加载xml文件是需要时间的)
2. 使用listview提供的方法,实现的步骤如下:

a. 主要重写ListAdapter的newView(), getItemViewType(),getViewItemTypeCount()几个方法,如下:

01 public class MyCursorAdapter extends CursorAdapter {
02  
03 publicMyCursorAdapter(Context context, Cursor c, boolean autoRequery) {
04  
05 super(context, c,autoRequery);
06  
07 // TODO Auto-generatedconstructor stub
08  
09 }
10  
11  
12  
13 publicMyCursorAdapter(Context context, Cursor c, int flags) {
14  
15 super(context, c, flags);
16  
17 // TODO Auto-generatedconstructor stub
18  
19 }
20  
21  
22  
23 public void bindView(Viewarg0, Context arg1, Cursor arg2) {
24  
25  
26  
27 }
28  
29  
30  
31  
32 publicView newView(Context arg0, Cursor arg1, ViewGroup arg2) {
33  
34 if (接受文字){
35  
36 return 接受文字view
37  
38 }else if(发送文字){
39  
40 return 发送文字view
41  
42  
43 }else{
44  
45 return 圆角图片view
46  
47 }
48  
49 }
50  
51  
52  
53  
54 public int getItemViewType(int position) {
55  
56 StringitemValue = getCursor().getString(position);
57  
58  
59  
60 //下面的代码只是模拟判断逻辑
61  
62  
63 //另外,这个序号是从0开始索引的,由于我们有3种类型的item,所以返回0,1,2,请参考getViewTypeCount()
64  
65 if(itemValue.equals("接收文字信息")){//如果是接受文字信息,则显示布局1
66  
67 return0;
68  
69 }else if(itemValue.equals("发送文字信息")){//如果是发送文字信息,则显示布局2
70  
71 return1;
72  
73 }
74  
75  
76 return 2;//显示单张图片
77  
78  
79 }
80  
81  
82  
83  
84 public intgetViewTypeCount() {
85  
86 return 3;//有3种类型的item,所以返回3
87  
88 }
89 }

嗯,所做的差不多就是这么多,另外就是要准备3个item布局文件,也就是newView里面要调用的3个布局文件
对了,在bindView的时候最好对view进行null的检查,因为3个布局文件里面的view是不同的,或者要分开进行bind,不然有可能会有空指针异常。

三 原理分析
上面第2种实现方法确实比较灵活,那listview是怎么实现的呢?
而且我们知道listview的item是可以复用的,那么为什么它不会复用错位呢?比如第2种类型的item,结果找到了缓存中第1种类型的item,就像本来要显示一个发送图片,结果找到发送文字的item,那么复用的时候肯定有问题,因为发送文字的item中根本没有ImageView,只有TextView来显示文字的。
1. 文件路径
frameworks\base\core\java\android\widget\AbsListView.java
代码

01 /**
02  
03 * The data set used to store unused viewsthat should be reused during the next layout
04  
05 * to avoid creating new ones
06  
07 */
08  
09 final RecycleBin mRecycler = newRecycleBin();
10 View obtainView(int position,boolean[] isScrap) {
11  
12 ...
13  
14  
15  
16 final View scrapView =mRecycler.getScrapView(position);
17  
18 ...
19 }

在ListView的一个item要显示的时候,就会调用AbsListView.obtainView()方法,比如滑动的时候,滑动出一个Item
AbsListview会向RecycleBin请求一个scrapView,这个RecycleBin是listview里面的一个重要机制,简单来说,就是它缓存了那些不在屏幕内的listview item,相当于一个垃圾箱,然后当有新的item需要显示的时候,它会首先向垃圾箱里面请求一个已经不显示的item,如果有这样的item的话,就直接拿过来,然后调用下bindView,重新bind下数据就可以了。
如果没有这样的item就会调用newView去创建一个item view。

滑动的时候,它会不断地把滑出屏幕的item添加到RecycleBin这个垃圾箱里面。
这样就实现了一个循环,listview不管有多少数据,不管滑动多少次,真正通过newView产生的item view其实就是一个屏幕内最多容纳的item数目,形成一个链条结构,不断回收,不断复用。

那接下来看看mRecycler.getScrapView(position)的实现

01 private ArrayList<View>[] mScrapViews;
02  
03  
04 private ArrayList<View> mCurrentScrap;
05  
06  
07 View getScrapView(int position) {
08  
09 if (mViewTypeCount ==1) {
10  
11 return retrieveFromScrap(mCurrentScrap,position);
12  
13 } else {
14  
15 int whichScrap =mAdapter.getItemViewType(position);
16  
17 if (whichScrap>= 0 && whichScrap < mScrapViews.length) {
18  
19 returnretrieveFromScrap(mScrapViews[whichScrap], position);
20  
21 }
22  
23 }
24  
25 return null;
26  
27 }

这个viewTypeCount就是ListAdapter的getViewTypeCount()方法返回的,默认实现就是返回1,如果没有重写的话,在setAdapter的时候调用,代表的是listview里面会有多少种类型的item,如下:

01 public void setAdapter(ListAdapter adapter) {
02  
03 ...
04  
05 super.setAdapter(adapter);
06  
07  
08  
09 if (mAdapter != null){
10  
11  
12  
13  
14 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
15  
16  
17  
18 }
19 ..
20 }

如果为viewTypeCount==1的话,也就是只有一种类型的item,那么直接从mCurrentScrap里面获取即可。

那如果有多个类型的item的话,怎么办呢?
首先,调用我们重写的getItemViewType(int position)来获取到这种类型的item的索引号
int whichScrap = mAdapter.getItemViewType(position);
然后根据这个索引号whichScrap从mScrapView数组里面获取到一个垃圾箱,然后再从垃圾箱里面去获取这个类型的被回收的Item。
这样就解决了复用错误的问题,比如把第2种类型的item复用了缓存中第1种类型的Item,这样就解决了第三章开头说的那个复用错位的问题。

2. Listview是怎么把一个item添加到垃圾箱?
那么,我们来拿一个简单的情景来举例子,比如滑动的时候
文件路径:
frameworks\base\core\java\android\widget\AbsListView.java
代码:

001 /**
002  
003 * Track a motion scroll
004  
005 *
006  
007 * @param deltaY Amount tooffset mMotionView. This is the accumulated delta since the motion
008  
009 *
010 began. Positive numbers mean the user'sfinger is moving down the screen.
011  
012 * @param incrementalDeltaYChange in deltaY from the previous event.
013  
014 * <a href="http://home.51cto.com/index.php?s=/space/34010" target="_blank">@return</a> true if we'realready at the beginning/end of the list and have nothing to do.
015  
016 */
017  
018 boolean trackMotionScroll(intdeltaY, int incrementalDeltaY) {
019  
020 ...
021  
022  
023  
024 if (down) {//向上滚动
025  
026  
027 int top =-incrementalDeltaY;
028  
029 ...
030  
031 for (int i = 0; i< childCount; i++) {
032  
033 final View child= getChildAt(i);
034  
035 if(child.getBottom() >= top) {
036  
037 break;
038  
039 } else {
040  
041 count++;
042  
043 int position= firstPosition + i;
044  
045 if (position>= headerViewsCount && position < footerViewsStart) {
046  
047 // Theview will be rebound to new data, clear any
048  
049 //system-managed transient state.
050  
051 if(child.isAccessibilityFocused()) {
052  
053 child.clearAccessibilityFocus();
054  
055 }
056  
057 mRecycler.addScrapView(child, position);
058  
059 }
060  
061 }
062  
063 }
064  
065 } else {//向下滚动
066  
067  
068  
069 int bottom = getHeight() -incrementalDeltaY;
070  
071 if ((mGroupFlags& CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
072  
073 bottom -=listPadding.bottom;
074  
075 }
076  
077 for (int i =childCount - 1; i >= 0; i--) {
078  
079  
080 final View child = getChildAt(i);
081  
082 if(child.getTop() <= bottom) {
083  
084 break;
085  
086 } else {
087  
088 start = i;
089  
090 count++;
091  
092 int position= firstPosition + i;
093  
094 if (position>= headerViewsCount && position < footerViewsStart) {
095  
096 // Theview will be rebound to new data, clear any
097  
098 //system-managed transient state.
099  
100 if(child.isAccessibilityFocused()) {
101  
102 child.clearAccessibilityFocus();
103  
104 }
105  
106 mRecycler.addScrapView(child, position);
107  
108 }
109  
110 }
111  
112 }
113  
114 }

如果向上滚动的话,那么就判断item的bottom-滚动距离 >=0?如果是,那么说明这个item还是可见的,不应该添加到垃圾箱;否则就不可见了。
这个判断逻辑要结合下手机屏幕坐标系来理解,如下:

图有点丑,勿见怪,坐标原点(0,0)是在屏幕的左上方,这个有点特别。
那么如果是向上滑动话的,我们要判断某个item是否被滑动出了屏幕,就是判断这个item的bottom – 向上滚动量< 0?比如一个item 的最下面的边界是50px那个地方,然后向上滚动了60px,那么肯定已经滑动出了屏幕,对不对?
也就是50 – 60 = -10 <0
如果只是滑动了40px,那么这个item应该还有10px留着屏幕上面,这个时候肯定不能被回收,因为它对于用户还是可见的。
也就是 50 – 40 = 10 >0
如果刚好滑动了50px,按照listview 的逻辑,这个item也是不回收的。如下:

1 if (child.getBottom() >=top) {
2  
3  
4 break;
5  
6 }

再排除是否是listview 的header或者footer,如果不是的话,那就是listview的内容item了,应该添加到垃圾箱里面。

01 else {
02  
03 count++;
04  
05 int position= firstPosition + i;
06  
07 if (position>= headerViewsCount && position < footerViewsStart) {
08  
09 // Theview will be rebound to new data, clear any
10  
11 //system-managed transient state.
12  
13 if(child.isAccessibilityFocused()) {
14  
15 child.clearAccessibilityFocus();
16  
17 }
18  
19 mRecycler.addScrapView(child,position);
20  
21 }
22  
23 }

为了清理内存,它会先清理掉这个itemview的一些属性,然后调用mRecycler.addScrapView(child, position);添加到垃圾箱。

那如果是向下滑动呢?
根据上面手机的坐标系,这个时候肯定是判断item的top和整个ListView的高度以及滚动距离。应该是top+ 滚动距离 > 整个ListView的高度,这个时候说明item已经不可见;如果top + 滚动距离 <= 整个ListView的高度,就说明这个item还是可见的。

1 int bottom = getHeight() -incrementalDeltaY;
2 if (child.getTop() <=bottom) {//仍然可见
3  
4  
5 break;
6 }

好,分析完这个滚动的计算逻辑后,来看看如何把view添加到垃圾箱的。

01 void addScrapView(View scrap,int position) {
02  
03 ...
04  
05  
06  
07 if(mViewTypeCount == 1) {
08 //如果只有一种item类型,直接添加
09  
10  
11 mCurrentScrap.add(scrap);
12  
13 } else {//如果有多种item类型,找到viewType对应的垃圾箱添加
14  
15  
16 mScrapViews[viewType].add(scrap);
17  
18 }
19  
20 ...
21 }

四  小结
1. 这篇帖子总结Listview中如果有多种类型的item的实现方式和原理。
2. 多个Item实现的原理主要就是AbsListView中有个mScrapViews数组,它的大小对应着Item类型的数目,也就是getItemTypeCount的返回大小。这个mScrapViews里面根据viewType的值,把不同类型的item存放在不同的ArrayList里面;
然后获取的时候再根据这个viewType首先来找到对应的ArrayList垃圾箱,然后再从ArrayList垃圾箱里面找到同一个类型的缓存item,当然如果没有找到,就会调用newView新建。
3. 分析了滚动的情况下,listview判断item是否可见的实现原理,它是根据item的坐标来判断的。

转至 http://bbs.51cto.com/thread-1168539-1.html

Android ListView实现不同item的方法和原理分析的更多相关文章

  1. android中获取root权限的方法以及原理(转)

    一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android 玩家中常说的“越狱”有一个更深层次的认识. 二. Root 的介绍 1. Root 的目的 可以让我们拥有 ...

  2. Android ListView 滚动的N种方法

    Android 里面让ListView滚动有N种方法,这儿列举三种: 我的需求是通过按键让Listview滚动起来,当然这些按键不是通过Android标识接口传输过来的,所以不能通过监听按键事件来实现 ...

  3. Android ListView异步载入图片乱序问题,原因分析及解决方式

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android全部系统自带的控件其中,ListView这个控件算是 ...

  4. ScrollView嵌套ListView,ListView完全展开及makeMeasureSpec测量机制原理分析

    在实际应用中,经常会碰到非常规的布局要求,比如说在ScrollView里嵌套ListView,ScrollView和ListView都是可以滚动的控件,这样布局看似很奇怪,但是有些效果又不得不这样做. ...

  5. Charles的HTTPS抓包方法及原理分析

    原文地址:http://www.jianshu.com/p/870451cb4eb0 背景 作为移动平台的RD,项目开发过程中一项比较重要的甩锅技能——抓包应该大家都比较熟悉了,毕竟有些bug可能是由 ...

  6. Eventbus 使用方法和原理分析

    对于 Eventbus ,相信很多 Android 小伙伴都用到过. 1.创建事件实体类 所谓的事件实体类,就是传递的事件,一个组件向另一个组件发送的信息可以储存在一个类中,该类就是一个事件,会被 E ...

  7. 【Android漏洞复现】StrandHogg漏洞复现及原理分析_Android系统上的维京海盗

    文章作者MG1937 CNBLOG博客:ALDYS4 QQ:3496925334 0x00 StrandHogg漏洞详情 StrandHogg漏洞 CVE编号:暂无 [漏洞危害] 近日,Android ...

  8. Android ListView实现单击item出现删除按钮以及滑动出现删除按钮

    我自己一个人弄的公司的产品客户端,所以还是想记录下来以免忘记或者丢失... 在我的上一篇博文(点击打开链接)是一个文件管理的东西,基础组件也是ListView所以在此只是改动一下而已. 单击: 点击出 ...

  9. android ListView中的Item有Button时候点击异常处理

    1.当ListView中有Button的时候往往会遇到很多问题,比较常见的一个问题是: 假设:在ListView中有N个Item当点击其中某个Item中的Button的时候,需要改变当前Button的 ...

随机推荐

  1. min-height

    1.min-height min-height:160px;height:auto!important;height:160px; min-height:160px; 设置对象box的最小高度,Fir ...

  2. c#语句 类

    知识点: 1.string类 2.Math类 3.DateTime  获取时间 for穷举 1.羽毛球拍15元,球3元,水2元.现有200元,每种至少买一个,共有多少种可能.

  3. 无法删除DLL文件解决方法(转)

    手动解决dll文件无法删除的终极方法 手动解决dll文件无法删除的终极方法 相信大家都遇见过:在删除一些软件的时候弹出某某文件正在运行或磁盘写保护不能删除这样的报错提示吧.而常常删除不掉的都一些后缀为 ...

  4. css中的一些兼容问题

    浏览器兼容 为什么会有兼容问题? 由于市场上浏览器种类众多,而不同浏览器其内核亦不尽相同,所以各个浏览器对网页的解析就有一定出入,这也是导致浏览器兼容问题出现的主要原因,我们的网页需要在主流浏览器上正 ...

  5. springMVC搭建

    springMVC搭建 1.Spring特点: 方便耦合,简化开发,提升性能 AOP面向切面的编程 声明式事务支持 方便程序的调试 方便集成各大优秀的框架 Java源代码学习的典范 2.Java的面向 ...

  6. Chrome浏览器插件推荐大全

    如何下载:http://www.cnplugins.com/devtool/ 提示:下载可能版本过旧,推荐搜索喜爱的插件之后前往官网或者github(编译的时候确保node和npm都是最新的版本).或 ...

  7. Python3.5连接Mysql

    由于mysqldb目前仅支持到python3.4,所以这里选择pymysql. pymysql下载地址: https://pypi.python.org/packages/source/P/PyMyS ...

  8. js中把数据库时间转为正常值

    function timeFormatter(value) { var da = new Date(parseInt(value.replace("/Date(", "& ...

  9. Java集合框架使用总结

    Java集合框架使用总结 前言:本文是对Java集合框架做了一个概括性的解说,目的是对Java集合框架体系有个总体认识,如果你想学习具体的接口和类的使用方法,请参看JavaAPI文档. 一.概述数据结 ...

  10. ASP.NET获取工程根目录的方法集合

    1.取得控制台应用程序的根目录方法 方法1.Environment.CurrentDirectory //取得或设置当前工作目录的完整限定路径 方法2.AppDomain.CurrentDomain. ...