ListView和性能

来源:互联网 发布:电脑桌面归类软件 编辑:程序博客网 时间:2024/06/17 20:52


一谈起ListView,我想大家都不陌生。而且最近该控件特别红,像QQ,人人和新浪客户端里都有它的影子。

其实实现ListView非常的简单。

我想大家都用过各种各样的控件,比如说一个最简单的TextView,我们都是在布局文件里加入TextView标签,然后在Activity里通过findViewById(int id)方法得到该对象的引用,最后调用TextView类的setText(CharSequence s)方法设置该控件的值。

同样,对于ListView,我们先在布局文件里这样添加标签:


<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <ListView        android:id="@+id/mylist"        android:layout_width="match_parent"        android:layout_height="wrap_content" >    </ListView></LinearLayout>

了布局文件,然后我们在Activity里通过findViewById(int id)方法得到ListView对象的引用


ListView listView = (ListView) findViewById(R.id.mylist);

有了控件还不行,我们可以把ListView看作是一个可以伸缩的容器,我们需要往里添加内容。作为数据传输的桥梁,Adapter封装了所需的数据,通过调用ListView的方法setAdapter(Adapter a)将数据绑定到ListView中,这样屏幕上就有数据显示了。


Adapter是一个接口,定义了许多规范。Android提供了实现该接口的一些方便的类,如ArrayAdapter,CursorAdapter。下面以ArrayAdapter类为例讲解如何创建一个Adapter。


String[] values = new String[] { "Android", "iPhone", "WindowsMobile",  "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",  "Linux", "OS/2" };// First paramenter - Context// Second parameter - Layout for the row// Third parameter - ID of the TextView to which the data is written// Forth - the Array of dataArrayAdapter<String> adapter = new ArrayAdapter<String>(this,  android.R.layout.simple_list_item_1, android.R.id.text1, values);

是不是很简单?在上面的那个构造方法中,一共有四个参数,第一个参数很简单,就是一个上下文对象Context,第二个参数是描述每一行的布局,这里使用的是Android自带的一个简单布局,第三个参数是该View的id,最后一个是加入的数组。


上面的ArrayAdapter只能在每一行显示一些文本信息,如果想丰富一下,比如增加图片等,就需要继承该类,实现自己的自定义类。


public class MySimpleArrayAdapter extends ArrayAdapter<String> {  private final Context context;  private final String[] values;  public MySimpleArrayAdapter(Context context, String[] values) {    super(context, R.layout.rowlayout, values);    this.context = context;    this.values = values;  }  @Override  public View getView(int position, View convertView, ViewGroup parent) {    LayoutInflater inflater = (LayoutInflater) context        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);    View rowView = inflater.inflate(R.layout.rowlayout, parent, false);    TextView textView = (TextView) rowView.findViewById(R.id.label);    ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);    textView.setText(values[position]);    String s = values[position];    imageView.setImageResource(R.drawable.ok);    return rowView;  }} 

继承该类最关键的就是复写getView()方法,因为ListView是通过该方法得到视图然后显示在屏幕上的。在本方法中,我们自定义了一个XML布局文件,里面有TextView标签和ImageView标签,分别用来显示文字和图片信息,这里是先得到系统服务LayoutInflater,调用该方法的inflate得到该布局的View,最后通过findViewById()方法获取TextView和ImageView的对象引用,再给它们赋值返回就结束了。



但是本章的讨论不是讲解如何实现ListView,但是考虑到有些没有接触过ListView的同志,就大概写了一点demo,同时以此例子为引子,指出该方法存在的一些性能问题。


由于通过调用LayoutInflater的inflate方法获得的View,其实会产生新的对象,创建对象是很耗时和资源的(内存),另外调用getViewById()方法也会相对耗时和耗资源,虽然其强度不如前者。

所以Android决定,如果代表每一行的View不可见(向下滑动,上面的View被遮住了,即为不可见),那么它将允许getView方法通过convertView复用该View,达到提升性能的目的。

我们先来看下ArrayAdapter是如何进行优化的。


 public View getView(int position, View convertView, ViewGroup parent) {        return createViewFromResource(position, convertView, parent, mResource);    }    private View createViewFromResource(int position, View convertView, ViewGroup parent,            int resource) {        View view;        TextView text;        if (convertView == null) {            view = mInflater.inflate(resource, parent, false);        } else {            view = convertView;        }        try {            if (mFieldId == 0) {                //  If no custom field is assigned, assume the whole resource is a TextView                text = (TextView) view;            } else {                //  Otherwise, find the TextView field within the layout                text = (TextView) view.findViewById(mFieldId);            }        } catch (ClassCastException e) {            Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");            throw new IllegalStateException(                    "ArrayAdapter requires the resource ID to be a TextView", e);        }        T item = getItem(position);        if (item instanceof CharSequence) {            text.setText((CharSequence)item);        } else {            text.setText(item.toString());        }        return view;    }

方法首先判断传给该方法的convertView是否为null,如果为null,那么就调用耗时的inflate方法创建View对象,如果不为空(该convertView是以前inflate过的,只不过被遮住了),就复用该对象,达到了部分优化。

上面之所以说是部分优化,是因为只考虑了优化inflate带来的负载,而忽略了getViewById()方法引起的性能问题。解决办法是在自定义Adapter类里引进静态内部类ViewHolder,如其名字,该类里存放我们需要显示每一行的所有控件,比如TextView,ImageView等。当convertView为空时,我们创建布局文件的View,然后分别得到布局里的各种控件,再把它们存放在ViewHolder类里,最后再调用convertView的 setTag(Object o)方法把该类绑定到该类里。


public class MyPerformanceArrayAdapter extends ArrayAdapter<String> {  private final Activity context;  private final String[] names;  static class ViewHolder {    public TextView text;    public ImageView image;  }  public MyPerformanceArrayAdapter(Activity context, String[] names) {    super(context, R.layout.rowlayout, names);    this.context = context;    this.names = names;  }  @Override  public View getView(int position, View convertView, ViewGroup parent) {    View rowView = convertView;    if (rowView == null) {      LayoutInflater inflater = context.getLayoutInflater();      rowView = inflater.inflate(R.layout.rowlayout, null);      ViewHolder viewHolder = new ViewHolder();      viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);      viewHolder.image = (ImageView) rowView          .findViewById(R.id.ImageView01);      rowView.setTag(viewHolder);    }    ViewHolder holder = (ViewHolder) rowView.getTag();    String s = names[position];    holder.text.setText(s);    if (s.startsWith("Windows7") || s.startsWith("iPhone")        || s.startsWith("Solaris")) {      holder.image.setImageResource(R.drawable.no);    } else {      holder.image.setImageResource(R.drawable.ok);    }    return rowView;  }} 

根据统计信息,这样的优化设计,比最初的方法效率上要快15%以上。


Over...

原创粉丝点击