ListView进阶用法

http://my.oschina.net/smalant/blog/50383

最近有需求需要在listView中载入不同的listItem布局,开始没有使用convertView,加载了多个item后导致了内存泄露,所以回来研究convertView在多个listItem布局时的

最近有需求需要在listView中载入不同的listItem布局,开始没有使用convertView,加载了多个item后导致了内存泄露,所以回来研究convertView在多个listItem布局时的缓存及应用,并且和大家分享

构造Adapter时,没有使用缓存的 convertView,导致内存泄露

示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = new Xxx(...);
  ... ...
  return view;
}


描述:
  
以构造ListViewBaseAdapter为例,在BaseAdapter中提供了方法:



public View getView(int position, View convertView, ViewGroup parent){ }


来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list itemview对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list itemview对象(初始化时缓存中没有view对象则convertViewnull)
  
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。

修正示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = null;
  if (convertView != null) {
  view = convertView;
  ...
  } else {
  view = new Xxx(...);
  ...
  }
  return view;
}

上述代码很好的解决了内存泄露的问题,使用convertView回收一些布局供下面重构是使用。

但是如果出现如下图的需求,convertView就不太好用了,convertViewItem为单一的布局时,能够回收并重用,但是多个Item布局时,convertView的回收和重用会出现问题。


无标题.png 


Listview中有3Item布局,即使convertView缓存了一些布局,但是在重构时,根本不知道怎么样去让convertView返回你所需要的布局,这时你需要让adapter知道我当前有哪些布局,我重构Item时的布局选取规则,好让convertView能返回你需要的布局

需要重写一下两个函数

@Override

public int getItemViewType(int position) {}

官网解释如下,不解释了
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)]getView(int, View, ViewGroup)for the specified item.

Parameters

position The position of the item within the adapter's data set whose view type we want.

Returns




  • An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.


@Override

public int getViewTypeCount() {}


Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup)for the specified item.

Parameters

position The position of the item within the adapter's data set whose view type we want.

Returns




  • An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.


上述两个函数的作用这如它的名字,得到Item的样式,得到所有的样式数量

下面直接上代码,就是上图的实现代码:



