最近在做android相关的开发,ListView中有一个图片错位的问题,今天查了很多人写的一些东西,所以记录下来,算是一种加深理解吧。

ListView是一个非常常用的控件,功能可以扩展的很丰富,而且与GridView有很多相似的地方。都可以存放大量数据。而且当我们需要比较复杂的布局时,一般用SimpleAdapter,或者继承BaseAdapter自己重写。

如果是继承ArrayAdapter,SimpleAdapter的时候,由于父类本身维护了一个List,所以当有数据更新的时候,尽量用adapter的add,这样可以保证getCount()返回的值是正确。

这里面涉及到的一个比较重要的重写函数就是getView

在getView中有三种方式实现view的返回。

第一种方式:

最简单也是最容易理解的是每一个view都通过inflate生成一个新的view进行返回

public View getView(int position, View convertView, ViewGroup parent) {        LayoutInflater inflater = LayoutInflater.from(context);         View item = inflater.inflate(R.layout.list_item_icon_text, null);     ((TextView) item.findViewById(R.id.text)).setText(DATA[position]);     ((ImageView) item.findViewById(R.id.icon)).setImageBitmap(      return item;}

这种方式,在数据量小的时候,劣势不明显,但是当一个listview里面有大量条目时,这种方式就显得非常浪费。因为每一次item的出现,消失或者更新时,都需要重新inflate。给内存造成了很大的消耗。

那么还有什么方式可以节省开销呢?

第二种方式:

我们看看getView函数里面conertview的是干啥的。查看官方文档后发现:

convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see Adapter.getViewTypeCount() and Adapter.getItemViewType(int)).

注:

getViewTypeCount()是用来当listview中有不同的种类的item,比如分割线之类的时候,用getItemViewType返回某个item的类型,然后坐不同的


原来converView是用来重用view的。经过查找别的资料。找到了一张比较经典的图如下:

这张图清楚的描述了listview是如何重用view的。listview通过getview分别请求所有可见项目。此时converview是空的。

当item1滑动出屏幕后,item8从下面滑动出来,此时又要调用getview,但是此时的converview已经不再为空了,而是上次一划出屏幕的item1,此时我们只需要重新修改下item1的数据,而不必重新创建一个新的view。

这样就节省了内存。

那么由此我们得到了第二种方式

public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) {   convertView = mInflater.inflate(R.layout.item, parent, false); }   ((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);   ((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap(   (position & 1) == 1 ? mIcon1 : mIcon2);   return convertView; }

这种方式里面,我们通过converview找到对应需要修改的text和ico,然后修改数据,进行返回。

有人会说,上面不已经解决了重用的问题了么,还有第三种方式?

那么我们下面看看google推荐的第三种方式是怎么实现的。

static class ViewHolder {TextView text;ImageView icon;}

这里定义了一个内部静态类,

public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder;  if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.text); holder.icon = (ImageView) convertView.findViewById(R.id.icon); convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}holder.text.setText(DATA[position]);holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);return convertView;}

然后用convertView.setTag(holder)的方式将之前通过findViewById找到的textview与ImageView放入converivew对象中。

这样当再次重用converview的时候,就不必再次利用findViewById来查找了,省掉了开销。

最后引用别人的一张图,来说明三种方式的性能。

写了这么多,还有个小问题需要解决:

当我们是异步加载图片等资源的时候,由于下载速度不同,所以重用view之后,可能会造成图片的错乱。

举个简单例子:

假如上面item1和item8两个图片下载速度不同,item8下载比较快。

那么当item1划出屏幕,item8出现的时候,item8会先显示下载好的图片,接下来item1也下载好了,于是下载图片的线程又将item1(也就是与item8共用的view)设置为item1下载好的图片。

所以在这里我们必须去标记下,当前显示的是哪个view,于是最简单的方法就是给每一个holder.icon设置一个tag(也就是图片的url),当后台下载完图片后,将holder.icon的tag与url对比,如果不同,就说明不是这个view的图片,那么就不去加载。

更多相关文章

  1. android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(一)
  2. Android(安卓)网络图片加载
  3. android 访问 OData
  4. React Native开发——Image组件
  5. 20172321 2017-2018-2 《程序设计与数据结构》第11周学习总结
  6. Android中Density和Pixel的关系对界面显示的影响
  7. Android(安卓)修改SeekBar样式,打造理想进度条
  8. 【转】Android深入探究笔记之三 -- Intent (隐式意图和显示意图)
  9. Android(安卓)之使用ContentProvider(内容提供者)共享数据

随机推荐

  1. 转-Android(安卓)Studio系列教程六--Grad
  2. Android:interpolator用法
  3. androidSDK无法更新的解决方法之一
  4. Android(安卓)Battery 架构
  5. android全屏去掉title栏的多种实现方法
  6. Android将drawable图像转化为二进制字节
  7. 为什么Android不是GPL许可证?
  8. android中如何获取文件的路径总结
  9. Android前向兼容的几个问题
  10. Android触摸屏幕时间-android学习之旅(三)