打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Android ListView实现不同item的方法和原理分析
ListView实现不同item的方法和原理分析


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



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


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


a. 主要重写ListAdapternewView(), getItemViewType(),getViewItemTypeCount()几个方法,如下:
01public class MyCursorAdapter extends CursorAdapter {
02 
03publicMyCursorAdapter(Context context, Cursor c, boolean autoRequery) {
04 
05super(context, c,autoRequery);
06 
07// TODO Auto-generatedconstructor stub
08 
09}
10 
11 
12 
13publicMyCursorAdapter(Context context, Cursor c, int flags) {
14 
15super(context, c, flags);
16 
17// TODO Auto-generatedconstructor stub
18 
19}
20 
21 
22 
23public void bindView(Viewarg0, Context arg1, Cursor arg2) {
24 
25 
26 
27}
28 
29 
30 
31 
32publicView newView(Context arg0, Cursor arg1, ViewGroup arg2) {
33 
34if (接受文字){
35 
36return 接受文字view
37 
38}else if(发送文字){
39 
40return 发送文字view
41 
42 
43}else{
44 
45return 圆角图片view
46 
47}
48 
49}
50 
51 
52 
53 
54public int getItemViewType(int position) {
55 
56StringitemValue = getCursor().getString(position);
57 
58 
59 
60//下面的代码只是模拟判断逻辑
61 
62 
63//另外,这个序号是从0开始索引的,由于我们有3种类型的item,所以返回0,1,2,请参考getViewTypeCount()
64 
65if(itemValue.equals("接收文字信息")){//如果是接受文字信息,则显示布局1
66 
67return0;
68 
69}else if(itemValue.equals("发送文字信息")){//如果是发送文字信息,则显示布局2
70 
71return1;
72 
73}
74 
75 
76return 2;//显示单张图片
77 
78 
79}
80 
81 
82 
83 
84public intgetViewTypeCount() {
85 
86return 3;//有3种类型的item,所以返回3
87 
88}
89}
嗯,所做的差不多就是这么多,另外就是要准备3item布局文件,也就是newView里面要调用的3个布局文件
对了,在bindView的时候最好对view进行null的检查,因为3个布局文件里面的view是不同的,或者要分开进行bind,不然有可能会有空指针异常。


原理分析
上面第2种实现方法确实比较灵活,那listview是怎么实现的呢?
而且我们知道listviewitem是可以复用的,那么为什么它不会复用错位呢?比如第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 
09final RecycleBin mRecycler = newRecycleBin();
10View obtainView(int position,boolean[] isScrap) {
11 
12...
13 
14 
15 
16final View scrapView =mRecycler.getScrapView(position);
17 
18...
19}
ListView的一个item要显示的时候,就会调用AbsListView.obtainView()方法,比如滑动的时候,滑动出一个Item
AbsListview会向RecycleBin请求一个scrapView,这个RecycleBinlistview里面的一个重要机制,简单来说,就是它缓存了那些不在屏幕内的listview item,相当于一个垃圾箱,然后当有新的item需要显示的时候,它会首先向垃圾箱里面请求一个已经不显示的item,如果有这样的item的话,就直接拿过来,然后调用下bindView,重新bind下数据就可以了。
如果没有这样的item就会调用newView去创建一个item view


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


那接下来看看mRecycler.getScrapView(position)的实现
01private ArrayList<View>[] mScrapViews;
02 
03 
04private ArrayList<View> mCurrentScrap;
05 
06 
07View getScrapView(int position) {
08 
09if (mViewTypeCount ==1) {
10 
11return retrieveFromScrap(mCurrentScrap,position);
12 
13} else {
14 
15int whichScrap =mAdapter.getItemViewType(position);
16 
17if (whichScrap>= 0 && whichScrap < mScrapViews.length) {
18 
19returnretrieveFromScrap(mScrapViews[whichScrap], position);
20 
21}
22 
23}
24 
25return null;
26 
27}
这个viewTypeCount就是ListAdaptergetViewTypeCount()方法返回的,默认实现就是返回1,如果没有重写的话,在setAdapter的时候调用,代表的是listview里面会有多少种类型的item,如下:
01public void setAdapter(ListAdapter adapter) {
02 
03...
04 
05super.setAdapter(adapter);
06 
07 
08 
09if (mAdapter != null){
10 
11 
12 
13 
14mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
15 
16 
17 
18}
19..
20}
如果为viewTypeCount==1的话,也就是只有一种类型的item,那么直接从mCurrentScrap里面获取即可。


