Android 入门宝典 - ListView & Adapter 列表视图

本文详细介绍了Android中的ListView及其Adapter,讲解了ListView的复用机制,包括RecycleBin和ScrapViews,以及如何避免复用问题。同时,对比了RecyclerView的复用机制,并探讨了View.setTag在视图管理中的作用。通过实例展示了如何创建和应用Adapter,以及在音乐播放列表场景下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现方式:

  1. 界面添加 ListView
  2. 使用数据集创建适配器,返回项视图
  3. ListView 应用适配器
  4. 数据集增删改的通知

ListView

https://developer.android.com/reference/android/widget/ListView

显示可垂直滚动的视图集合,其中每个视图都位于列表中上一个视图的紧下方。对于一种更现代,灵活和高效的列表显示方法,请使用 RecyclerView。

java.lang.Object
   ↳	android.view.View
 	   ↳	android.view.ViewGroup
 	 	   ↳	android.widget.AdapterView<android.widget.ListAdapter>
 	 	 	   ↳	android.widget.AbsListView
 	 	 	 	   ↳	android.widget.ListView

列表视图并不知道视图的细节,只根据需要从 ListAdapter 中请求视图,例如在用户向上或向下滚动时请求新视图。

为了显示列表中的项目,请调用 setAdapter(android.widget.ListAdapter) 将适配器与列表关联。

简单示例

要为数据集中的每个项目显示自定义的视图,请实现 ListAdapter。例如,扩展 BaseAdapter 并在 getView(…) 中创建并配置每个数据项的视图:

private class MyAdapter extends BaseAdapter {
   
   

    // override other abstract methods here

    @Override
    public View getView(int position, View convertView, ViewGroup container) {
   
   
        if (convertView == null) {
   
   
            convertView = getLayoutInflater().inflate(R.layout.list_item, container, false);
        }

        ((TextView) convertView.findViewById(android.R.id.text1))
                .setText(getItem(position));
        return convertView;
    }
}

ListView 尝试重用视图对象,以提高性能并避免响应用户滚动的滞后。要利用此功能,请在创建或扩展新视图对象之前检查提供给 getView(…) 的 convertView 是否为 null。

常用方法
setAdapter(ListAdapter adapter):在此 ListView 后面配置数据。传递给此方法的适配器可以再由 WrapperListAdapter 包装,增加添加页眉 and/or 页脚的功能。
ListAdapter负责维护支持该列表的数据,并产生一个视图以表示该数据集中的项目。

addHeaderView (View v):添加固定视图以显示在列表顶部。如果多次调用此方法,则视图将按添加顺序出现。如果需要,使用此调用添加的视图可以成为焦点。
注意:首次引入此方法时,只能在使用setAdapter(android.widget.ListAdapter)设置适配器之前调用此方法。从Build.VERSION_CODES.KITKAT开始,可以随时调用此方法。如果ListView的适配器未扩展HeaderViewListAdapter,则它将与WrapperListAdapter的支持实例一起包装。

常用属性
android:divider:可绘制或可在列表项之间绘制的颜色,@null 为不绘制。
android:dividerHeight:分隔线的高度。如果未指定,将使用分隔线的固有高度。
android:scrollbars:定义在滚动时是否显示滚动条,none 为不显示。

AbsListView

https://developer.android.com/reference/android/widget/AbsListView#setChoiceMode(int)

setChoiceMode (int choiceMode):定义列表的选择行为。默认情况下,列表没有任何选择行为(CHOICE_MODE_NONE)。通过将choiceMode设置为CHOICE_MODE_SINGLE,列表可以允许最多一项处于选定状态。通过将choiceMode设置为 CHOICE_MODE_MULTIPLE,列表允许选择任意数量的项目。

WrapperListAdapter

包装另一个列表适配器的列表适配器。可以通过调用 getWrappedAdapter() 来取回包装的适配器。

public interface WrapperListAdapter implements ListAdapter

ListAdapter

实现 Adapter 接口,它是 ListView 和支持列表的数据之间的桥梁。通常,数据来自 Cursor,但这不是必需的。ListView 可以显示任何包装在 ListAdapter 中的数据。

Adapter

Adapter 对象充当 AdapterView 和该视图的基础数据之间的桥梁。适配器提供对数据项的访问。适配器还负责为数据集中的每个项目创建一个 View。

