ListView
实现方式:
- 界面添加 ListView
- 使用数据集创建适配器,返回项视图
- ListView 应用适配器
- 数据集增删改的通知
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,可以直接对它的控件进行初始化配置)。如果没有存储该种视图则创建新的视图。
具体操作见源码:
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; <