Android中ListView的几种常见的优化方法

Android中的ListView应该算是布局中几种最常用的组件之一了,使用也十分方便,下面将介绍ListView几种比较常见的优化方法:

首先我们给出一个没有任何优化的Listview的Adapter类,我们这里都继承自BaseAdapter,这里我们使用一个包含100个字符串的List集合来作为ListView的项目所要显示的内容,每一个条目都是一个自定义的组件,这个组件中只包含一个textview:


Activity:

package com.alexchen.listviewoptimize;

import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

  private ListView lv_demo;
  private List<String> list;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    lv_demo = (ListView) findViewById(R.id.lv_demo);
    //list为要加载的条目文本的集合,这里总共是100条
    list = new ArrayList<String>();
    for (int i = 0; i < 100; i++) {
      list.add("条目" + i);
    }

    lv_demo.setAdapter(new MyAdapter());
  }

  private class MyAdapter extends BaseAdapter {

    @Override
    public int getCount() {
      return list.size();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      //listview_item里只有一个textview
      View view = View.inflate(MainActivity.this, R.layout.listview_item,
          null);
      //使用每一次都findviewById的方法来获得listview_item内部的组件
      TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
      tv_item.setText(list.get(position));
      return view;
    }

    @Override
    public Object getItem(int position) {
      return null;
    }

    @Override
    public long getItemId(int position) {
      return 0;
    }

  }
}

优化一:

也是最普通的优化,就在MyAdapter类中的getView方法中,我们注意到,上面的写法每次需要一个View对象时,都是去重新inflate一个View出来返回去,没有实现View对象的复用,而实际上对于ListView而言,只需要保留能够显示的最大个数的view即可,其他新的view可以通过复用的方式使用消失的条目的view,而getView方法里也提供了一个参数:convertView,这个就代表着可以复用的view对象,当然这个对象也可能为空,当它为空的时候,表示该条目view第一次创建,所以我们需要inflate一个view出来,所以在这里,我们使用下面这种方式来重写getView方法:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
      View view;
      // 判断convertView的状态,来达到复用效果
      if (null == convertView) {
        //如果convertView为空,则表示第一次显示该条目,需要创建一个view
        view = View.inflate(MainActivity.this, R.layout.listview_item,
            null);
      } else {
        //否则表示可以复用convertView
        view = convertView;
      }
      // listview_item里只有一个textview
      TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
      tv_item.setText(list.get(position));
      return view;
    }

优化二:

上面是对view对象的复用做的优化,我们经过上面的优化之后,我们不需要每一个view都重新生成了。下面我们来解决下一个每一次都需要做的工作,那就是view中组件的查找:

TextView tv_item = (TextView) view.findViewById(R.id.tv_item);

实际上,findViewById是到xml文件中去查找对应的id,可以想象如果组件多的话也是挺费事的,如果我们可以让view内的组件也随着view的复用而复用,那该是多美好的一件事啊。。实际上谷歌也推荐了一种优化方法来做应对,那就是重新建一个内部静态类,里面的成员变量跟view中所包含的组件个数类型相同,我们这里的view只包含了一个TextView,所以我们的这个静态类如下:

private static class ViewHolder {
    private TextView tvHolder;
  }

那么这个viewHolder类我们要如何使用才可以达到复用效果呢?基本思路就是在convertView为null的时候,我们不仅重新inflate出来一个view,并且还需要进行findviewbyId的查找工作,但是同时我们还需要获取一个ViewHolder类的对象,并将findviewById的结果赋值给ViewHolder中对应的成员变量。最后将holder对象与该view对象“绑”在一块。

当convertView不为null时,我们让view=converView,同时取出这个view对应的holder对象,就获得了这个view对象中的TextView组件,它就是holder中的成员变量,这样在复用的时候,我们就不需要再去findViewById了,只需要在最开始的时候进行数次查找工作就可以了。这里的关键在于如何将view与holder对象进行绑定,那么就需要用到两个方法:setTag和getTag方法了:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
      View view;
      ViewHolder holder;
      // 判断convertView的状态,来达到复用效果
      if (null == convertView) {
        // 如果convertView为空,则表示第一次显示该条目,需要创建一个view
        view = View.inflate(MainActivity.this, R.layout.listview_item,
            null);
        //新建一个viewholder对象
        holder = new ViewHolder();
        //将findviewbyID的结果赋值给holder对应的成员变量
        holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);
        // 将holder与view进行绑定
        view.setTag(holder);
      } else {
        // 否则表示可以复用convertView
        view = convertView;
        holder = (ViewHolder) view.getTag();
      }
      // 直接操作holder中的成员变量即可,不需要每次都findViewById
      holder.tvHolder.setText(list.get(position));
      return view;
    }

经过上面的做法,可能大家感觉不太到优化的效果,根据Google的文档,实际优化效果在百分之5左右。

