安卓ListView如何优化性能与滚动流畅度?

99ANYc3cd6
预计阅读时长 36 分钟
位置: 首页 安卓 正文

ListView 是 Android 中一个非常经典且重要的控件,它用于垂直滚动地显示一个项目列表,尽管现在更现代的 RecyclerView 已经成为主流,但理解 ListView 的工作原理对于学习 Android UI 开发至关重要,因为 RecyclerView 的许多核心概念(如适配器、视图回收)都源于它。

安卓 listview
(图片来源网络,侵删)

ListView 是什么?

ListView 是一个 ViewGroup,它允许你将一个垂直的、可滚动的项目列表显示在屏幕上,当列表项的数量超出屏幕可见范围时,用户可以上下滚动来查看所有内容。

核心特点:

  • 垂直滚动:默认只能垂直滚动。
  • 数据驱动ListView 本身不存储数据,它需要通过一个“适配器”(Adapter)来获取数据并将其显示出来。
  • 视图回收:这是 ListView 性能优化的关键,它不会为列表中的每一个项目都创建一个 View 对象,而是只创建足够填满屏幕的 View,并在滚动时回收这些视图,然后填充新的数据,从而极大地节省了内存,避免了频繁的 findViewById 和对象创建带来的性能问题。

ListView 的核心三要素

要使用 ListView,你必须理解以下三个核心组件:

  1. ListView (控件本身)

    安卓 listview
    (图片来源网络,侵删)
    • 这是你在 XML 布局文件中声明的 UI 控件。
    • 它负责显示列表,并处理用户的滚动事件。
  2. Adapter (适配器)

    • 角色ListView 和数据源之间的“桥梁”。
    • 作用
      • 从数据源(如数组、ArrayList、数据库等)获取数据。
      • 创建代表列表项的 View
      • 将数据绑定到 View 上,然后将 View 返回给 ListView 进行显示。
    • 常用子类
      • ArrayAdapter:最简单的适配器,用于将数组或 List 中的字符串绑定到 TextView 上。
      • SimpleAdapter:可以将 List<Map<String, ?>> 类型的数据绑定到各种 View(如 ImageView, TextView 等)上。
      • BaseAdapter:最灵活的适配器,你需要继承它并实现四个核心方法,当你需要自定义列表项的复杂布局时,就必须使用它。
  3. 数据源

    • 任何提供数据的集合,
      • String[] (字符串数组)
      • List<String> (字符串列表)
      • List<Map<String, Object>> (键值对列表,常用于 SimpleAdapter)
      • 数据库查询结果

如何使用 ListView(完整步骤)

下面我们通过一个最经典的例子:自定义一个包含图片和文本的列表项,来演示完整的使用流程。

步骤 1:准备数据源

你需要一个数据列表,这里我们创建一个 List<Map<String, Object>>,每个 Map 代表一个列表项,包含“图片”和“标题”信息。

安卓 listview
(图片来源网络,侵删)
// 在 Activity 或 Fragment 中准备数据
private List<Map<String, Object>> getData() {
    List<Map<String, Object>> list = new ArrayList<>();
    Map<String, Object> map;
    for (int i = 1; i <= 20; i++) {
        map = new HashMap<>();
        map.put("image", R.drawable.ic_launcher_foreground); // 使用一个固定的图片资源
        map.put("title", "列表项 " + i);
        list.add(map);
    }
    return list;
}

步骤 2:创建列表项的布局文件

每个列表项长什么样,需要由一个独立的 XML 布局文件来定义,我们创建一个 list_item.xml,里面包含一个 ImageView 和一个 TextView

res/layout/list_item.xml

<?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="wrap_content"
    android:orientation="horizontal"
    android:padding="16dp">
    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/ic_launcher_foreground" />
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="16dp"
        android:text="列表项标题"
        android:textSize="18sp" />
</LinearLayout>

步骤 3:在主布局中添加 ListView

在 Activity 的主布局文件(activity_main.xml)中,添加 ListView 控件。

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

步骤 4:创建自定义适配器

为了使用我们自定义的 list_item.xml 布局,我们需要创建一个继承自 BaseAdapter 的自定义适配器。

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import java.util.Map;
public class MyAdapter extends BaseAdapter {
    private Context context;
    private List<Map<String, Object>> data;
    // 构造函数
    public MyAdapter(Context context, List<Map<String, Object>> data) {
        this.context = context;
        this.data = data;
    }
    // 1. 返回数据源的总数量
    @Override
    public int getCount() {
        return data.size();
    }
    // 2. 返回指定位置的数据项
    @Override
    public Object getItem(int position) {
        return data.get(position);
    }
    // 3. 返回指定位置的数据项的 ID
    @Override
    public long getItemId(int position) {
        return position;
    }
    // 4. **核心方法**:创建并返回指定位置的列表项 View
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 优化:检查 convertView 是否为空,如果不为空,直接复用,避免重复创建 View
        if (convertView == null) {
            // convertView 为空,说明需要创建一个新的 View
            // 使用 LayoutInflater 将 list_item.xml 布局文件转换为 View 对象
            convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        }
        // 从 convertView 中找到子视图
        ImageView ivIcon = convertView.findViewById(R.id.iv_icon);
        TextView tvTitle = convertView.findViewById(R.id.tv_title);
        // 获取当前位置的数据
        Map<String, Object> item = data.get(position);
        // 将数据设置到子视图中
        ivIcon.setImageResource((Integer) item.get("image"));
        tvTitle.setText((String) item.get("title"));
        // 返回填充好数据的 View
        return convertView;
    }
}