android.widget.Adapter

public interface Adapter

已知子类
ArrayAdapter, BaseAdapter, CursorAdapter, HeaderViewListAdapter, ListAdapter, ResourceCursorAdapter, SimpleAdapter, SimpleCursorAdapter, SpinnerAdapter, ThemedSpinnerAdapter, WrapperListAdapter

常见方法
getView(int position, View convertView, ViewGroup parent):获取一个显示数据集中指定位置数据的视图。
getItemViewType(int position):获取 getView(int, View, ViewGroup) 将要创建的指定项 View 的类型。使用该方法可以为不同类型的项创建不同样式的 View。当然使用的 Adapter 的逻辑要自己实现。一种实现是在 getView 中通过 getItemViewType 返回的类型码使用不同的 ViewHold 创建不同样式的 View。如果适配器始终为所有项目返回相同类型的 View,则此方法应返回 1。
getViewTypeCount():返回将由 getView(int, View, ViewGroup) 创建的 View 的类型数。可以在 getView 中调用 getItemViewType 根据返回的不同类型创建不同的 ViewHolder。

getCount():此适配器表示的数据集中有多少个项目。
isEmpty():如果此适配器不包含任何数据,则为true。这用于确定是否应显示空白视图。典型的实现将返回 getCount() == 0,但是自从 getCount() 包含页眉和页脚,专用适配器可能不这样返回。

getItem(int position):获取与指定位置关联的数据集中的数据项。
getItemId(int position):获取与指定位置关联的数据集中数据项的行 ID。

registerDataSetObserver(DataSetObserver observer):注册一个观察者,该适配器使用的数据发生更改时将通知该观察者。
unregisterDataSetObserver(DataSetObserver observer):注消观察者。

getAutofillOptions():获取适配器数据的字符串表示形式,它可以帮助 AutofillService 自动填充适配器支持的视图。

RecyclerView

https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView

灵活的视图,可以使用有限窗口展示大型数据集。AndroidX 中可以直接使用,Support 中需要单独引入,见 Android - 构建编译 & Java 库插件

java.lang.Object
   ↳	android.view.View
 	   ↳	android.view.ViewGroup
 	 	   ↳	android.support.v7.widget.RecyclerView

RecyclerView.Adapter

RecyclerView的子类,负责为 RecyclerView 提供表示数据集中项目的视图。

java.lang.Object
   ↳	android.support.v7.widget.RecyclerView.Adapter<VH extends android.support.v7.widget.RecyclerView.ViewHolder>

ViewHolder:内部类,定义 View 元素,构造时绑定 View 组件

getItemViewType(int position):返回该项目的视图类型,以用于视图循环使用。与 ListView 适配器不同的是类型不必是连续的。可以考虑使用 ID 资源来唯一标识项目视图类型。
onCreateViewHolder:当 RecyclerView 需要新的给定类型的 RecyclerView.ViewHolder 来创建 View 时,通过 createViewHolder(ViewGroup parent, int viewType) 回掉。具体实现是新建 ViewHolder,对 ViewHolder 元素进行初始化
onBindViewHolder:由 RecyclerView 调用 bindViewHolder(VH holder, int position) 时回调在指定位置显示数据。与 ListView 不同,如果只是结构更改事件,则 RecyclerView 将不会再次回调此方法,除非该项目本身无效或无法确定新位置。具体实现是对 ViewHolder 子项进行赋值。
getItemCount:返回适配器持有的数据集中的项目总数。

hasStableIds():如果此适配器已经为数据集中指定的项生成唯一的 long 型键值,也就是说适配器内的键值列表没有新的 long 值加入时,则返回 true。若该项内容不作改变,单纯重新放置入数据集中时,它们的键值依然相同。

onDetachedFromRecyclerView(RecyclerView recyclerView):在 RecyclerView 停止观察此适配器时调用。

onViewAttachedToWindow(VH holder):当此适配器创建的视图已附加到窗口时调用。这可以用作用户即将看到该视图的合理信号。如果适配器先前 onViewDetachedFromWindow 释放了任何资源,则应在此处还原这些资源。
onViewDetachedFromWindow(VH holder):当此适配器创建的视图从其窗口分离时,调用此方法。从窗户上移开不一定是永久的情况。适配器视图的使用者可以选择在不可见的情况下在屏幕外缓存视图,并根据需要进行 Attach 和 Detach。