001 package com.bestv.listViewTest;
002 import java.util.ArrayList;
003 import android.app.Activity;
004 import android.content.Context;
005 import android.os.Bundle;
006 import android.util.Log;
007 import android.view.LayoutInflater;
008 import android.view.View;
009 import android.view.ViewGroup;
010 import android.widget.BaseAdapter;
011 import android.widget.CheckBox;
012 import android.widget.ImageView;
013 import android.widget.LinearLayout;
014 import android.widget.ListView;
015 import android.widget.TextView;
016   
017 public class listViewTest extends Activity {
018   
019 /** Called when the activity is first created. */
020 ListView listView;
021 MyAdapter listAdapter;
022 ArrayList<String> listString;
023   
024 @Override
025 public void onCreate(Bundle savedInstanceState) {
026 super.onCreate(savedInstanceState);
027 setContentView(R.layout.main);
028 listView = (ListView)this.findViewById(R.id.listview);
029 listString = new ArrayList<String>();
030   
031 for(int i = 0 ; i < 100 ; i++)
032   
033 {
034   
035 listString.add(Integer.toString(i));
036   
037 }
038   
039 listAdapter = new MyAdapter(this);
040   
041 listView.setAdapter(listAdapter);
042   
043 }
044   
045   
046   
047 class MyAdapter extends BaseAdapter{
048   
049   
050   
051 Context mContext;
052   
053 LinearLayout linearLayout = null;
054   
055 LayoutInflater inflater;
056   
057 TextView tex;
058   
059 final int VIEW_TYPE = 3;
060   
061 final int TYPE_1 = 0;
062   
063 final int TYPE_2 = 1;
064   
065 final int TYPE_3 = 2;
066   
067   
068   
069 public MyAdapter(Context context) {
070   
071 // TODO Auto-generated constructor stub
072   
073 mContext = context;
074   
075 inflater = LayoutInflater.from(mContext);
076   
077 }
078   
079   
080   
081 @Override
082   
083 public int getCount() {
084   
085 // TODO Auto-generated method stub
086   
087 return listString.size();
088   
089 }
090   
091   
092   
093 //每个convert view都会调用此方法,获得当前所需要的view样式
094   
095 @Override
096   
097 public int getItemViewType(int position) {
098   
099 // TODO Auto-generated method stub
100   
101 int p = position%6;
102   
103 if(p == 0)
104   
105 return TYPE_1;
106   
107 else if(p < 3)
108   
109 return TYPE_2;
110   
111 else if(p < 6)
112   
113 return TYPE_3;
114   
115 else
116   
117 return TYPE_1;
118   
119   
120   
121 }
122   
123   
124   
125 @Override
126   
127 public int getViewTypeCount() {
128   
129 // TODO Auto-generated method stub
130   
131 return 3;
132   
133 }
134   
135   
136   
137 @Override
138   
139 public Object getItem(int arg0) {
140   
141 // TODO Auto-generated method stub
142   
143 return listString.get(arg0);
144   
145 }
146   
147   
148   
149 @Override
150   
151 public long getItemId(int position) {
152   
153 // TODO Auto-generated method stub
154   
155 return position;
156   
157 }
158   
159   
160   
161 @Override
162   
163 public View getView(int position, View convertView, ViewGroup parent) {
164   
165 // TODO Auto-generated method stub
166   
167 viewHolder1 holder1 = null;
168   
169 viewHolder2 holder2 = null;
170   
171 viewHolder3 holder3 = null;
172   
173 int type = getItemViewType(position);
174   
175   
176   
177   
178   
179 //无convertView,需要new出各个控件
180   
181 if(convertView == null)
182   
183
184   
185 Log.e("convertView = ", " NULL");
186   
187   
188   
189 //按当前所需的样式,确定new的布局
190   
191 switch(type)
192   
193 {
194   
195 case TYPE_1:
196   
197 convertView = inflater.inflate(R.layout.listitem1, parent, false);
198   
199 holder1 = new viewHolder1();
200   
201 holder1.textView = (TextView)convertView.findViewById(R.id.textview1);
202   
203 holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox);
204   
205 Log.e("convertView = ", "NULL TYPE_1");
206   
207 convertView.setTag(holder1);
208   
209 break;
210   
211 case TYPE_2:
212   
213 convertView = inflater.inflate(R.layout.listitem2, parent, false);
214   
215 holder2 = new viewHolder2();
216   
217 holder2.textView = (TextView)convertView.findViewById(R.id.textview2);
218   
219 Log.e("convertView = ", "NULL TYPE_2");
220   
221 convertView.setTag(holder2);
222   
223 break;
224   
225 case TYPE_3:
226   
227 convertView = inflater.inflate(R.layout.listitem3, parent, false);
228   
229 holder3 = new viewHolder3();
230   
231 holder3.textView = (TextView)convertView.findViewById(R.id.textview3);
232   
233 holder3.imageView = (ImageView)convertView.findViewById(R.id.imageview);
234   
235 Log.e("convertView = ", "NULL TYPE_3");
236   
237 convertView.setTag(holder3);
238   
239 break;
240   
241 }
242   
243 }
244   
245 else
246   
247 {
248   
249 //有convertView,按样式,取得不用的布局
250   
251 switch(type)
252   
253 {
254   
255 case TYPE_1:
256   
257 holder1 = (viewHolder1) convertView.getTag();
258   
259 Log.e("convertView !!!!!!= ", "NULL TYPE_1");
260   
261 break;
262   
263 case TYPE_2:
264   
265 holder2 = (viewHolder2) convertView.getTag();
266   
267 Log.e("convertView !!!!!!= ", "NULL TYPE_2");
268   
269 break;
270   
271 case TYPE_3:
272   
273 holder3 = (viewHolder3) convertView.getTag();
274   
275 Log.e("convertView !!!!!!= ", "NULL TYPE_3");
276   
277 break;
278   
279 }
280   
281 }
282   
283   
284   
285 //设置资源
286   
287 switch(type)
288   
289 {
290   
291 case TYPE_1:
292   
293 holder1.textView.setText(Integer.toString(position));
294   
295 holder1.checkBox.setChecked(true);
296   
297 break;
298   
299 case TYPE_2:
300   
301 holder2.textView.setText(Integer.toString(position));
302   
303 break;
304   
305 case TYPE_3:
306   
307 holder3.textView.setText(Integer.toString(position));
308   
309 holder3.imageView.setBackgroundResource(R.drawable.icon);
310   
311 break;
312   
313 }
314   
315   
316   
317   
318   
319 return convertView;
320   
321 }
322   
323   
324   
325 }
326   
327   
328   
329   
330   
331 //各个布局的控件资源
332   
333   
334   
335 class viewHolder1{
336   
337 CheckBox checkBox;
338   
339 TextView textView;
340   
341 }
342   
343 class viewHolder2{
344   
345 TextView textView;
346   
347 }
348   
349 class viewHolder3{
350   
351 ImageView imageView;
352   
353 TextView textView;
354   
355 }
356   
357 }



 

     在getView()中需要将不同布局进行缓存和适配,系统在判断是否有convertView时,会自动去调用getItemViewType (int position) ,查看是否已经有缓存的该类型的布局,从而进入if(convertView == null)和else{}的判断。期间需要做的是convertView.setTag(holder3),以便在convertView重用时可以直接拿到该布局的控件,holder3 = (viewHolder3) convertView.getTag()。到这一步,convertView的回收和重用就已经写好了,接下来只需要对你的不同的控件进行设置就行了。