那如果有多个类型的item的话,怎么办呢?
首先,调用我们重写的getItemViewType(int position)来获取到这种类型的item的索引号
int whichScrap = mAdapter.getItemViewType(position);
然后根据这个索引号whichScrapmScrapView数组里面获取到一个垃圾箱,然后再从垃圾箱里面去获取这个类型的被回收的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*
010began. 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 
018boolean trackMotionScroll(intdeltaY, int incrementalDeltaY) {
019 
020...
021 
022 
023 
024if (down) {//向上滚动
025 
026 
027int top =-incrementalDeltaY;
028 
029...
030 
031for (int i = 0; i< childCount; i++) {
032 
033final View child= getChildAt(i);
034 
035if(child.getBottom() >= top) {
036 
037break;
038 
039} else {
040 
041count++;
042 
043int position= firstPosition + i;
044 
045if (position>= headerViewsCount && position < footerViewsStart) {
046 
047// Theview will be rebound to new data, clear any
048 
049//system-managed transient state.
050 
051if(child.isAccessibilityFocused()) {
052 
053child.clearAccessibilityFocus();
054 
055}
056 
057mRecycler.addScrapView(child, position);
058 
059}
060 
061}
062 
063}
064 
065} else {//向下滚动
066 
067 
068 
069int bottom = getHeight() -incrementalDeltaY;
070 
071if ((mGroupFlags& CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
072 
073bottom -=listPadding.bottom;
074 
075}
076 
077for (int i =childCount - 1; i >= 0; i--) {
078 
079 
080final View child = getChildAt(i);
081 
082if(child.getTop() <= bottom) {
083 
084break;
085 
086} else {
087 
088start = i;
089 
090count++;
091 
092int position= firstPosition + i;
093 
094if (position>= headerViewsCount && position < footerViewsStart) {
095 
096// Theview will be rebound to new data, clear any
097 
098//system-managed transient state.
099 
100if(child.isAccessibilityFocused()) {
101 
102child.clearAccessibilityFocus();
103 
104}
105 
106mRecycler.addScrapView(child, position);
107 
108}
109 
110}
111 
112}
113 
114}
如果向上滚动的话,那么就判断itembottom-滚动距离 >=0?如果是,那么说明这个item还是可见的,不应该添加到垃圾箱;否则就不可见了。
这个判断逻辑要结合下手机屏幕坐标系来理解,如下:




图有点丑,勿见怪,坐标原点(0,0)是在屏幕的左上方,这个有点特别。
那么如果是向上滑动话的,我们要判断某个item是否被滑动出了屏幕,就是判断这个itembottom – 向上滚动量< 0?比如一个item 的最下面的边界是50px那个地方,然后向上滚动了60px,那么肯定已经滑动出了屏幕,对不对?
也就是50 – 60 = -10 <0
如果只是滑动了40px,那么这个item应该还有10px留着屏幕上面,这个时候肯定不能被回收,因为它对于用户还是可见的。
也就是 50 – 40 = 10 >0
如果刚好滑动了50px,按照listview 的逻辑,这个item也是不回收的。如下:
1if (child.getBottom() >=top) {
2 
3 
4break;
5 
6}
再排除是否是listview header或者footer,如果不是的话,那就是listview的内容item了,应该添加到垃圾箱里面。
01else {
02 
03count++;
04 
05int position= firstPosition + i;
06 
07if (position>= headerViewsCount && position < footerViewsStart) {
08 
09// Theview will be rebound to new data, clear any
10 
11//system-managed transient state.
12 
13if(child.isAccessibilityFocused()) {
14 
15child.clearAccessibilityFocus();
16 
17}
18 
19mRecycler.addScrapView(child,position);
20 
21}
22 
23}
为了清理内存,它会先清理掉这个itemview的一些属性,然后调用mRecycler.addScrapView(child, position);添加到垃圾箱。



那如果是向下滑动呢?
根据上面手机的坐标系,这个时候肯定是判断itemtop和整个ListView的高度以及滚动距离。应该是top+ 滚动距离 > 整个ListView的高度,这个时候说明item已经不可见;如果top + 滚动距离 <= 整个ListView的高度,就说明这个item还是可见的。
1int bottom = getHeight() -incrementalDeltaY;
2if (child.getTop() <=bottom) {//仍然可见
3 
4 
5break;
6}
好,分析完这个滚动的计算逻辑后,来看看如何把view添加到垃圾箱的。
01void addScrapView(View scrap,int position) {
02 
03...
04 
05 
06 
07if(mViewTypeCount == 1) {
08//如果只有一种item类型,直接添加
09 
10 
11mCurrentScrap.add(scrap);
12 
13} else {//如果有多种item类型,找到viewType对应的垃圾箱添加
14 
15 
16mScrapViews[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的坐标来判断的。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ListView系列(七)
RecyclerView和ListView使用对比分析
android Settings入门
[Android] ListView中getView的原理+如何在ListView中放置多个item
Android下Listview的onItemClick以及onItemLongClick等易模糊问题验证
Android笔记
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服