> For the complete documentation index, see [llms.txt](https://xxgqin.gitbook.io/android/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xxgqin.gitbook.io/android/appendix/app01-1.md).

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

## 实验目的

* 掌握**ListView**中**Item**布局加载机制；
* 掌握**View\.setTag()**&#x53CA;**View\.getTag()**&#x8BFB;取数据的方式；

## 实验要求

* 自定义**ViewHolder**保存**Item**布局控件实例
* 修改**NewsAdapter.getView()**&#x65B9;法提升**Item**加载效率；

## 实验内容

在[第七个项目](https://xxgqin.gitbook.io/android/ch02/ch02-4)中，使用了**ListView**用于显示新闻列表。为了实现**ListView**中的**Item**加载自定义布局，项目中实现了名为**NewsAdapter**的适配器类，在该类中覆盖了**getView()**&#x65B9;法。当用户上下滑动**ListView**时，该方法被调用； 该方法中需要返回当前**postion**对应的**Item**布局，原代码中在**getView()**&#x65B9;法中每次都通过**LayoutInflater**加载**list\_item.xml**布局资源，并实例化相应的**TextView**、**ImageView**控件。

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

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

```java
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;
    }
}
```

### 步骤一，打开第七个项目

打开[第七个项目](https://xxgqin.gitbook.io/android/ch02/ch02-4)，并打开文件**NewsAdapter.java**。

### 步骤二，定义ViewHolder类

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

```java
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()**&#x65B9;法中，通过判断**convertView**是否为空，可判断**ListView**是否缓存了**Item**的布局，如果已缓存则可直接使用，否则需要通过**Layoutinflater**进行加载。

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

{% hint style="info" %}
**View\.setTag(Object obj)**&#x65B9;法的参数为**Object**类型，通过该方法可以把任意类型的数据跟当前的**View**对象进行绑定，后续还可以见到更多这类使用方式。

例如删除**ListView**控件中某一个**Item**时，首先将当前**Item**对应数据的**Id**与表示删除图标的**ImageView**控件进行绑定(通过**setTag()**&#x65B9;法)，再实现该**ImageView**控件的**OnClickListener**事件处理器中通过**getTag()**&#x65B9;法取出**Id**，并执行相应的删除操作。 具体详见[第十七个项目](https://xxgqin.gitbook.io/android/appendix/app01-3)。
{% endhint %}

```java
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;
    }
}
```

### 步骤四，编译并运行项目

编译并运行项目，其执行效果与[第七个项目](https://xxgqin.gitbook.io/android/ch02/ch02-4)相同，你可以为**ListView**的数据源添加更多的数据对比一下其性能是否有提升。

## 实验小结

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

* 了解如何提升**ListView**控件运行性能；
* 使用ViewHolder存储**ListView**的**Item**布局控件；
* 使用**View\.setTag()**、**View\.setTag()**&#x5C06;数据绑定至控件；


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xxgqin.gitbook.io/android/appendix/app01-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