优化三:

上面的两个例子中ListView都是显示的本地的List集合中的内容,List的长度也只有100个,我们可以毫不费力一次性加载完这100个数据;但是实际应用中,我们往往会需要使用Listview来显示网络上的内容,比如说我们拿使用ListView显示新闻为例:

其一:假如网络情况很好,我们使用的手机也许能够一下子加载完所有新闻数据,然后显示在ListView中,用户可能感觉还好,假如说在网络不太顺畅的情况下,用户加载完所有网络的数据,可能这个list是1000条新闻,那么用户可能需要面对一个空白的Activity好几分钟,这个显然是不合适的

其二:我们知道Android虚拟机给每个应用分配的运行时内存是一定的,一般性能不太好的机器只有16M,好一点的可能也就是64M的样子,假如说我们现在要浏览的新闻总数为一万条,即便是网络很好的情况下,我们可以很快的加载完毕,但是多数情况下也会出现内存溢出从而导致应用崩溃的情况。

那么为了解决上面的两个问题,我们需要进行分批加载,比如说1000条新闻的List集合,我们一次加载20条,等到用户翻页到底部的时候,我们再添加下面的20条到List中,再使用Adapter刷新ListView,这样用户一次只需要等待20条数据的传输时间,不需要一次等待好几分钟把数据都加载完再在ListView上显示。其次这样也可以缓解很多条新闻一次加载进行产生OOM应用崩溃的情况。

实际上,分批加载也不能完全解决问题,因为虽然我们在分批中一次只增加20条数据到List集合中,然后再刷新到ListView中去,假如有10万条数据,如果我们顺利读到最后这个List集合中还是会累积海量条数的数据,还是可能会造成OOM的情况,这时候我们就需要用到分页,比如说我们将这10万条数据分为1000页,每一页100条数据,每一页加载时都覆盖掉上一页中List集合中的内容,然后每一页内再使用分批加载,这样用户的体验就会相对好一些。


 前段时间做的新浪微博项目一直想实现listview分页加载数据,今天终于实现了,哈哈!感觉挺好的,今天又写了个demo给大家分享下。

              首先说下listview的优化方案,这也是面试中常考的题目。优化方案有三种:1,如果自定义适配器,那么在getView方法中判断contentView是否为空,如果为空创建contentView并返回,如果不为空直接返回contentView。这样能尽可能少创建view。2.给contentView设置tag,传入一个viewHoder对象,用于缓存要实现的数据。3,如果listview中显示的item太多,就要考虑分页加载了。

               下面就注意介绍一下分页加载数据。首先在layout下创建listview.xml:

           

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <ListView  
  8.         android:id="@+id/listView1"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content" >  
  11.     </ListView>  
  12.   
  13. </LinearLayout>  
          然后创建listview_item.xml:

          

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
  3.     android:orientation="vertical"    
  4.     android:layout_width="fill_parent"    
  5.     android:layout_height="fill_parent">    
  6.     <TextView    
  7.         android:id="@+id/list_item_text"    
  8.         android:layout_width="fill_parent"    
  9.         android:layout_height="fill_parent"    
  10.         android:gravity="center"    
  11.         android:textSize="20sp"    
  12.         android:paddingTop="10dp"    
  13.         android:paddingBottom="10dp"/>    
  14. </LinearLayout>    

           再为跟多按钮添加一个xml:

      

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="wrap_content"  
  5.     android:gravity="center"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <Button  
  9.         android:id="@+id/loadMoreButton"  
  10.         android:layout_width="fill_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:onClick="loadMore"  
  13.         android:text="加载更多" />  
  14.   
  15. </LinearLayout>  
      

           代码部分:

       

[html]  view plain  copy
  1. public class ListViewAdapter extends BaseAdapter {  
  2.   
  3.     private static Map<Integer,View> m=new HashMap<Integer,View>();  
  4.       
  5.     private List<String> items;  
  6.     private LayoutInflater inflater;  
  7.       
  8.     public ListViewAdapter(List<String> items, Context context) {  
  9.         super();  
  10.         this.items = items;  
  11.         this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  12.     }  
  13.   
  14.     @Override  
  15.     public int getCount() {  
  16.         // TODO Auto-generated method stub  
  17.         return items.size();  
  18.     }  
  19.   
  20.     @Override  
  21.     public Object getItem(int position) {  
  22.         // TODO Auto-generated method stub  
  23.         return items.get(position);  
  24.     }  
  25.   
  26.     @Override  
  27.     public long getItemId(int position) {  
  28.         // TODO Auto-generated method stub  
  29.         return position;  
  30.     }  
  31.   
  32.     @Override  
  33.     public View getView(int position, View contentView, ViewGroup arg2) {  
  34.         // TODO Auto-generated method stub  
  35.         contentView=m.get(position);  
  36.         if(contentView==null){  
  37.             contentView=inflater.inflate(R.layout.listview_item, null);  
  38.             TextView text=(TextView) contentView.findViewById(R.id.list_item_text);  
  39.             text.setText(items.get(position));  
  40.         }  
  41.         m.put(position, contentView);  
  42.         return contentView;  
  43.     }  
  44.       
  45.     public void addItem(String item) {    
  46.         items.add(item);    
  47.     }    
  48.   
  49. }  

         
