ListView控件
listview是 Android 中常用的列表控件,用于垂直滚动显示一组相同或相似结构的条目(如商品列表、联系人列表)。它本身只负责滚动和条目回收,具体每个条目显示什么内容由 适配器(Adapter) 提供。 常用属性如下:
数据适配器(Adapter)
数据适配器是数据与视图-view之间的桥梁,它类似于一个转换器,将复杂的数据转换成用户可以接受的方式进行呈现。
- BaseAdapter BaseAdapter顾名思义是基本的适配器。它实际上是一个抽象类,通常在自定义适配器时会继承BaseAdapter,该类拥有四个抽象方法,根据这几个抽象方法对ListView控件进行数据适配。
- SimpleAdapter SimpleAdapter继承BaseAdapter,实现了BaseAdapter的四个抽象方法并进行封装。
- ArrayAdapter ArrayAdapter也是BaseAdapter的子类,用法与SimpleAdapter类似,开发者只需要在构造方法里面传入相应参数即可。ArrayAdapter通常用于适配TextView控件,ArrayAdapter有多个构造方法。 下面有个案例来演示如何通过ListView控件与数据适配器显示一个商品信息的列表:
这是案例项目的结构
activity_main.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="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:text="购物商城"
android:textSize="18sp"
android:textColor="#FFFFFF"
android:background="#FF8F03"
android:gravity="center"/>
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
- 顶部
TextView:作为标题栏,显示“购物商城”,橙色背景,文字居中。这只是界面的装饰部分,与列表功能无关。
ListView控件:
android:id="@+id/lv":在 Java 代码中通过 findViewById(R.id.lv)获取该控件。android:layout_width="match_parent":宽度填满父容器。android:layout_height="wrap_content":高度根据内部所有条目的总高度自动扩展。 注意:如果条目数量很多,建议改为 match_parent 或固定高度,否则 ListView 会尝试测量所有条目,可能导致性能问题或滚动异常。但在此示例中数据量小,wrap_content 可以工作。
这个布局简单明了:整个屏幕分为标题栏和列表区。
适配器(Adapter)如何将数据填充到 ListView 中
- 数据准备(在 Activity 中)
String[] titles = {"桌子", "苹果", "蛋糕", "线衣", "猕猴桃", "围巾"};
String[] prices = {"1800元", "10元/kg", "300元", "350元", "10元/kg", "280元"};
int[] icons = {R.drawable.table, R.drawable.apple, ...};
2. 适配器的 getView() 方法(关键)
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(MainActivity.this, R.layout.list_item, null);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.title);
holder.price = convertView.findViewById(R.id.price); // 注意:这里是 price,不是 tv_price
holder.iv = convertView.findViewById(R.id.iv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.title.setText(titles[position]);
holder.price.setText(prices[position]);
holder.iv.setImageResource(icons[position]); // 或 setBackgroundResource
return convertView;
}
-
position:当前正在创建的条目索引(0 到 5)。
-
convertView:历史回收的视图,用于复用,提升性能。
-
ViewHolder:缓存子控件引用,避免重复
findViewById。 -
绑定数据:
- 从
titles[position]获取商品名 --> 设置到holder.title。 - 从
prices[position]获取价格字符串 --> 设置到holder.price。 - 从
icons[position]获取图片资源 ID --> 设置到holder.iv。
- 从
- 将适配器设置给 ListView
mListView = findViewById(R.id.lv); MyBaseAdapter mAdapter = new MyBaseAdapter(); mListView.setAdapter(mAdapter);
-
setAdapter() 会让ListView向适配器询问:
- 一共有多少个条目(getCount())--> 6 个。
- 每个条目长什么样(反复调用 getView())-->生成 6 个不同的视图并显示。
RecyclerView控件的使用
- RecyclerView与ListView控件相似,同样是以列表的形式展示数据,并且数据都是通过适配器加载的。
- RecyclerView控件使用的是RecyclerView.Adapter,该数据适配器将BaseAdapter中getView()方法拆分为 onCreateViewHolder()和onBindViewHolder()方法;强制使用ViewHolder类,使代码编写规范化,避免出现初学者写的代码性能不佳的情况,避免了ListView出现的问题。
- RecyclerView控件复用Item对象工作由该控件自己实现。类似convertview的功能已经实现了。
- 通过setItemAnimator()方法为Item添加动画效果。 下面看个事例:
在 Activity 中初始化 RecyclerView:
- 找到控件
- 设置布局管理器(LayoutManager)
- 创建并设置适配器(Adapter)
接着这个适配器HomeAdapter继承了RecyclerView.Adapter,并且重写了三个方法来实现功能,分别是onCreateViewHolder()、onBindViewHolder()、getItemCount()。
- onCreateViewHolder()用于创建列表项视图 当 RecyclerView 需要一个新的列表项视图时调用。只负责 加载布局文件 并创建 ViewHolder对象。
2. onBindViewHolder()这个方法用于绑定数据,将列表中的数据填充到holder所持有的控件上。
3. RecyclerView 通过getItemCount()这个方法知道一共有多少个列表项。
- MyViewHolder这是个内部类,必须继承RecyclerView.ViewHolder,构造函数中通过view.findViewById()找到布局文件中的子控件,并保存起来,避免重复查找。
接下来演示如何使用RecyclerView控件,下面是个仿今日头条的推荐列表界面:
- 创建程序:创建一个名为HeadLine的应用程序,指定包名为cn.edu.headline
- 导入界面图片:将界面需要的图片导入到drawable-hdpi文件夹中
- 添加库文件:添加recyclerview-v7库
- 创建样式:创建文本样式和创建图片样式
- 添加颜色值:添加浅灰色颜色值,添加深灰色颜色值
- 去掉默认标题栏:修改theme属性的值为“@style/Theme.AppCompat.NoActionBar”
- 搭建界面: 1.创建标题栏2.搭建推荐列表界面3.搭建列表条目界面
- 封装实体类:创建NewsBean类,在该类中创建新闻信息属性对应的字段
- 编写推荐列表适配器:1.创建适配器NewsAdapter2.在适配器中加载条目视图、获取条目类型、绑定界面数据、获取条目总数
- 显示列表数据:1.创建setData()方法,将定义的数组中的数据添加到新闻数据集合NewsList中2.将新闻集合数据设置到适配器NewsAdapter中
- 运行结果
1. 创建程序:
项目结构如下:
由style.xml中设置Headline的应用程序
2. 导入界面图片:
3. 添加库文件:
4. 创建样式:
在style.xml中创建样式
代码包含了appTheme,tvStyle,tvInfo,ivImg
- appTheme中定义整个应用的基础视觉主题,其中
parent="Theme.AppCompat.Light.DarkActionBar"继承自 AppCompat的浅色主题,自带深色标题栏。 - tvStyle是文本样式,用于TextView 或继承自 TextView 的控件(如 Button、EditText)的通用样式。注意其中android:gravity="center"表示文字在控件内水平和垂直居中显示。
- tvInfo是信息文本样式,专门用于显示次要信息或辅助文字的 TextView 样式(如时间、标签、说明)。
- tvImg是图片样式,用于ImageView的样式,典型场景是在 LinearLayout 中通过权重分配宽度,同时在 RelativeLayout 中定位到某个控件右侧。注意:android:layout_toRightOf="@id/ll_info":相对布局专用属性,将本控件放置在 ID 为ll_info的视图右侧。
5. 添加颜色值:
color.xml中设置颜色样式
6. 去掉默认标题栏:
7. 搭建界面:
首先是标题栏,创建一个独立的布局文件title_bar.xml,在复用到当前界面。 整体高度固定在50dp,内边距左右各留10dp,背景是深红色。左侧标题文字(TextView)垂直居中,字体在22sp。右侧搜索框(EditText) 在主布局中,通过"include layout="@layout/title_bar"引入。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#d33d3c" //深红色
android:orientation="horizontal"
android:paddingLeft="10dp" //内边距
android:paddingRight="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="仿今日头条"
android:textColor="@android:color/white"
android:textSize="22sp" />
<EditText
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_gravity="center_vertical" //在父容器中垂直居中
android:layout_marginStart="15dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="15dp"
android:background="@drawable/search_bg" //引用一个自定义 Drawable资源
android:gravity="center_vertical" //输入框内文字在竖直方向居中
android:textColor="@android:color/black"
android:hint="搜你想搜的" //灰色提示文字,引导用户操作
android:textColorHint="@color/gray_color"
android:textSize="14sp"
android:paddingLeft="30dp" //左侧内边距 30dp,用于给左侧的搜索图标留出空间/>
</LinearLayout>
推荐列表界面的搭建,分为水平Tab栏和RecyclerView.
- 水平的线性布局,内部放置了多个TextView,所有文字都引用了之前定义的tvStyle,保证视觉的一致性。
- RecyclerView这是推荐列表的核心控件,它负责高效地显示大量新闻条目。id定义为rv_list,方便在 Activity或Fragment中通过findViewById获取并设置适配器。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_gray_color"
android:orientation="vertical">
<include layout="@layout/title_bar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@android:color/white"
android:orientation="horizontal">
<TextView
style="@style/tvStyle"
android:text="推荐"
android:textColor="@android:color/holo_red_dark" />
<TextView
style="@style/tvStyle"
android:text="抗疫"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="小视频"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="北京"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="视频"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="热点"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="娱乐"
android:textColor="@color/gray_color" />
</LinearLayout>
**<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eeeeee" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />**
</LinearLayout>
搭建列表条目有两种方式,因为布局的方式不同,后面会讲到。类型的加载和选择
8. 封装实体类:
这是一个典型的 Java 实体类(Entity / POJO),用于封装新闻相关的数据。
- 私有字段+公有getter/setter:符合 JavaBean 规范,保证了数据的可控性(例如可以在 setter 中添加校验逻辑)。
- 对外隐藏内部表示,提供统一的访问接口,便于后期修改字段名或类型(只需改 getter/setter,不影响调用方)
9. 编写推荐列表适配器:
创建适配器NewsAdapter
mContext:用于后续获取LayoutInflater服务,这是将XML布局文件解析为Java View对象的必经之路。NewsList:保存外部传入的数据源引用。
获取条目类型: getItemViewType(int position)
@Override
public int getItemViewType(int position) {
return NewsList.get(position).getType();
}
- RecyclerView 在渲染列表时,会先遍历可见范围内的每一个 position,调用
getItemViewType确定该位置应当使用哪种视图布局。该方法的返回值会在内部传递给onCreateViewHolder的第二个参数int viewType。 - 根据
NewsBean.getType()的返回值(此处假设为 1 或 2),适配器将条目分为两类: viewType == 1:对应list_item_one.xml布局,通常用于显示 单张图片 的新闻样式。viewType == 2:对应list_item_two.xml布局,用于显示 三张图片 的新闻样式。
这种设计使得 RecyclerView 的视图池(RecycledViewPool)可以独立管理两种类型的 ViewHolder,避免单图布局的 ViewHolder 被错误地复用给三图条目。
创建条目视图: onCreateViewHolder图片在上面创建适配器后
LayoutInflater.inflate 详解
LayoutInflater.from(mContext):获取布局解析器服务。inflate(R.layout.list_item_one, parent, false):- 参数1:待解析的 XML 布局资源 ID。
- 参数2:
parent即 RecyclerView 本身,用于获取布局参数的类型。 - 参数3:
false表示 不立即将解析出的 View 添加到 parent 中。这是因为 RecyclerView 会根据内部布局算法在合适的时机调用addView。如果此处传入true,会导致视图层级混乱甚至崩溃。这是 RecyclerView 性能优化的核心阵地,也是 ViewHolder 模式的具体体现。
NewsAdapter还定义了两个内部类来充当 ViewHolder : 能将布局中需要频繁操作的子控件引用缓存在ViewHolder对象中。当条目滑出屏幕被回收后,这些控件引用依然保留,下次复用时无需再次执行耗时的findViewById遍历。
讲到这里又要回到之前搭建条目时候的两种布局了。
第一个是list_item_one.xml 这是左文右图的特点,左侧文字信息固定包裹内容,右侧图片利用layout_toRightOf占满剩余宽度。iv_top与底部信息栏处于同一水平线,它的显示与隐藏会直接影响tv_name等文字的起始位置。
第二个是list_item_two.xml 这是上文下图,并且三图并排的特点。垂直的结构进行排列,标题在上,图片在中,信息流在下。ivImg样式定义了layout_width="0dp"和layout_weight="1",使得三张图片在横向LinearLayout中均匀分配宽度。
对于位置position,应该用上面的哪一个布局来呈现呢?
NewsBean实体类中就包含一个
type 字段,例如:
- 单图新闻、纯文字新闻、带置顶标识的新闻返回1。
- 三图新闻、多图新闻返回2。 刚好在MainActivity.java中就有体现。
绑定界面数据: onBindViewHolder 它的作用也显而易见,就是将NewsBean对象中的字符串和图片资源 ID,填入上述 ViewHolder 持有的控件中。
单图模式绑定(MyViewHolder1)
-
在 list_item_one.xml中,iv_top和底部信息流是重叠或并列在同一个RelativeLayout中的。虽然我们只看到android:layout_toRightOf="@id/iv_top",实际上这意味:
-
当iv_top显示(VISIBLE)时,它占据 20dp 宽度,底部的LinearLayout(包含作者、评论)会被挤到右侧。
-
当iv_top隐藏(GONE)时,底部的LinearLayout会左对齐。
-
互斥处理:代码中特意将iv_img设置为
GONE当显示置顶时。这虽然与iv_top不是同一个位置,但结合布局来看,当显示置顶图标时,通常意味着该条目是纯文字头条,不需要显示右侧缩略图。这是一种常见的 UI 变体处理。 -
底部的三个 TextView 均引用了
style="@style/tvInfo"。回顾前文样式定义,它规定了字体大小 14sp、灰色、垂直居中。这意味着在绑定数据时,我们无需在 Java 代码中重复设置这些视觉属性,只需关注文本内容本身。 -
if (bean.getImgList().size() == 0) return;是一道简单的防线。但严格来说,更优雅的做法是设置一个默认的占位图,而不是直接跳过设置(可能导致显示上一轮复用的残留图片)。
三图模式绑定(MyViewHolder2)
- 该样式定义了layout_width="0dp"、layout_weight="1"、layout_height="90dp"。因此,无论原图尺寸如何,显示区域都被严格限制为等宽90dp高的方块。这种样式先行的策略大大简化了适配器代码。
- 代码直接调用 get(0)、get(1)、get(2),这隐含了imgList必须包含至少 3 个元素的契约。若后端数据异常返回了仅 2 张图的数组,应用将在该位置崩溃。
- 改进建议:在绑定前判断
imgList.size(),如果小于 3,可将多余的ImageView设为GONE,或者加载默认图
总结: getItemViewType是分岔路口的路牌,onCreateViewHolder是制造不同车型的工厂,而onBindViewHolder则是那位熟练的装配工——它不问车型,只看零件槽位(findViewById的 ID),准确地将数据螺栓拧入对应的螺孔中。
获取条目总数: getItemCount
- 它的作用很简单:RecyclerView 通过此返回值决定需要渲染的 Item 数量。如果NewsList为null,此处将抛出空指针异常。
10. 显示列表数据:
1.创建setData()方法,将定义的数组中的数据添加到新闻数据集合NewsList中
在调用setData()之前,在 Activity 的成员变量中定义一系列数组,用于模拟后端返回的 JSON 数据。
-
- 初始化集合容器
-
- 遍历标题数组(代表新闻条目数)
-
- 通用字段赋值(每个条目都有的属性)
-
- 根据不同位置构建不同的图片列表(模拟真实接口的不规则数据)
-
- 将构建好的Bean加入集合
2.将新闻集合数据设置到适配器NewsAdapter中
数据准备完成后,需要将NewsList传递给 NewsAdapter,并关联到 RecyclerView。
- 加载主布局文件(即前文包含
<include layout="@layout/title_bar"/>和RecyclerView的那个布局),使后续findViewById能正确找到控件。 - 执行我们之前分析的静态数据组装逻辑,生成NewsList集合对象。注意:NewsList通常为该 Activity 的成员变量(如
private List<NewsBean> NewsList;)。 - 从布局中获取android:id="@+id/rv_list"的 RecyclerView 控件实例。
- RecyclerView 不预设任何排列方式,需要指定一个LayoutManager来定义条目是垂直滚动、水平滚动、网格还是瀑布流。此处使用线性垂直布局。
- 调用适配器构造函数,将当前上下文和构建好的新闻数据集合传递进去。适配器内部保存了NewsList的引用。
- 触发渲染。RecyclerView 拿到适配器后,会立即调用getItemCount()确定条目总数,并根据屏幕可见范围调用onCreateViewHolder和onBindViewHolder绘制首屏内容。