缓存及应用,并且和大家分享

构造Adapter时,没有使用缓存的 convertView,导致内存泄露

示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = new Xxx(...);
  ... ...
  return view;
}


描述:
  
以构造ListViewBaseAdapter为例,在BaseAdapter中提供了方法:



public View getView(int position, View convertView, ViewGroup parent){ }


来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list itemview对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list itemview对象(初始化时缓存中没有view对象则convertViewnull)
  
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。

修正示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = null;
  if (convertView != null) {
  view = convertView;
  ...
  } else {
  view = new Xxx(...);
  ...
  }
  return view;
}

上述代码很好的解决了内存泄露的问题,使用convertView回收一些布局供下面重构是使用。

但是如果出现如下图的需求,convertView就不太好用了,convertViewItem为单一的布局时,能够回收并重用,但是多个Item布局时,convertView的回收和重用会出现问题。


无标题.png 


Listview中有3Item布局,即使convertView缓存了一些布局,但是在重构时,根本不知道怎么样去让convertView返回你所需要的布局,这时你需要让adapter知道我当前有哪些布局,我重构Item时的布局选取规则,好让convertView能返回你需要的布局

需要重写一下两个函数

@Override

public int getItemViewType(int position) {}

官网解释如下,不解释了
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)] getView(int, View, ViewGroup) for the specified item.

Parameters
position The position of the item within the adapter's data set whose view type we want.

Returns


  • An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.



@Override

public int getViewTypeCount() {}


Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup) getView(int, View, ViewGroup) for the specified item.

Parameters
position The position of the item within the adapter's data set whose view type we want.

Returns


  • An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.




上述两个函数的作用这如它的名字,得到Item的样式,得到所有的样式数量

下面直接上代码,就是上图的实现代码:



001 package com.bestv.listViewTest;
002 import java.util.ArrayList;
003 import android.app.Activity;
004 import android.content.Context;
005 import android.os.Bundle;
006 import android.util.Log;
007 import android.view.LayoutInflater;
008 import android.view.View;
009 import android.view.ViewGroup;
010 import android.widget.BaseAdapter;
011 import android.widget.CheckBox;
012 import android.widget.ImageView;
013 import android.widget.LinearLayout;
014 import android.widget.ListView;
015 import android.widget.TextView;
016   
017 public class listViewTest extends Activity {
018   
019 /** Called when the activity is first created. */
020 ListView listView;
021 MyAdapter listAdapter;
022 ArrayList<String> listString;
023   
024 @Override
025 public void onCreate(Bundle savedInstanceState) {
026 super.onCreate(savedInstanceState);
027 setContentView(R.layout.main);
028 listView = (ListView)this.findViewById(R.id.listview);
029 listString = new ArrayList<String>();
030   
031 for(int i = 0 ; i < 100 ; i++)
032   
033 {
034   
035 listString.add(Integer.toString(i));
036   
037 }
038   
039 listAdapter = new MyAdapter(this);
040   
041 listView.setAdapter(listAdapter);
042   
043 }
044   
045   
046   
047 class MyAdapter extends BaseAdapter{
048   
049   
050   
051 Context mContext;
052   
053 LinearLayout linearLayout = null;
054   
055 LayoutInflater inflater;
056   
057 TextView tex;
058   
059 final int VIEW_TYPE = 3;
060   
061 final int TYPE_1 = 0;
062   
063 final int TYPE_2 = 1;
064   
065 final int TYPE_3 = 2;
066   
067   
068   
069 public MyAdapter(Context context) {
070   
071 // TODO Auto-generated constructor stub
072   
073 mContext = context;
074   
075 inflater = LayoutInflater.from(mContext);
076   
077 }
078   
079   
080   
081 @Override
082   
083 public int getCount() {
084   
085 // TODO Auto-generated method stub
086   
087 return listString.size();
088   
089 }
090   
091   
092   
093 //每个convert view都会调用此方法,获得当前所需要的view样式
094   
095 @Override
096   
097 public int getItemViewType(int position) {
098   
099 // TODO Auto-generated method stub
100   
101 int p = position%6;
102   
103 if(p == 0)
104   
105 return TYPE_1;
106   
107 else if(p < 3)
108   
109 return TYPE_2;
110   
111 else if(p < 6)
112   
113 return TYPE_3;
114   
115 else
116   
117 return TYPE_1;
118   
119   
120   
121 }
122   
123   
124   
125 @Override
126   
127 public int getViewTypeCount() {
128   
129 // TODO Auto-generated method stub
130   
131 return 3;
132   
133 }
134   
135   
136   
137 @Override
138   
139 public Object getItem(int arg0) {
140   
141 // TODO Auto-generated method stub
142   
143 return listString.get(arg0);
144   
145 }
146   
147   
148   
149 @Override
150   
151 public long getItemId(int position) {
152   
153 // TODO Auto-generated method stub
154   
155 return position;
156   
157 }
158   
159   
160   
161 @Override
162   
163 public View getView(int position, View convertView, ViewGroup parent) {
164   
165 // TODO Auto-generated method stub
166   
167 viewHolder1 holder1 = null;
168   
169 viewHolder2 holder2 = null;
170   
171 viewHolder3 holder3 = null;
172   
173 int type = getItemViewType(position);
174   
175   
176   
177   
178   
179 //无convertView,需要new出各个控件
180   
181 if(convertView == null)
182   
183
184   
185 Log.e("convertView = ", " NULL");
186   
187   
188   
189 //按当前所需的样式,确定new的布局
190   
191 switch(type)
192   
193 {
194   
195 case TYPE_1:
196   
197 convertView = inflater.inflate(R.layout.listitem1, parent, false);
198   
199 holder1 = new viewHolder1();
200   
201 holder1.textView = (TextView)convertView.findViewById(R.id.textview1);
202   
203 holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox);
204   
205 Log.e("convertView = ", "NULL TYPE_1");
206   
207 convertView.setTag(holder1);
208   
209 break;
210   
211 case TYPE_2:
212   
213 convertView = inflater.inflate(R.layout.listitem2, parent, false);
214   
215 holder2 = new viewHolder2();
216   
217 holder2.textView = (TextView)convertView.findViewById(R.id.textview2);
218   
219 Log.e("convertView = ", "NULL TYPE_2");
220   
221 convertView.setTag(holder2);
222   
223 break;
224   
225 case TYPE_3:
226   
227 convertView = inflater.inflate(R.layout.listitem3, parent, false);
228   
229 holder3 = new viewHolder3();
230   
231 holder3.textView = (TextView)convertView.findViewById(R.id.textview3);
232   
233 holder3.imageView = (ImageView)convertView.findViewById(R.id.imageview);
234   
235 Log.e("convertView = ", "NULL TYPE_3");
236   
237 convertView.setTag(holder3);
238   
239 break;
240   
241 }
242   
243 }
244   
245 else
246   
247 {
248   
249 //有convertView,按样式,取得不用的布局
250   
251 switch(type)
252   
253 {
254   
255 case TYPE_1:
256   
257 holder1 = (viewHolder1) convertView.getTag();
258   
259 Log.e("convertView !!!!!!= ", "NULL TYPE_1");
260   
261 break;
262   
263 case TYPE_2:
264   
265 holder2 = (viewHolder2) convertView.getTag();
266   
267 Log.e("convertView !!!!!!= ", "NULL TYPE_2");
268   
269 break;
270   
271 case TYPE_3:
272   
273 holder3 = (viewHolder3) convertView.getTag();
274   
275 Log.e("convertView !!!!!!= ", "NULL TYPE_3");
276   
277 break;
278   
279 }
280   
281 }
282   
283   
284   
285 //设置资源
286   
287 switch(type)
288   
289 {
290   
291 case TYPE_1:
292   
293 holder1.textView.setText(Integer.toString(position));
294   
295 holder1.checkBox.setChecked(true);
296   
297 break;
298   
299 case TYPE_2:
300   
301 holder2.textView.setText(Integer.toString(position));
302   
303 break;
304   
305 case TYPE_3:
306   
307 holder3.textView.setText(Integer.toString(position));
308   
309 holder3.imageView.setBackgroundResource(R.drawable.icon);
310   
311 break;
312   
313 }
314   
315   
316   
317   
318   
319 return convertView;
320   
321 }
322   
323   
324   
325 }
326   
327   
328   
329   
330   
331 //各个布局的控件资源
332   
333   
334   
335 class viewHolder1{
336   
337 CheckBox checkBox;
338   
339 TextView textView;
340   
341 }
342   
343 class viewHolder2{
344   
345 TextView textView;
346   
347 }
348   
349 class viewHolder3{
350   
351 ImageView imageView;
352   
353 TextView textView;
354   
355 }
356   
357 }



 

     在getView()中需要将不同布局进行缓存和适配,系统在判断是否有convertView时,会自动去调用getItemViewType (int position) ,查看是否已经有缓存的该类型的布局,从而进入if(convertView == null)和else{}的判断。期间需要做的是convertView.setTag(holder3),以便在convertView重用时可以直接拿到该布局的控件,holder3 = (viewHolder3) convertView.getTag()。到这一步,convertView的回收和重用就已经写好了,接下来只需要对你的不同的控件进行设置就行了。

转载于:https://my.oschina.net/pangzipig/blog/86388

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值