ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ListView 与 Adapter 适配器入门:从 ArrayAdapter 到 BaseAdapter 的简单介绍

ListView 与 Adapter 适配器入门:从 ArrayAdapter 到 BaseAdapter 的简单介绍 1. ListView 与 Adapter 到底谁在管数据Android 列表入门第一课ListView 是 Android 里最经典的滚动列表控件它能做什么把一堆结构相同的数据字符串、键值对、自定义对象按行铺开超出屏幕后可以上下滑动。适合谁刚学完 Activity 和基础控件、想搞懂「数据怎么变成一行行界面」的初学者。核心检索词就三个ListView、Adapter、适配器绑定关系。很多人第一次写列表会卡在同一个地方明明把数据塞进了 ListView界面却一片空白。原因几乎都是没理解 Adapter 的角色。你可以把 ListView 想成一个空货架它只负责「摆」和「滚动」完全不知道要摆什么。Adapter 则是理货员它拿着你的数据源按行生成一个个 View再交给 ListView 去展示。没有理货员货架永远是空的。所以绑定关系是这条链数据源 → Adapter → ListView.setAdapter()。数据源可以是 String[]、List、List自定义对象Adapter 决定了每一行长什么样、显示哪些字段。Android 官方提供了多种 Adapter入门阶段最常接触的是 ArrayAdapter、SimpleAdapter、BaseAdapter 这三种它们的能力从弱到强写法从简到繁。我试过把这三个放在同一个项目里对比结论很清晰ArrayAdapter 适合纯文本单行SimpleAdapter 适合「多字段但布局固定」的键值对BaseAdapter 适合自定义对象、要放图片或复杂控件、还要做性能优化的场景。下面按这个顺序逐个拆每个都给可直接复制的布局和代码最后附运行验证步骤。需要说明的是本文所有代码基于 Android Studio 默认工程结构包名用 com.example.listviewdemo 演示你替换成自己的即可。布局文件放在 res/layout 下图片资源放 res/drawable。跑通一个可滚动列表从下一节开始动手。2. 三种 Adapter 的适用场景与差异对照ArrayAdapter、SimpleAdapter、BaseAdapter 怎么选在动手写代码前先把三者的定位讲清楚这样你写的时候知道自己在干什么而不是照抄。ArrayAdapter 是最轻的适配器。它的数据源通常是一个数组或 List每一项是一个对象默认把对象的 toString() 结果塞进一个 TextView。系统自带了几种 item 布局比如 android.R.layout.simple_list_item_1 就是「一行一个 TextView」。它的优点是三行代码就能出列表缺点是只能显示单一文本想加第二个控件就得自己写布局而 ArrayAdapter 对自定义布局的支持比较别扭。SimpleAdapter 的数据源是 List也就是一串键值对。你告诉它用哪些 key 取数据、对应 item 布局里哪些控件 id。它内部帮你完成「取值 → 塞进控件」的映射。优点是布局可以自定义、多字段映射不用写循环缺点是数据类型被限制在 Map 里遇到自定义对象要先转成 Map而且图片这类资源处理起来不直观。BaseAdapter 是抽象基类前面两个其实都继承自它。它要求你重写四个方法getCount()、getItem()、getItemId()、getView()。getCount 告诉 ListView 一共多少行getView 负责「给我第 position 行的 View」。因为完全自己控制所以能放任意控件、绑定任意对象、做 convertView 复用优化。缺点是代码量最大初学者容易在 getView 里踩坑。用一张表对照更直观维度ArrayAdapterSimpleAdapterBaseAdapter数据源数组 / ListList任意 Listitem 布局系统默认或简单自定义自定义完全自定义显示字段单文本多字段键值对任意支持图片不方便不直观支持代码量最少中等最多适用场景纯文字列表固定多字段列表复杂/自定义对象选择逻辑可以记成一句话能简单就别复杂。纯文字用 ArrayAdapter两三个文本字段用 SimpleAdapter要图片、要按钮、要性能优化直接上 BaseAdapter。接下来三节分别给出完整可运行代码。3. ArrayAdapter 与 SimpleAdapter 可复制配置布局 XML 加 Java 适配器代码先建工程然后在 res/layout/activity_main.xml 里放一个 ListView。这个布局三个示例共用直接复制?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical ListView android:idid/lv_id android:layout_widthmatch_parent android:layout_heightmatch_parent / /LinearLayout3.1 ArrayAdapter 示例数据源用字符串数组item 用系统自带的 simple_list_item_1。MainActivity.javapackage com.example.listviewdemo; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private ListView mListView; private String[] data {item_One, item_Two, item_Three, item_Four, item_Five, item_Six, item_Seven, item_Eight, item_Nine, item_Ten}; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView findViewById(R.id.lv_id); // 参数1 上下文参数2 item 布局参数3 数据源 ArrayAdapterString adapter new ArrayAdapter( this, android.R.layout.simple_list_item_1, data); mListView.setAdapter(adapter); } }运行后你会看到十行文字可以上下滑动。这就是最小可用的列表。3.2 SimpleAdapter 示例SimpleAdapter 需要自定义 item 布局因为要放两个 TextView。新建 res/layout/listview_item.xml?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationvertical android:padding12dp TextView android:idid/text_one android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize16sp android:textColor#222222 / TextView android:idid/text_two android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize14sp android:textColor#888888 / /LinearLayoutMainActivity 里构造 ListHashMapString,String再交给 SimpleAdapterpackage com.example.listviewdemo; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView findViewById(R.id.lv_id); ListHashMapString, String data new ArrayList(); for (int i 1; i 18; i) { HashMapString, String map new HashMap(); map.put(key_one, dataOne_ i); map.put(key_two, dataTwo_ i); data.add(map); } // 参数4 是 map 的 key 数组参数5 是对应的控件 id 数组顺序一一对应 SimpleAdapter adapter new SimpleAdapter( this, data, R.layout.listview_item, new String[]{key_one, key_two}, new int[]{R.id.text_one, R.id.text_two}); mListView.setAdapter(adapter); } }注意参数 4 和参数 5 是严格按位置对应的key_one 的值进 text_onekey_two 的值进 text_two。写反了数据就会串位这是新手最常见的错误之一。4. BaseAdapter 自定义适配器完整写法getView 复用与运行验证步骤BaseAdapter 是重点也是面试和实战里用得最多的。我们做一个学生信息列表每行显示姓名、年龄和一张头像。先定义数据类 StudentData.javapackage com.example.listviewdemo; public class StudentData { private String name; private int age; private int photo; public String getName() { return name; } public void setName(String name) { this.name name; } public int getAge() { return age; } public void setAge(int age) { this.age age; } public int getPhoto() { return photo; } public void setPhoto(int photo) { this.photo photo; } }item 布局 listview_item.xml 换成带 ImageView 的版本?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:gravitycenter_vertical android:padding12dp ImageView android:idid/photo_id android:layout_width48dp android:layout_height48dp android:scaleTypecenterCrop / LinearLayout android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:orientationvertical android:layout_marginStart12dp TextView android:idid/text_name android:layout_widthwrap_content android:layout_heightwrap_content android:textSize16sp android:textColor#222222 / TextView android:idid/text_age android:layout_widthwrap_content android:layout_heightwrap_content android:textSize14sp android:textColor#888888 / /LinearLayout /LinearLayout自定义适配器 MyListAdapter.java这里用 convertView 复用避免重复 inflatepackage com.example.listviewdemo; 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; public class MyListAdapter extends BaseAdapter { private ListStudentData mStudentDataList; private LayoutInflater inflater; public MyListAdapter(ListStudentData list, Context context) { this.mStudentDataList list; this.inflater LayoutInflater.from(context); } Override public int getCount() { return mStudentDataList null ? 0 : mStudentDataList.size(); } Override public Object getItem(int position) { return mStudentDataList.get(position); } Override public long getItemId(int position) { return position; } Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView null) { convertView inflater.inflate(R.layout.listview_item, parent, false); holder new ViewHolder(); holder.tvName convertView.findViewById(R.id.text_name); holder.tvAge convertView.findViewById(R.id.text_age); holder.imPhoto convertView.findViewById(R.id.photo_id); convertView.setTag(holder); } else { holder (ViewHolder) convertView.getTag(); } StudentData data mStudentDataList.get(position); holder.tvName.setText(data.getName()); holder.tvAge.setText(age: data.getAge()); holder.imPhoto.setImageResource(data.getPhoto()); return convertView; } static class ViewHolder { TextView tvName; TextView tvAge; ImageView imPhoto; } }MainActivity 里初始化数据并绑定package com.example.listviewdemo; import android.os.Bundle; import android.widget.ListView; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView findViewById(R.id.lv_id); ListStudentData list new ArrayList(); for (int i 1; i 18; i) { StudentData s new StudentData(); s.setName(lum i 号); s.setAge(i); s.setPhoto(R.drawable.lum); // 换成你自己的图片资源名 list.add(s); } MyListAdapter adapter new MyListAdapter(list, this); mListView.setAdapter(adapter); } }运行验证步骤把图片放进 res/drawable 并命名为 lum.png点击 Android Studio 的 Run模拟器启动后应看到 18 行每行有头像、姓名、年龄上下滑动流畅。如果图片没显示先检查资源名拼写和 R.drawable.lum 是否报红。5. 本篇常见报错排查从 NullPointerException 到列表空白逐条对照列表跑不起来报错往往集中在几个固定位置。下面按真实报错对照排查。第一个高频问题是列表一片空白日志没有崩溃。原因通常是 setAdapter 没调用或者数据源 size 为 0。检查你的 getCount() 是否返回了正确数量ArrayAdapter 场景检查数组是否为空。还有一种情况是 ListView 被外层布局高度压成了 0确认 layout_height 是 match_parent 或具体值。第二个是 NullPointerException 指向 getView 里的 findViewById。这几乎都是 convertView 为 null 时忘了 inflate或者 ViewHolder 没 setTag 就直接 getTag。对照上一节代码确保 inflate 的第二个参数传了 parent、第三个参数传 false这样布局的 LayoutParams 才正确。第三个是数据串位滑动后某几行的文字变成别人的。这是 convertView 复用后没有重新 setText 导致的。复用的 View 带着旧数据你必须在 getView 里对每个控件重新赋值不能只在 convertView null 的分支里设置。第四个是 SimpleAdapter 显示不出内容。检查参数 4 的 key 数组和 map 里 put 的 key 是否完全一致大小写都算再检查参数 5 的控件 id 是否存在于 item 布局里。key 对不上取出来就是 null控件自然空白。第五个是图片不显示或报 Resources$NotFoundException。确认图片确实在 res/drawable 下文件名只能是小写字母、数字、下划线不能有大写和横线。R.drawable.lum 要和文件名严格对应。第六个是点击无响应。ListView 的 item 点击需要在 Adapter 的 getView 返回的根布局上可点击如果 item 里有 Button 这类抢占焦点的控件会吃掉点击事件。入门阶段先别在 item 里放 Button或者给 Button 设 focusablefalse。排查顺序建议先看 Logcat 有没有崩溃栈定位到具体行没崩溃就看数据源 size再确认 setAdapter 执行了最后检查布局层级和控件 id。按这个顺序九成问题能自己解决。6. 从跑通到用顺ListView 适配器入门后的下一步三个示例跑通后你已经掌握了 ListView 和 Adapter 的核心绑定关系。接下来可以做的练习把 ArrayAdapter 的数据换成从网络或数据库读出来的 List给 BaseAdapter 的 item 加点击回调用接口把 position 传回 Activity把 convertView 复用逻辑再检查一遍理解为什么它能减少内存抖动。如果你在配置环境或调试适配器时需要查文档、验证模型输出可以用 TaoToken 的模型对话快速问一些 API 用法和报错含义接入文档在 https://taotoken.net/api 有说明API Key 在 https://taotoken.net/api-keys 获取。长期写 Android 项目、需要稳定调用编码模型的可以看 Coding Planhttps://taotoken.net/coding-plan 。这些只是辅助工具核心还是把 getView 的复用逻辑亲手写几遍。最后留一个实用技巧调试 BaseAdapter 时在 getView 第一行打 Log.d(Adapter, position position convertView (convertView null))滑动列表观察日志你能直观看到哪些行复用了旧 View。这个习惯能帮你彻底搞懂 ListView 的回收机制也为以后学 RecyclerView 打好底子。
返回列表