步骤 5:在 Activity 中设置 ListView

在你的 MainActivity.java 中,获取 ListView 实例,创建适配器,并将其设置给 ListView

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 1. 找到 ListView 控件
        listView = findViewById(R.id.list_view);
        // 2. 准备数据源
        List<Map<String, Object>> data = getData();
        // 3. 创建适配器
        adapter = new MyAdapter(this, data);
        // 4. 将适配器设置给 ListView
        listView.setAdapter(adapter);
    }
    // 步骤1中定义的 getData() 方法
    private List<Map<String, Object>> getData() {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map;
        for (int i = 1; i <= 20; i++) {
            map = new HashMap<>();
            map.put("image", R.drawable.ic_launcher_foreground);
            map.put("title", "列表项 " + i);
            list.add(map);
        }
        return list;
    }
}

运行代码,你就能看到一个包含图片和文本的列表了。


ListView 的事件处理

我们需要监听列表项的点击事件。

// 在 MainActivity 的 onCreate 方法中,设置适配器之后添加
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // parent: AdapterView (这里是 ListView)
        // view: 被点击的列表项的 View 对象
        // position: 被点击项在列表中的位置 (从 0 开始)
        // id: 被点击项的 ID (通常是 position)
        // 获取被点击项的数据
        Map<String, Object> clickedItem = (Map<String, Object>) parent.getItemAtPosition(position);
        String title = (String) clickedItem.get("title");
        Toast.makeText(MainActivity.this, "你点击了: " + title, Toast.LENGTH_SHORT).show();
    }
});

ListView vs. RecyclerView

特性 ListView RecyclerView
布局管理 只能垂直列表。 支持线性、网格、瀑布流等多种布局。
视图回收 内置,但不可定制。 内置,且高度可定制(通过 LayoutManager)。
动画支持 内置简单的插入/删除动画,但难以自定义。 内置强大的动画 API,可以轻松实现复杂的 item 动画。
ViewHolder模式 不强制,开发者需要手动实现,否则性能会很差。 强制要求RecyclerView.Adapter 要求你必须使用 ViewHolder,这极大地提升了性能和代码整洁度。
用途 简单的、垂直列表。 复杂的、高性能的、可定制的列表或网格。
  • 对于简单的垂直列表,ListView 仍然可以快速实现。
  • 在现代 Android 开发中,强烈推荐使用 RecyclerView,它更灵活、性能更好、可扩展性更强,学习 ListView 是为了更好地理解 RecyclerView 的基础。

性能优化(getView 方法)

ListView 的性能瓶颈几乎都在 getView 方法中,优化 getView 是关键:

  1. 使用 ViewHolder 模式:这是最重要的优化!

    • 问题getView 方法在每次滚动时都会被调用,如果在里面每次都调用 findViewById(),会导致大量的重复查找,非常耗时。
    • 解决方案:创建一个静态的 ViewHolder 内部类,在 convertView 为空时,将找到的子视图(ImageView, TextView 等)缓存在 ViewHolder 对象中,并将 ViewHolder 对象作为 convertViewTag 存储起来,当下次 convertView 被复用时,直接从 Tag 中取出 ViewHolder,从而避免了 findViewById() 的调用。

    优化后的 getView 方法:

    // 在 MyAdapter 类中添加一个静态的 ViewHolder 内部类
    static class ViewHolder {
        ImageView ivIcon;
        TextView tvTitle;
    }
    // 修改 getView 方法
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
            // 创建 ViewHolder 对象
            holder = new ViewHolder();
            // 查找子视图并缓存到 holder 中
            holder.ivIcon = convertView.findViewById(R.id.iv_icon);
            holder.tvTitle = convertView.findViewById(R.id.tv_title);
            // 将 holder 对象存储到 convertView 的 Tag 中
            convertView.setTag(holder);
        } else {
            // convertView 不为空,直接从 Tag 中取出 holder
            holder = (ViewHolder) convertView.getTag();
        }
        // 使用 holder 中的视图来设置数据
        Map<String, Object> item = data.get(position);
        holder.ivIcon.setImageResource((Integer) item.get("image"));
        holder.tvTitle.setText((String) item.get("title"));
        return convertView;
    }
  2. 避免在 getView 中执行耗时操作:不要在 getView 里进行网络请求、复杂的文件读写或数据库查询,这些操作应该在数据获取阶段完成,getView 只负责显示数据。

  3. 合理使用 android:layout_height:确保列表项的高度是固定的(如 wrap_content 或具体数值),避免动态计算高度导致的性能问题。

希望这份详细的指南能帮助你全面理解 Android ListView 的使用!

-- 展开阅读全文 --
头像
苹果手机锁屏密码怎么改?
« 上一篇 01-24
电脑如何重装系统win7?步骤详解在此!
下一篇 » 01-24

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]