
博客专栏:Android初级入门UI组件与布局
源码:通过网盘分享的文件:Android入门布局及UI相关案例
链接: https://pan.baidu.com/s/1EOuDUKJndMISolieFSvXXg?pwd=4k9n 提取码: 4k9n
引言
在上一篇文章《Android UI 组件系列(八):ListView 基础用法与适配器详解》中,我们学习了如何通过 ArrayAdapter 或 SimpleAdapter 快速构建一个 ListView 列表,并实现了简单的点击事件和图文混排。
这些内容虽然可以满足大多数"原型阶段"或"低频操作"的列表需求,但一旦涉及到:
- 大数据量 的展示;
- 频繁滚动 的交互;
- 自定义复杂布局(如图标、文字、按钮并存);
就会遇到非常明显的卡顿、内存占用高的问题。这背后的关键,其实就是我们常说的 getView() 频繁创建 View 布局而未复用的问题。
🎯 所以本篇的核心目标是:
🧠 搞懂 getView() 的循环机制、掌握 ViewHolder 模式的优化技巧,并学会使用 BaseAdapter 构建高性能的复杂列表 UI。
我们还将简要对比 ListView 与 RecyclerView 在性能、灵活性方面的差异,为后续迁移做好准备。
如果你正在使用 ListView 开发中大型列表页面,又想让滑动丝滑不卡顿,这一篇你一定要看完!
一、getView() 的复用机制
在 ListView 中,getView() 是最核心的性能关键点。每一个列表项在显示时,系统都会回调一次 getView() 方法,由你来负责"返回该位置所需的 View"。
🌀 为什么会卡顿?
如果你在 getView() 中每次都执行:
- LayoutInflater.inflate(...) 创建新 View;
- findViewById(...) 查找子控件;
当列表有几十甚至上百项时,每次滚动都会重复执行这些开销操作 ------ 滑动顿挫、内存抖动,就是这样来的。
🧩 convertView 是什么?
getView() 方法的标准签名是:
Kotlin
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View
其中:
- convertView:系统传进来的 "可复用的旧 View",如果为 null,说明要新创建;不为 null,就可以复用,节省开销;
- parent:当前列表的父容器 ListView 本身;
🚀 标准复用流程
你应该这样使用 convertView 来进行判断和复用:
Kotlin
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View = convertView ?: LayoutInflater.from(context).inflate(R.layout.list_item, parent, false)
val textView = view.findViewById<TextView>(R.id.text_view)
textView.text = data[position]
return view
}
这段代码的意思是:
- 如果 convertView 为 null,就新建一个 View;
- 否则复用旧 View,避免重复构建;
🧠 实际运行时是这样的
用户打开页面:
→ 系统初始化列表前几个 item → getView() 被调用 N 次(初次加载)
用户滑动列表:
→ 顶部 item 滑出屏幕 → 系统将旧 view 传入 convertView
→ getView() 使用 convertView 进行复用(无需新建)
🛠️代码实现如下
我们在textView上来标记处哪些是创建的哪些是复用的。
Kotlin
/// getview() 方法的自定义适配器
private fun setupCustomAdapter() {
val listView = findViewById<ListView>(R.id.list_view)
val data = List(20) { index ->
mapOf("title" to "微信 #$index", "icon" to R.drawable.ic_wechat)
}
val adapter = object : android.widget.BaseAdapter() {
override fun getCount(): Int = data.size
override fun getItem(position: Int): Any = data[position]
override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: android.view.View?, parent: android.view.ViewGroup): android.view.View {
val view = convertView ?: layoutInflater.inflate(R.layout.list_item, parent, false)
val imageView = view.findViewById<android.widget.ImageView>(R.id.image_view)
val textView = view.findViewById<android.widget.TextView>(R.id.text_view)
val item = data[position]
imageView.setImageResource(item["icon"] as Int)
val title = item["title"]?.toString() ?: "未知"
val state = if (convertView == null) " 创建" else " 复用"
textView.text = title + state
return view
}
}
listView.adapter = adapter
}
效果如下:

