第十五个项目——使用ViewHolder提升ListView显示性能

实验目的

  • 掌握ListViewItem布局加载机制;

  • 掌握View.setTag()View.getTag()读取数据的方式;

实验要求

  • 自定义ViewHolder保存Item布局控件实例

  • 修改NewsAdapter.getView()方法提升Item加载效率;

实验内容

第七个项目中,使用了ListView用于显示新闻列表。为了实现ListView中的Item加载自定义布局,项目中实现了名为NewsAdapter的适配器类,在该类中覆盖了getView()方法。当用户上下滑动ListView时,该方法被调用; 该方法中需要返回当前postion对应的Item布局,原代码中在getView()方法中每次都通过LayoutInflater加载list_item.xml布局资源,并实例化相应的TextViewImageView控件。

假设ListView中的数据源包含成千上万条数据,最极端的情况下,用户滑动ListView到底部,那么App将有上万个TextViewImageView控件的实例,但是ListView能在屏幕上显示的数据远远小于数据源所包含的数据量,这样的做法无疑会降低App的运行效率。有什么办法可以提升ListView加载显示数据的效率?如果当ListView中某一Item滑出屏幕时,新滑入的Item能复用其实例化的布局就可以避免刚才所说的问题。

实际上ListView控件提供了缓存机制,可有效提高Item布局加载效率低下的问题。getView()方法的第二个参数(View convertView)如果不为null则保存了被缓存的Item布局实例。

public class NewsAdapter extends ArrayAdapter<News> {

    ...
    @Override
    public View getView(int position, View convertView, 
                ViewGroup parent) {
        News news = getItem(position);
        View view ;

        view = LayoutInflater.from(getContext())
                    .inflate(resourceId, parent, false);

        TextView tvTitle  = view.findViewById(R.id.tv_title);
        TextView tvAuthor = view.findViewById(R.id.tv_subtitle);
        ImageView ivImage = view.findViewById(R.id.iv_image);

        tvTitle.setText(news.getTitle());
        tvAuthor.setText(news.getAuthor());
        ivImage.setImageResource(news.getImageId());
        return view;
    }
}

步骤一,打开第七个项目

打开第七个项目,并打开文件NewsAdapter.java

步骤二,定义ViewHolder类

NewsAdapter中定义名为ViewHolder的内部类(inner class)ViewHolder中包含了list_item.xml布局中三个控件的定义,其主要作用为保存每一个Item布局中控件的实例。

public class NewsAdapter extends ArrayAdapter<News> {

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

      class ViewHolder {  
        TextView tvTitle;
        TextView tvAuthor;
        ImageView ivImage;
    }
}

步骤三,更改NewsAdapter.getView()方法

NewsApdater适配器类的getView()方法中,通过判断convertView是否为空,可判断ListView是否缓存了Item的布局,如果已缓存则可直接使用,否则需要通过Layoutinflater进行加载。

通过LayoutInflater进行加载的Item布局中的控件实例,通过实例化ViewHolder类对象进行存储, 而通过view.setTag(Object viewHolder)ViewHolder类对象存储于view对象中,ViewHolder对象可通过view.getTag()的方式进行读取即可。具体代码如下所示。

public class NewsAdapter extends ArrayAdapter<News> {

    ...
    @Override
    public View getView(int position, View convertView,
                         ViewGroup parent) {
        News news = getItem(position);
        View view ;

        ViewHolder viewHolder;

        if (convertView == null) {
            view = LayoutInflater.from(getContext())
                      .inflate(resourceId, parent, false);

            viewHolder = new ViewHolder();
            viewHolder.tvTitle  = view.findViewById(R.id.tv_title);
            viewHolder.tvAuthor = view.findViewById(R.id.tv_subtitle);
            viewHolder.ivImage = view.findViewById(R.id.iv_image);

              view.setTag(viewHolder);  
        } else {
            view = convertView;
              viewHolder = (ViewHolder) view.getTag();  
        }

        viewHolder.tvTitle.setText(news.getTitle());
        viewHolder.tvAuthor.setText(news.getAuthor());
        viewHolder.ivImage.setImageResource(news.getImageId());

        return view;
    }

      class ViewHolder \{  
        TextView tvTitle;
        TextView tvAuthor;
        ImageView ivImage;
    }
}

步骤四,编译并运行项目

编译并运行项目,其执行效果与第七个项目相同,你可以为ListView的数据源添加更多的数据对比一下其性能是否有提升。

实验小结

通过本次实验,你应该掌握了如下知识内容:

  • 了解如何提升ListView控件运行性能;

  • 使用ViewHolder存储ListViewItem布局控件;

  • 使用View.setTag()View.setTag()将数据绑定至控件;

Last updated

Was this helpful?