对数据集的增删改

notifyDataSetChanged():通知任何注册观察员数据集已更改。RecycleView 在设置适配器的同时会注册成为观察者,获得通知会更改相应的 View。

数据更改事件有两种不同的类别,即项目内容的更改或结构的更改。项目内容的更改是指单个项目的数据已更新但未发生位置更改的情况。结构更改是指数据集的插入,删除或移动。 notifyDataSetChanged 未指定是哪种类型,使得所有观察者需认定所有现有项目内容和结构不再有效。LayoutManager 将强制所有可见视图的重新绑定和重新布局。
如果 hasStableIds 返回 true 表示项目内容未发生变化,则 RecyclerView 将尝试使用结构更改事件。这可以保留动画和可见对象,但是仍然需要重新放置和重新布局个别项目视图。 如果您正在编写适配器,则编写更具体的更改事件方法总是更加高效。将 notifyDataSetChanged() 作为最后的选择。

notifyItemChanged(int position, Object payload):项目内容变更事件。它表示该位置数据的任何反映都已过时,应进行更新。该位置的项目保留原来的标识。

传递有效负载可以进行部分更改。如果项目已经由 ViewHolder 表示,则所有有效载荷将被合并成 List 并传递给 onBindViewHolder(ViewHolder, int, List),此间可以做部分修改(eg. 将 ViewHold 隐藏的控件赋值显示反馈等内容),或者调用 onBindViewHolder(ViewHolder, int) 做原本的修改。有效载荷为 null 的 notifyItemRangeChanged() 会清除所有现有的有效载荷。当 View 未 attachedToWindow 时,有效载荷将被简单地丢弃。

notifyItemChanged(int position):同上(无有效载荷,传递的是 onBindViewHolder(ViewHolder, int))
notifyItemInserted(int position):结构更改事件。数据集中其他现有项目的表示形式仍被认为是最新的,并且不会重新绑定,尽管它们的位置可能会发生变化。
notifyItemRemoved(int position):同上
notifyItemMoved(int fromPosition, int toPosition):结构更改事件。原先在 fromPosition 的项目已移至 toPosition。数据集中其他现有项目的表示形式仍被认为是最新的,并且不会重新绑定,尽管它们的位置可能会发生变化。
notifyItemRangeChanged(int positionStart, int itemCount, Object payload):内容更改事件。一个范围所有项目的更新。
notifyItemRangeChanged(int positionStart, int itemCount):同上(无有效载荷)
notifyItemRangeInserted(int positionStart, int itemCount):范围插入
notifyItemRangeRemoved(int positionStart, int itemCount):范围移除

ListView 复用的机制

layout 开始时会通过 getView 创建一屏幕的视图放在 ActiveViews 中,同时也会创建与视图对应的 ViewHolder;在 layout 结束时 ActiveViews 中的所有视图均降级为 ScrapViews,方便复用。(见 ListView.layoutChildren

当有个视图滑进屏幕,意味着另一边有个视图从窗口中 detach、添加到弃置列表中。

新的视图可以从弃置列表中获取同样类型的视图,对其进行重新初始化配置来复用(表现为往 getView 中传入 scrapView 作为 convertView,判断时不为 null,可以直接对它的控件进行初始化配置)。如果没有存储该种视图则创建新的视图。

listview_recycler

具体操作见源码:

https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/widget/ListView.java
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/widget/AbsListView.java

RecycleBin

RecycleBin有助于跨布局重复使用视图。RecycleBin具有两个级别的存储:ActiveViews和ScrapViews。

AbsListView # RecycleBin 内部类的成员变量:

/**
 * 存储在 mActiveViews 中的第一个视图的位置。
 */
private int mFirstActivePosition;

/**
 * 布局开始时在屏幕上显示的视图。此数组在布局开始时填充,在布局结束时将mActiveViews中的所有视图移至mScrapViews。
 * mActiveViews中的视图表示视图的连续范围,其中第一个视图存储在mFirstActivePosition中。
 */
private View[] mActiveViews = new View[0];

/**
 * 适配器可以将其用作转换视图的弃置视图列表,其所存储的视图是无序的。
 */
private ArrayList<View>[] mScrapViews;

private int mViewTypeCount;	<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值