我们发现只有首次出现的屏幕的上的视图是创建的,而从屏幕外出现的所有视图都是复用的已经创建好的视图。
但我们这一步只解决了视图的重复创建问题,但每次仍然需要执行view.findViewById在视图上来查找UI组件,接下来就是ViewHolder出场的时候了。
二、ViewHolder 的作用与实现
在上一节中我们提到,虽然我们通过 convertView 复用了 item 布局本身,但每次执行 getView() 时,仍然需要重新调用 findViewById() 来查找子控件(如 TextView、ImageView),这是一个相对昂贵的操作。
🎯 问题复盘
Kotlin
val imageView = view.findViewById<ImageView>(R.id.image_view)
val textView = view.findViewById<TextView>(R.id.text_view)
这段代码在滑动过程中会被反复调用,而其实每个 item 的控件结构是固定的,只需要找一遍即可。我们需要一种方式把这些"已经找过的控件"缓存起来。
✅ ViewHolder 是什么?
ViewHolder 本质上是一个静态内部类 ,用来缓存每个 item 布局中的子控件引用,避免每次滑动都调用 findViewById()。
它通常搭配 setTag() / getTag() 使用,在首次加载时创建 ViewHolder 并绑定,在复用时直接取出使用。
🧩 ViewHolder 的标准用法
Kotlin
class ViewHolder(val imageView: ImageView, val textView: TextView)
然后在 getView() 中这样写:
Kotlin
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View
val holder: ViewHolder
if (convertView == null) {
view = layoutInflater.inflate(R.layout.list_item, parent, false)
val imageView = view.findViewById<ImageView>(R.id.image_view)
val textView = view.findViewById<TextView>(R.id.text_view)
holder = ViewHolder(imageView, textView)
view.tag = holder
} else {
view = convertView
holder = view.tag as ViewHolder
}
val item = data[position]
holder.imageView.setImageResource(item["icon"] as Int)
holder.textView.text = item["title"] as String
return view
}
🚀 优化效果
使用 ViewHolder 后:
- findViewById() 只执行一次;
- 后续滑动时直接复用已有控件;
- 滑动更流畅,卡顿概率显著降低;
- 是开发中必须掌握的基础优化技巧。
三、使用 BaseAdapter 自定义复杂布局
我们准备展示一个"新闻卡片"列表项,包含以下字段:
- 新闻封面图(图片)
- 新闻标题(内容)
- 发布时间(日期)
- 点赞图标(根据点赞状态变化)
✅ 第一步:定义数据模型
包含标题、日期、图片,点赞状态。
Kotlin
data class NewsItem(
val title: String,
val date: String,
val imageResId: Int,
var liked: Boolean = false
)
✅ 第二步:设计布局 news_item.xml
放在 res/layout/news_item.xml,示例结构如下:
XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:background="@android:color/white">
<ImageView
android:id="@+id/image_cover"
android:layout_width="match_parent"
android:layout_height="180dp"
android:scaleType="centerCrop"
android:src="@drawable/news_placeholder" />
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#000"
android:textStyle="bold"
android:paddingTop="8dp"
android:text="新闻标题" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="space_between"
android:paddingTop="4dp">
<TextView
android:id="@+id/text_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2025-07-23"
android:textColor="#888"
android:textSize="14sp" />
<ImageView
android:id="@+id/image_like"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_like_off" />
</LinearLayout>
</LinearLayout>
✅ 第三步:准备数据源
你可以在 setupNewsAdapter() 中构造 10 条 NewsItem 模拟数据:
Kotlin
val newsList = List(10) { index ->
NewsItem(
title = "这是第 $index 条新闻内容",
date = "2025-07-23",
imageResId = R.drawable.news_placeholder,
liked = index % 2 == 0 // 偶数默认已点赞
)
}
**✅**第四步:实现列表
Kotlin
/// 使用 BaseAdapter 展示新闻卡片列表
private fun setupNewsAdapter() {
val listView = findViewById<ListView>(R.id.list_view)
// 模拟新闻数据
val newsList = List(10) { index ->
NewsItem(
title = "这是第 $index 条新闻内容",
date = "2025-07-23",
imageResId = R.drawable.news_placeholder,
liked = index % 2 == 0
)
}
val adapter = object : android.widget.BaseAdapter() {
override fun getCount(): Int = newsList.size
override fun getItem(position: Int): Any = newsList[position]
override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: android.view.View?, parent: android.view.ViewGroup): android.view.View {
val view: android.view.View
val holder: NewsViewHolder
if (convertView == null) {
view = layoutInflater.inflate(R.layout.news_item, parent, false)
val imageView = view.findViewById<android.widget.ImageView>(R.id.image_cover)
val titleView = view.findViewById<android.widget.TextView>(R.id.text_title)
val dateView = view.findViewById<android.widget.TextView>(R.id.text_date)
val likeView = view.findViewById<android.widget.ImageView>(R.id.image_like)
holder = NewsViewHolder(imageView, titleView, dateView, likeView)
view.tag = holder
} else {
view = convertView
holder = view.tag as NewsViewHolder
}
val item = newsList[position]
holder.imageView.setImageResource(item.imageResId)
holder.titleView.text = item.title
holder.dateView.text = item.date
holder.likeView.setImageResource(
if (item.liked) R.drawable.ic_like_on else R.drawable.ic_like_off
)
return view
}
}
listView.adapter = adapter
}
其中NewsViewHolder实现如下:
Kotlin
private class NewsViewHolder(
val imageView: android.widget.ImageView,
val titleView: android.widget.TextView,
val dateView: android.widget.TextView,
val likeView: android.widget.ImageView
)
最终效果如下:

📌 结语
通过本篇内容,我们围绕 ListView 的性能优化做了逐步深入:
-
✅ 了解了 getView() 的调用机制与 convertView 的复用原理;
-
✅ 学会了使用 ViewHolder 缓存子控件,避免重复调用 findViewById();
-
✅ 使用 BaseAdapter 构建了一个图文混排的"新闻卡片"列表,完整演示了高性能 ListView 的实现方式。
🎯 为什么我们还要学 ListView?
虽然 RecyclerView 已成为 Android 的主流列表组件,但理解 ListView 的机制依然非常关键:
- 很多老项目仍在使用 ListView,维护时需要具备优化能力;
- RecyclerView 中的 ViewHolder、回收机制,其设计理念本就源于 ListView 的优化实践;
- 对初学者来说,ListView 是入门列表原理、掌握 Adapter 模型的绝佳起点。
⏭️ 再进一步:迈向 RecyclerView
相比之下,RecyclerView 提供了更强的扩展能力:
- 更灵活的布局控制(线性、网格、瀑布流等);
- 内置高效的 ViewHolder 机制;
- 支持动画、分页加载、拖拽排序等高级特性;
- 官方已将其作为列表类组件的首选方案。