转:http://johncookie.iteye.com/blog/1250049
这里提到的ListView只是作为一个典型代表 其实在Android中 采用类似Adapter机制的GridView等都是可以适用的 而ListView应该是用得最多的 所以就以它来举例
大家都知道 将ListView和Adapter绑定以后 其实也就是将数据源和控件显示绑定在一起 而每次需要显示ListView的时候 里面的item其实是Adapter提供的 通过的就是重要的getView()方法 而做法也是在这上面进行
先来看一下基本的getView写法
- public View getView(int position, View convertView, ViewGroup parent) {
- View view =new View();
- //通过inflate等找到布局 然后findViewById等 设置各个显示的item
- return view;
- }
而在ListView滑动的过程中 很容易就会发现每次getView被执行 都会new出一个View对象 长此以往会产生很大的消耗 特别当item中还有Bitmap等 甚至会造成OOM的错误导致程序崩溃
在看getView提供的参数时 可能已经注意到了 有一个参数View convertView 而这个convertView其实就是最关键的部分了 原理上讲 当ListView滑动的过程中 会有item被滑出屏幕 而不再被使用 这时候Android会回收这个条目的view 这个view也就是这里的convertView
在上面的做法中 当item1被移除屏幕的时候 我们会重新new一个View给新显示的item_new 而如果使用了这个convertView 我们其实可以复用它 这样就省去了new View的大量开销
下面就是使用convertView后的情况
- public View getView(int position, View convertView, ViewGroup parent) {
- View view =null;
- if (convertView !=null) {
- view = convertView;
- //复用了回收的view 只需要直接作内容填充的修改就好了
- }else {
- view =new Xxx(...);
- //没有供复用的view 按一般的做法新建view
- }
- return view;
- }
这样一来 就避免了反复创建大量view的问题了
但是上面的仍然有缺陷 当我们的ListView中填充的item有多种形式时 比如微博中 有的item中包含图片 有的item包含视频 那么必然的 我们需要用到2种item的布局方式
此时如果只是单纯判断convert是否存在 会造成回收的view不符合你当前需要的布局 而类似转换失败出错退出
这里要提到Adapter中的另外2个方法:
public int getItemViewType(int position) {}
public int getViewTypeCount() {}
从方法名上 就可以比较明显的明白这2个的作用
下面附上一个demo代码
- class MyAdapterextends BaseAdapter{
- Context mContext;
- LinearLayout linearLayout =null;
- LayoutInflater inflater;
- TextView tex;
- finalint VIEW_TYPE =2;
- finalint TYPE_1 =0;
- finalint TYPE_2 =1;
- public MyAdapter(Context context) {
- mContext = context;
- inflater = LayoutInflater.from(mContext);
- }
- @Override
- publicint getCount() {
- return listString.size();
- }
- //每个convert view都会调用此方法,获得当前所需要的view样式
- @Override
- publicint getItemViewType(int position) {
- int p = position%6;
- if(p ==0)
- return TYPE_1;
- elseif(p <3)
- return TYPE_2;
- else
- return TYPE_1;
- }
- @Override
- publicint getViewTypeCount() {
- return2;
- }
- @Override
- public Object getItem(int arg0) {
- return listString.get(arg0);
- }
- @Override
- publiclong getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- viewHolder1 holder1 =null;
- viewHolder2 holder2 =null;
- int type = getItemViewType(position);
- //无convertView,需要new出各个控件
- if(convertView ==null)
- {
- //按当前所需的样式,确定new的布局
- switch(type)
- {
- case TYPE_1:
- convertView = inflater.inflate(R.layout.listitem1, parent,false);
- holder1 =new viewHolder1();
- holder1.textView = (TextView)convertView.findViewById(R.id.textview1);
- holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox);
- convertView.setTag(holder1);
- break;
- case TYPE_2:
- convertView = inflater.inflate(R.layout.listitem2, parent,false);
- holder2 =new viewHolder2();
- holder2.textView = (TextView)convertView.findViewById(R.id.textview2);
- holder2.imageView = (ImageView)convertView.findViewById(R.id.imageview);
- convertView.setTag(holder2);
- break;
- }
- }
- else
- {
- //有convertView,按样式,取得不用的布局
- switch(type)
- {
- case TYPE_1:
- holder1 = (viewHolder1) convertView.getTag();
- break;
- case TYPE_2:
- holder2 = (viewHolder2) convertView.getTag();
- break;
- }
- //设置资源
- switch(type)
- {
- case TYPE_1:
- holder1.textView.setText(Integer.toString(position));
- holder1.checkBox.setChecked(true);
- break;
- case TYPE_2:
- holder2.textView.setText(Integer.toString(position));
- holder2.imageView.setBackgroundResource(R.drawable.icon);
- break;
- }
- }
- return convertView;
- }
- }
- //各个布局的控件资源
- class viewHolder1{
- CheckBox checkBox;
- TextView textView;
- }
- class viewHolder2{
- ImageView imageView;
- TextView textView;
- }
这里对于每个View使用了一个viewHolder来控制其内部的子item
还有一个需要注意的地方是使用了setTag和getTag的方法 将holder绑定到了view上 也算一种技巧
以上基本就是主要的内容了 下面再补充实际操作当中的一些Tips
*如果convertView上用Type区分有些繁琐 或者不需要那么复杂 只是很少有出现不同的情况 那么还可以在取得convertView后 通过java提供的 instanceof 来判断是否可以强转 如果不能强转 就去新建一个View的做法 但是其实这种做法并不规范 所以还是推荐上面的做法
*第二个是关于ListView 对于纯色的item背景 其实可以直接设置BackgroundColor 而不要使用图片 这一部分其实可以有不小的提升 同样的 对于任何纯色的背景 应该尽量去设置RGB颜色 而不是全用一张图片做背景
转:http://blog.youkuaiyun.com/kesenhoo/article/details/7196920
【0】ListView中getView的工作原理:
[1]ListView asks adapter “give me a view” (getView) for each item of the list.(通过getView来获取每个item)
[2]A new View is returned and displayed(获取到后返回显示)
那么如果我们有大量的数据需要显示的时候,每个Item都去重复执行getView中的创建新的View的动作吗?这样做会耗费大量的资源去执行重复的事情,实际上Android为我们提供了一套重复利用的机制叫做“Recycler”:
原理简单描述下就是这样:
在一个完整的ListView第一次出现时,每个Item都是Null的,getView的时候会跑到需要inflate一个Item的代码段,假设整个view只能最多显示10个item,那么当滑动到第11个Item的时候,第一个item会放入“recycler”,如果第11个Item和放入“Recycler”的item的view一致,那么就会使用"Recycler"里面的Item来显示,从而不用再重复inflate一次,这样大大节省了创建View的工作,在需要显示大量数据时显得尤为重要。
工作原理的示意图如下:
学习自http://android.amberfog.com/?p=296:
Demo:
这是一个getView的方法,其他细节的Code就不显示了
static class ViewHolder
{
public ImageView localImageView = null;
public TextView localTextView1 = null;
public TextView localTextView2 = null;
public TextView localTextView3 = null;
}
public View getView(int paramInt, View paramView, ViewGroup paramViewGroup)
{
logger.i("This is position:" + paramInt);
WrapperSonglist localUserShareSonglistEntity = (WrapperSonglist) getItem(paramInt);
if(localUserShareSonglistEntity != null)
{
ViewHolder holder = null;
if(paramView == null)
{
this.logger.d("convertView == null,Then inflate and findViewById");
paramView = this.mInflater.inflate(R.layout.listitem04, paramViewGroup, false);
holder = new ViewHolder();
holder.localImageView = (ImageView) paramView.findViewById(R.id.listitem04ImageView);
holder.localTextView1 = (TextView) paramView.findViewById(R.id.listitem04TextView01);
holder.localTextView2 = (TextView) paramView.findViewById(R.id.listitem04TextView02);
holder.localTextView3 = (TextView) paramView.findViewById(R.id.listitem04TextView03);
paramView.setTag(holder);
}
else
{
//Used ViewHolder to improve performance
this.logger.d("convertView != null,Then findViewById(get Holder)");
holder = (ViewHolder) paramView.getTag();
}
if(paramView != null)
{
this.logger.d("convertView != null,Then SetValue");
String mstr = localUserShareSonglistEntity.getSonglistImage();
int id = localUserShareSonglistEntity.getSonglistId();
holder.localTextView1.setText("[id]:"+id+",bitmap:url:"+mstr);
String name = localUserShareSonglistEntity.getSonglistName();
holder.localTextView2.setText("[Name]:"+name);
String url = localUserShareSonglistEntity.getSonglistUrl();
holder.localTextView3.setText("[Url]:"+url);
}
}
return paramView;
}
当我们第一次启动到Listview的时候如下,只显示了5个Item,那么getView被调用5次:
打印的Log如下:可以看到从0-4都是null,我们需要做inflate的动作,
- 01-12 17:58:22.144: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:0
- 01-12 17:58:22.154: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.174: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:1
- 01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.184: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:2
- 01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.194: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:3
- 01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
- 01-12 17:58:22.204: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:4
- 01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 17:58:22.214: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<STRONG>
- </STRONG>
01-12 17:58:22.144: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:0
01-12 17:58:22.154: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.174: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:1
01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.184: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:2
01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.194: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:3
01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.204: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:4
01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.214: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<strong>
</strong>
当我们小心往下滑动一个位置,刚出现第6个Item,此时第1个还没有消失时,
这个时候只打印了一个获取第6个Item的Log,如下:可以看到第6个Item还是为null,需要inflate出来新的
- 01-12 18:02:37.623: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:5
- 01-12 18:02:37.623: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
- 01-12 18:02:37.633: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<STRONG>
- </STRONG>
01-12 18:02:37.623: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:5
01-12 18:02:37.623: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 18:02:37.633: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<strong>
</strong>
如果此时再往下滑动列表,第1个item此时会放入Recycler,第7个Item此时不再是null,而是利用了第1个item的View,如下:
从下面的Log,可以看到从第7个开始就不是null了
01-12 18:52:36.243: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:6
01-12 18:52:36.243: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:36.243: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:36.693: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:7
01-12 18:52:36.693: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:36.693: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:37.024: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:8
01-12 18:52:37.024: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:37.034: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:37.604: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:9
01-12 18:52:37.604: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:37.604: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
等所有的item,一共10个都显示之后,不管上下滑动都再也不是NULL了,说明这个时候都是使用Recycle里面的view,而不会再重新inflate了,显然这样节省很多重复的操作
【1】重复多轮调用getView的解决方案
上面的例子我们可以看到,每滑动一次到需要显示的Item的时候就会调用一次getView,理论上是10个Item,均显示一次的话是要调用getView() 10次的,那么为什么有时候很奇怪,10个item显示一次也许会调用getView 20次,甚至40-50次呢?我想肯定很多人都遇到过这个问题
查了很久,其实我也没有找到root cause,只是知道我们在XML文件里面定义listView的时候需要设置height为fill_parent或者是指定的高度值(这个方法明显不靠谱,设置了高度,那么怎么进行不同屏幕的适配)
所以推荐使用下面的,例如:
- <ListView
- android:id="@+id/activity04_list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"<SPANstyle="FONT-SIZE: 13px"><STRONG></STRONG></SPAN><PREstyle="DISPLAY: inline !important"class=htmlname="code">/></PRE>
- <PRE></PRE>
- <P></P>
- <PRE></PRE>
- <BR>
- 网上有人解释说是因为ListView的Item的高度计算方法问题,试了下还是有效果的,希望之后有机会等了解原理后再来解释这个问题,我们可以先这也使用,设置之后会发现没滑动出现一个item才会调用一次getView,这样是合理的调用,而不会出现只有10个item却调用几十次这样比较tricky的事情,也节省了很多资源避免去重复做无用功。
- <P></P>
- <Pstyle="PADDING-BOTTOM: 0px; MARGIN-TOP: 0px; MARGIN-BOTTOM: 20px; PADDING-TOP: 0px">
- <SPANstyle="FONT-SIZE: 13px"><STRONG>谢谢!</STRONG></SPAN></P>
- <Pstyle="PADDING-BOTTOM: 0px; MARGIN-TOP: 0px; MARGIN-BOTTOM: 20px; PADDING-TOP: 0px">
- <SPANstyle="FONT-SIZE: 13px"><STRONG><BR>
- </STRONG></SPAN></P>
- <Pstyle="PADDING-BOTTOM: 0px; MARGIN-TOP: 0px; MARGIN-BOTTOM: 20px; PADDING-TOP: 0px">
- <STRONG><STRONG><BR>
- </STRONG></STRONG></P>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
转:http://hunankeda110.iteye.com/blog/1827691
以前一直在用BaseAdapter,对于其中的getview方法的重写一直不太清楚。今天终于得以有空来探究它的详细机制。
下面先讲讲我遇到的几个问题:
一.View getview(int position, View convertview, ViewGroup parent )中的第二个参数是什么含义;
二.View的SetTag和getTag方法的用途;
先来解决第一个问题:
android SDK中这样讲参数 convertview :
the old view to reuse, if possible. Note: You should check that this view is non-null and of anappropriate type before using.If it is not possible to convert this view to display the correct data, this method can create a new view.
翻译:
如果可以的话,这是旧View(这里不便翻译有的人翻成视图)的重用。 建议:在用之前,你应该检查这个View是
不是非空,是不是一个合适的类型。
如果不可能让这个VIew去显示一个恰当的数据,这个方法会创建一个新的View。
如果我们要做的是一个ListView,在手机上显示的只有那么几条Item,而整个ListView可能有很长,可能是100条
甚至是上万条,总不能让这么多条Item都驻留在内存中,所以android为你准备了一套机制,就是Recycler(反复循
环器),他的具体工作原理可以到 http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html去看。
但是有些地方他没有讲清,所以我再讲一下。先把代码贴出来
布局文件main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/result"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" >
</ListView>
</LinearLayout>
此处注意ListView的android:layout_height属性值为"fill_paternt",如果为“wrap_content"将会是另一种情况
adapter的代码ListViewAdapter.java :
class ListViewAdapter extends BaseAdapter
{
private Context mContext;
int i=0;
public ListViewAdapter (Context context)
{
this.mContext=context;
}
@Override
public int getCount()
{
return 30;
}
@Override
public Object getItem(int position)
{
return position;
}
@Override
public long getItemId(int position)
{
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
System.out.println("getView " + position + " " + convertView);//调试语句
Holder holder;
if(null==convertView)
{
holder=new Holder();
convertView=LayoutInflater.from(mContext).inflate(R.layout.textview, null); //mContext指的是调用的Activtty
holder.textView=(TextView)convertView.findViewById(R.id.textview);
convertView.setTag(holder);
}
else
{
holder=(Holder)convertView.getTag();
}
holder.textView.setText("position: "+position);
return convertView;
}
class Holder
{
public TextView textView;
}
}
运行程序之后发现屏幕上显示出的Item的convertview都为空,向下滑动新产生的Item的convetview都不为空。到此为止和上面链接中讲的是一致的,但是如果设置ListView的android:layout_height属性值为“wrap_content之后,发现只有第一个Item的convertview为null其他的不为空。
虽然两种设置不同,结果也不同,但是convertview的机制没有变。
其实到此为止我们可以总结出convertview的机制了,就是在初始显示的时候,每次显示一个item都调用一次getview方法但是每次调用的时候covertview为空(因为还没有旧的view),当显示完了之后。如果屏幕移动了之后,并且导致有些Item(也可以说是view)跑到屏幕外面,此时如果还有新的item需要产生,则这些item显示时调用的getview方法中的 convertview参数就不是null,而是那些移出屏幕的view(旧view),我们所要做的就是将需要显示的item填充到这些回收的view(旧view)中去,最后注意convertview为null的不仅仅是初始显示的那些item,还有一些是已经开始移入屏幕但是还没有view被回收的那些item。
最终我们用亲手写的代码实现了Recycler(反复循环器)。
第二个问题其实应该在第一个问题中嵌套来讲,但是为了思路清晰我分开了:
view的setTag和getTag方法其实很简单,在实际编写代码的时候一个view不仅仅是为了显示一些字符串、图片,有时我们还需要他们携带一些其他的数据以便我们对该view的识别或者其他操作。于是 android 的设计者们就创造了setTag(Object)方法来存放一些数据和view绑定,我们可以理解为这个是view的标签也可以理解为view 作为一个容器存放了一些数据。而这些数据我们也可以通过getTag()方法来取出来。
到这里setTag和getTag大家应该已经 明白了。再回到上面的话题,我们通过convertview的setTag方法和getTag方法来将我们要显示的数据来绑定在convertview 上。如果convertview 是第一次展示我们就创建新的Holder对象与之绑定,并在最后通过return convertview 返回,去显示;如果convertview 是回收来的那么我们就不必创建新的holder对象,只需要把原来的绑定的holder取出加上新的数据就行了。