[html]  view plain  copy
  1. public class ListViewActivity extends Activity implements OnScrollListener  {  
  2.       List<String> items = new ArrayList<String>();    
  3.      private ListView listView;    
  4.         private int visibleLastIndex = 0;   //最后的可视项索引    
  5.         private int visibleItemCount;       // 当前窗口可见项总数    
  6.         private ListViewAdapter adapter;    
  7.         private View loadMoreView;    
  8.         private Button loadMoreButton;    
  9.         private Handler handler = new Handler();    
  10.         
  11.         @Override    
  12.         public void onCreate(Bundle savedInstanceState) {    
  13.             super.onCreate(savedInstanceState);    
  14.             setContentView(R.layout.listview);    
  15.                 
  16.             loadMoreView = getLayoutInflater().inflate(R.layout.load_more, null);    
  17.             loadMoreButton = (Button) loadMoreView.findViewById(R.id.loadMoreButton);    
  18.             loadMoreButton.setOnClickListener(new OnClickListener() {  
  19.                   
  20.                 @Override  
  21.                 public void onClick(View v) {  
  22.                     // TODO Auto-generated method stub  
  23.                     loadMoreButton.setText("正在加载...");   //设置按钮文字loading    
  24.                     handler.postDelayed(new Runnable() {    
  25.                         @Override    
  26.                         public void run() {    
  27.                                 
  28.                             loadData();    
  29.                                 
  30.                             adapter.notifyDataSetChanged(); //数据集变化后,通知adapter    
  31.                             listView.setSelection(visibleLastIndex - visibleItemCount + 1); //设置选中项    
  32.                                 
  33.                             loadMoreButton.setText("加载更多");    //恢复按钮文字    
  34.                         }    
  35.                     }, 1000);    
  36.                 }  
  37.             });  
  38.             listView = (ListView) this.findViewById(R.id.listView1);  
  39.                 
  40.             listView.addFooterView(loadMoreView);   //设置列表底部视图    
  41.            // listView.addHeaderView(v)    //设置列表顶部视图  
  42.         
  43.             initAdapter();    
  44.                 
  45.             listView.setAdapter(adapter);                //自动为id是list的ListView设置适配器    
  46.                 
  47.             listView.setOnScrollListener(this);     //添加滑动监听    
  48.             listView.setOnItemClickListener(new OnItemClickListener() {  
  49.   
  50.                 @Override  
  51.                 public void onItemClick(AdapterView<?> arg0, View view,  
  52.                         int position, long arg3) {  
  53.                     // TODO Auto-generated method stub  
  54.                     Toast.makeText(getApplicationContext(), items.get(position),Toast.LENGTH_SHORT).show();  
  55.                 }  
  56.             });  
  57.         }    
  58.             
  59.         /**   
  60.          * 初始化适配器   
  61.          */    
  62.         private void initAdapter() {    
  63.            
  64.             for (int i = 0; i < 20; i++) {    
  65.                 items.add(String.valueOf(i + 1));    
  66.             }    
  67.             adapter = new ListViewAdapter(items,this);    
  68.         }    
  69.         
  70.         /**   
  71.          * 滑动时被调用   
  72.          */    
  73.         @Override    
  74.         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    
  75.             this.visibleItemCount = visibleItemCount;    
  76.             visibleLastIndex = firstVisibleItem + visibleItemCount - 1;    
  77.         }    
  78.         
  79.         /**   
  80.          * 滑动状态改变时被调用   
  81.          */    
  82.         @Override    
  83.         public void onScrollStateChanged(AbsListView view, int scrollState) {    
  84.             int itemsLastIndex = adapter.getCount() - 1;    //数据集最后一项的索引    
  85.             int lastIndex = itemsLastIndex + 1;             //加上底部的loadMoreView项    
  86.             if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && visibleLastIndex == lastIndex) {    
  87.                 //如果是自动加载,可以在这里放置异步加载数据的代码    
  88.                 Log.i("LOADMORE", "loading...");    
  89.             }    
  90.         }    
  91.             
  92.   
  93.         /**   
  94.          * 模拟加载数据   
  95.          */    
  96.         private void loadData() {    
  97.             int count = adapter.getCount();    
  98.             for (int i = count; i < count + 20; i++) {    
  99.                 adapter.addItem(String.valueOf(i + 1));    
  100.             }    
  101.         }  
  102.   
  103.           
  104. }  

           最后看看效果:

         

       



        

        

        


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值