ListView
列表视图控件,显示一个可垂直滚动的视图集合,其中每个视图都位于列表中前一个视图的正下方。
目前官方已经推荐使用兼容拓展库提供的RecyclerView (opens new window)来显示列表,这是一个更现代、更灵活、更有性能的新控件。
可滚动的列表,这个在日常的软件中已经是很常见的一种控件,在大量数据需要浏览查看时,滚动列表是一个很好的交互方式。如购物网站滑动列表刷宝贝,外卖软件滑动列表选择美食,服务软件滑动列表选择服务等等...
列表视图是一个适配器视图,它不知道它包含的视图的详细信息,比如类型和内容。相反,列表视图会根据需要从 ListAdapter 中请求视图,比如在用户向上或向下滚动时显示对应的新视图。
要为数据集中的每个项显示更自定义的视图,需要实现一个 ListAdapter
。例如,扩展 BaseAdapter
并为getView(…)
中的每个数据项创建和配置对应的视图。
# 基本属性和方法
常用属性:
android:divider
:设置列表的分隔条,可以用颜色分割,也可以用drawable资源分割android:dividerHeight
:设置列表分隔条的高度android:footerDividersEnabled
:当设置为false时,ListView将不会在每个页脚视图之前绘制分割线,默认值为true。android:headerDividersEnabled
:当设置为false时,ListView将不会在每个标题视图后绘制分割线,默认值为true。
常用方法:
addFooterView(View v, Object data, boolean isSelectable)
:添加一个固定视图以显示在列表的底部,并指定这个视图的数据以及视图是否可以被选中addFooterView(View v)
:添加一个固定视图以显示在列表的底部addHeaderView(View v, Object data, boolean isSelectable)
:添加一个固定视图以显示在列表的顶部,并指定这个视图的数据以及视图是否可以被选中addHeaderView(View v)
:添加一个固定视图以显示在列表的顶部removeFooterView(View v)
:移除先前添加的页脚视图removeHeaderView(View v)
:删除先前添加的标题视图setAdapter(ListAdapter adapter)
:设置列表适配器setSelection(int position)
:设置当前选择的项smoothScrollByOffset(int offset)
:平滑地滚动到指定的适配器偏移位置smoothScrollToPosition(int position)
:平滑地滚动到指定的适配器位置
# 基本使用示例
ListView 最常见的使用方法就是使用自定义 BaseAdapter,绑定自定义列表布局的方式。下面就来看看这种常见的实现方式。
- 定义列表的数据项对象:DataBean
/**
* 定义列表数据对象.
*/
public class DataBean {
// 图标
private int icon;
// 标题
private String title;
// 内容
private String tips;
public DataBean(int icon, String title, String tips) {
this.icon = icon;
this.title = title;
this.tips = tips;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTips() {
return tips;
}
public void setTips(String tips) {
this.tips = tips;
}
}
- 实现自定义实现 BaseAdapter 列表适配器:ListViewAdapter
/**
* ListView 列表适配器.
*/
public class ListViewAdapter extends BaseAdapter {
private Context mContext;
private List<DataBean> mDataBeans;
public ListViewAdapter(Context context, List<DataBean> dataBeans) {
mContext = context;
mDataBeans = dataBeans;
}
@Override
public int getCount() {
return mDataBeans == null ? 0 : mDataBeans.size();
}
@Override
public Object getItem(int position) {
return mDataBeans == null ? null : mDataBeans.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.layout_listview_item, parent, false);
}
ImageView ivIcon = convertView.findViewById(R.id.iv_icon);
TextView tvTitle = convertView.findViewById(R.id.tv_title);
TextView tvTips = convertView.findViewById(R.id.tv_tips);
DataBean dataBean = mDataBeans.get(position);
ivIcon.setBackgroundResource(dataBean.getIcon());
tvTitle.setText(dataBean.getTitle());
tvTips.setText(dataBean.getTips());
return convertView;
}
}
- 自定义列表布局:layout_listview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/iv_icon"
app:layout_constraintTop_toTopOf="@id/iv_icon" />
<TextView
android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/iv_icon"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 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:gravity="center_horizontal"
android:orientation="vertical">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
- 示例代码
// 定义图标类型
private static final int ICON_SPRING = 0;
private static final int ICON_SUMMER = 1;
private static final int ICON_AUTUMN = 2;
private static final int ICON_WINTER = 3;
// 初始化列表模拟数据
ArrayList<DataBean> list = new ArrayList<>();
for (int i = 0; i < 36; i++) {
int iconType = i % 4;
int iconRes;
String title;
String tips;
switch (iconType) {
case ICON_SPRING:
iconRes = R.drawable.icon_1;
title = "春天";
tips = "细听来,句句是乡音。";
break;
case ICON_SUMMER:
iconRes = R.drawable.icon_2;
title = "夏天";
tips = "午饭后,纳凉大树下。";
break;
case ICON_AUTUMN:
iconRes = R.drawable.icon_3;
title = "秋天";
tips = "东海岸,相约看海鸥。";
break;
case ICON_WINTER:
iconRes = R.drawable.icon_4;
title = "冬天";
tips = "大街上,传来爆竹声。";
break;
default:
iconRes = 0;
title = "";
tips = "";
}
DataBean dataBean = new DataBean(iconRes, title, tips);
list.add(dataBean);
}
ListView listView = findViewById(R.id.lv);
// 设置列表适配器
ListViewAdapter adapter = new ListViewAdapter(this, list);
listView.setAdapter(adapter);
// 设置item点击监听
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(getApplicationContext(),
String.format("点击了第%d项:%s", position, list.get(position).getTitle()),
Toast.LENGTH_SHORT).show();
}
});
// 设置item长按监听
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(getApplicationContext(),
String.format("长按了第%d项:%s", position, list.get(position).getTitle()),
Toast.LENGTH_SHORT).show();
return true;
}
});
- 效果图
# 添加表头表尾
Listview 作为一个列表控件,可以自己设置表头与表尾。通过 addHeaderView
和 addFooterView
方法即可实现。
- 定义一个表头和表尾的自定义布局文件(这里表头和表尾使用同一个布局):layout_listview_header.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="wrap_content"
android:background="#4CAF50">
<TextView
android:id="@+id/tv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="bold"
android:textColor="#fff"
android:textSize="25dp" />
</LinearLayout>
- 代码设置表头和表尾(在设置adapter前设置))
ListView listView = findViewById(R.id.lv);
ListViewAdapter adapter = new ListViewAdapter(this, list);
// 加载表头布局
View headerView = LayoutInflater.from(this).inflate(R.layout.layout_listview_header, null);
TextView tvHeader = headerView.findViewById(R.id.tv_header);
tvHeader.setText("表头");
// 设置列表的表头(设置adapter前设置)
listView.addHeaderView(headerView);
// 加载表尾布局
View footView = LayoutInflater.from(this).inflate(R.layout.layout_listview_header, null);
TextView tvFoot = footView.findViewById(R.id.tv_header);
tvFoot.setText("表尾");
// 设置列表的表尾(设置adapter前设置)
listView.addFooterView(footView);
// 设置列表适配器
listView.setAdapter(adapter);
- 效果图
# 列表显示方向
默认情况下,列表是从上到下方向显示的,如需要列表从下到上方向显示,可以设置android:stackFromBottom="true"
属性或者调用setStackFromBottom(true)
方法即可实现底部显示方向效果。
效果如下:
还有一种效果,底部显示方向效果在动态添加数据时,具有如下效果:
# 滚动条配置
当列表数据显示数量超过 ListView 本身的大小时,默认会显示一个滚动条(scrollbars
),指示当前显示的数据在整个列表中的相对位置。
常用的滚动条配置属性:
android:scrollbars
:它的取值可以是vertical,horizontal或none。 对ListView来说,它只能垂直滚动,将scrollbars设置成horizontal或者none效果都是一样的,也就是不会出现滚动条。所以如果不希望ListView显示滚动条,就将scrollbars设置成none。此外,如果scrollbars设置成none,那么其他的滚动条相关的配置都不会有效果。android:scrollbarThumbVertical
:滚动条滑块资源设置,这也是美化滚动条时最重要的一项配置。它可以设置为一个颜色值,或者是一个Drawable资源。对Drawable资源可以使用.9的png图片,也可以使用XML来配置。android:scrollbarTrackVertical
:滚动条的滑动轨道资源设置,可以设置为一个颜色值,或者是一个Drawable资源。android:scrollbarSize
:滚动条的大小,对ListView来说就是滚动条的宽度大小。如果scrollbarThumbVertical配置的是一个具有宽度的资源,此配置会被忽略。只有scrollbarThumbVertica配置的是颜色值或者xml时(自身宽度为0的Drawable),此项配置才会有效。android:verticalScrollbarPosition
:设置滚动条的位置,它可以是right,left或者是defaultPosition。如果不设置,默认是defaultPosition。如果是defaultPosition,则滚动条的位置受到布局RTL配置的影响,如果布局是从右往左,则滚动条显示在left侧,如果布局是从左往右,则滚动条显示在right侧。 滚动条没有上下位置的设置。对于可水平滚动的View(如HorizontalScrollView),滚动条始终在下方。不能设置到上方。android:scrollbarStyle
:此项配置也是用来设置滚动条的位置,不过不是左右位置,而是滚动条和ListView内容之间的相对位置,它的取值范围是insideoverlay(默认),insideInset,outsideOverlay,outsideInset。android:fadeScrollbars
:是否在ListView不滚动时隐藏滚动条,可以选择true或false,默认为true,也就是不滚动时隐藏。如果将其设置为false,那么只要ListView能够滚动,滚动条就会一直显示,不会隐藏。但如果ListView不足一页,不能滚动,则不会显示。android:scrollbarDefaultDelayBeforeFade
:设置滚动条从显示到隐藏的间隔时间,单位为毫秒,如果不设置,默认为300毫秒。需要注意的是,ListView初次加载完成时,会自动显示出滚动条。这时如果没有去做滑动操作,滚动条也会自动隐藏,不过和滑动后隐藏不同的是,这里需要经过4倍间隔时间后才会开始隐藏。android:scrollbarFadeDuration
:设置滚动条开始隐藏到完全隐藏的间隔时间,单位为毫秒,如果不设置,默认为250毫秒。android:scrollbarAlwaysDrawVerticalTrack
:设置是否总是显示垂直滚动条的Track,可以选择true或false,默认为false。通常,如果设置了滚动条的Track,那么Track会随着滚动条一起显示和隐藏。但如果设置了scrollbarAlwaysDrawVerticalTrack为true,则滚动条的Track将一直显示,不会隐藏。当然,如果没有配置scrollbarTrackVertical,即使设置了scrollbarAlwaysDrawVerticalTrack为true,也不会有Track显示。此外,fadeScrollbars配置为false,则无论scrollbarAlwaysDrawVerticalTrack配置为true还是false,Track都不会隐藏。android:fastScrollEnabled
:是否启用快速滚动条,可以选择true或false,默认为false,也就是不使用快速滚动条。如果启用了快速滚动条,当ListView页数小于4页时,仍然会显示普通滚动条,当ListView页数超过4页时,才会显示快速滚动条。android:fastScrollStyle
:设置快速滚动条的样式,其可配置的值,及含义和android:scrollbarStyle一样。不过此项配置是从Android5.0才开始有的,所以只有Android5 .0以上的系统中,此项配置才会有效。android:fastScrollAlwaysVisible
: 设置是否始终显示快速滚动条,可以选择true或false,默认为false,如果将其设置为true,则快速滚动条将始终显示,不会隐藏,且不受4页的约束,也就是说ListView即使不足4页,快速滚动条也会显示,甚至即使ListView不足一页,快速滚动条都会显示。此外,android:fadeScrollbars设置将不会生效,也就是即使android:fadeScrollbars设置为false,也不会显示普通滚动条。
滚动条的效果相关配置还是挺多的,这里列出的都是一般用的比较多的,这里也不再对这些属性进行逐个进行详细展开了。
设置滚动条位置android:scrollbarStyle
:
android:scrollbarStyle
用于控制滚动条的与列表内容之间的相对位置,主要有如下设置:
- insideoverlay:默认属性,表示滚动条右侧和ListView可用区域右侧对其,且覆盖在列表的Item之上。
- insideInset:表示滚动条右侧和ListView可用区域右侧对其,但不覆盖在Item之上,而是将Item挤到滚动条的左边。
- outsideOverlay:表示滚动条右侧和ListView右侧对其,且覆盖在右侧padding之上。
- outsideInset:表示滚动条右侧和ListView可用区域右侧对其,但不覆盖在padding之上,而是将padding挤到滚动条的左边。
滚动条的滑块和滑轨自定义实现:
- 定义滚动条滑块xml样式:listview_scrollbar_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 红蓝色上下方向渐变 -->
<gradient
android:angle="90"
android:endColor="#FF0000"
android:startColor="#03A9F4" />
<!-- 圆角 -->
<corners android:radius="6dp" />
<!-- 描边 -->
<stroke
android:width="1dp"
android:color="#A4000000" />
</shape>
- 定义滚动条滑动轨道xml样式:listview_scrollbar_track.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 颜色 -->
<solid
android:color="#DFF1C1" />
<!-- 圆角 -->
<corners android:radius="6dp" />
<!-- 描边 -->
<stroke
android:width="1dp"
android:color="#A2000000" />
</shape>
- 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:gravity="center_horizontal"
android:orientation="vertical">
<ListView
android:layout_width="300dp"
android:layout_height="match_parent"
android:fadeScrollbars="false"
android:paddingEnd="5dp"
android:scrollbarSize="10dp"
android:scrollbarStyle="outsideInset"
android:scrollbarThumbVertical="@drawable/listview_scrollbar_thumb"
android:scrollbarTrackVertical="@drawable/listview_scrollbar_track"
android:stackFromBottom="true" />
</LinearLayout>
- 效果图
# ConvertView复用
ListView 中有 ConvertView
复用机制,试想一下,列表控件主要用于大数据展示的场景,如果数据很庞大,成千万条的时候,会创建相应数量的View项么?显然不是,在 Adapter
的 getView
方法中,有一个ConvertView 参数,这个参数不为 null
的时候,就是 ListView 已经触发了 View 的复用机制了。
ConvertView的复用机制,简单电概括就是:ListView会创建列表显示区域大小所需数量的View,后面滑动时会复用不可见的View来代替新可见的显示项,然后复用这个View将界面元素进行重新刷新即可。当然是否复用这个View,取决与Adapter中getView返回的view,如果不希望复用,那么在getView中总是返回一个新创建的View即可(有内存和性能消耗风险)。
理解示意图:
- 正常ConvertView复用使用方法。
public View getView(int position, View convertView, ViewGroup parent) {
Log.e(TAG, "getView: position = " + position + "convertView = " + convertView);
// 当convertView为null时,需要创建一个新的列表项的View
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.layout_listview_item, parent, false);
}
// 当convertView不为null时,直接使用这个复用的View,重新刷新数据即可
ImageView ivIcon = convertView.findViewById(R.id.iv_icon);
TextView tvTitle = convertView.findViewById(R.id.tv_title);
TextView tvTips = convertView.findViewById(R.id.tv_tips);
DataBean dataBean = mDataBeans.get(position);
ivIcon.setBackgroundResource(dataBean.getIcon());
tvTitle.setText(dataBean.getTitle());
tvTips.setText(dataBean.getTips());
return convertView;
}
- 使用ViewHolder组件,优化减少findViewById()的调用。
/**
* ListView ViewHolder优化列表适配器.
*/
public class ListViewAdapter extends BaseAdapter {
private Context mContext;
private List<DataBean> mDataBeans;
public ListViewAdapter(Context context, List<DataBean> dataBeans) {
mContext = context;
mDataBeans = dataBeans;
}
@Override
public int getCount() {
return mDataBeans == null ? 0 : mDataBeans.size();
}
@Override
public Object getItem(int position) {
return mDataBeans == null ? null : mDataBeans.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.e(TAG, "getView: position = " + position + "convertView = " + convertView);
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.layout_listview_item, parent, false);
viewHolder = new ViewHolder(convertView);
// 将ViewHolder保存到当前的View中
convertView.setTag(viewHolder);
} else {
// 再当前的View中取出ViewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
DataBean dataBean = mDataBeans.get(position);
// 直接使用处理好的ViewHolder中的控件
viewHolder.getIvIcon().setBackgroundResource(dataBean.getIcon());
viewHolder.getTvTitle().setText(dataBean.getTitle());
viewHolder.getTvTips().setText(dataBean.getTips());
return convertView;
}
// 定义一个ViewHolder,保存View中的需要处理的控件和相关数据
private class ViewHolder {
private ImageView ivIcon;
private TextView tvTitle;
private TextView tvTips;
public ViewHolder(View root) {
ivIcon = root.findViewById(R.id.iv_icon);
tvTitle = root.findViewById(R.id.tv_title);
tvTips = root.findViewById(R.id.tv_tips);
}
public ImageView getIvIcon() {
return ivIcon;
}
public TextView getTvTitle() {
return tvTitle;
}
public TextView getTvTips() {
return tvTips;
}
}
}
# 列表数据更新
列表的数据更新包括新增、删除、修改等操作,在进行数据更新时,可以通过Adapter的notifyDataSetChanged()
来通知列表数据有更新,列表会重新刷新数据,刷新数据过程就是将当前所显示的View重新刷新一次(通过调用Adapter中getView方法来刷新指定的View)。
示例:
- 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"
xmlns:tools="http://schemas.android.com/tools"
android:gravity="center_horizontal"
tools:context=".activity.DemoActivity"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="addData"
android:text="增加数据" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="deleteData"
android:text="删除数据" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="updateData"
android:text="修改数据" />
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="false"
android:paddingEnd="5dp"
android:scrollbarSize="10dp"
android:scrollbarStyle="outsideInset" />
</LinearLayout>
- 示例代码
public class DemoActivity extends AppCompatActivity {
private static final String TAG = "DemoActivity";
private static final int ICON_SPRING = 0;
private static final int ICON_SUMMER = 1;
private static final int ICON_AUTUMN = 2;
private static final int ICON_WINTER = 3;
private List<DataBean> mData = new ArrayList<>();
private ListViewAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
ListView listView = findViewById(R.id.lv);
mAdapter = new ListViewAdapter(this, mData);
// 设置列表适配器
listView.setAdapter(mAdapter);
}
// 列表中增加一条数据
public void addData(View view) {
int count = mData.size();
int iconType = count % 4;
int iconRes;
String title;
String tips;
switch (iconType) {
case ICON_SPRING:
iconRes = R.drawable.icon_1;
title = "春天";
tips = "细听来,句句是乡音。";
break;
case ICON_SUMMER:
iconRes = R.drawable.icon_2;
title = "夏天";
tips = "午饭后,纳凉大树下。";
break;
case ICON_AUTUMN:
iconRes = R.drawable.icon_3;
title = "秋天";
tips = "东海岸,相约看海鸥。";
break;
case ICON_WINTER:
iconRes = R.drawable.icon_4;
title = "冬天";
tips = "大街上,传来爆竹声。";
break;
default:
iconRes = 0;
title = "";
tips = "";
}
DataBean dataBean = new DataBean(iconRes, title, tips);
mData.add(dataBean);
mAdapter.notifyDataSetChanged();
}
// 列表中移除数据
public void deleteData(View view) {
if (mData.size() > 0) {
// 移除第一条数据
mData.remove(mData.size() - 1);
mAdapter.notifyDataSetChanged();
}
}
// 列表中更新数据
public void updateData(View view) {
if (mData.size() > 0) {
// 更新最后一条数据
DataBean dataBean = mData.get(mData.size() - 1);
dataBean.setTitle(dataBean.getTitle() + " 【数据已经修改】");
mAdapter.notifyDataSetChanged();
}
}
}
- 效果图
# 多种列表布局
ListView中可以根据实际情况加载不同布局显示。实现方式也很简单:定义不同布局的类型,实现Adapter中的getItemViewType(int position)
,然后返回特定位置的类型,在Adapter的getView
中调用这个方法获取当前Item的类型,加载和处理对应的类型的View。
这里实现一个简单的不同类型布局法人示例:奇数行显示一行文字的布局,偶数行显示图片+文字的布局。
- 创建一个文字的布局:layout_listview_item_type1.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">
<TextView
android:id="@+id/tv_item1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold" />
</LinearLayout>
- 创建图片和文字的布局:layout_listview_item_type2.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="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_item2"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="5dp" />
<TextView
android:id="@+id/tv_item2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
</LinearLayout>
- 示例代码
public class DemoActivity extends AppCompatActivity {
private static final String TAG = "DemoActivity";
// 图标类型定义
private static final int ICON_SPRING = 0;
private static final int ICON_SUMMER = 1;
private static final int ICON_AUTUMN = 2;
private static final int ICON_WINTER = 3;
// 列表类型定义
private static final int TYPE_ODD = 0;
private static final int TYPE_EVEN = 1;
private List<DataBean> mData = new ArrayList<>();
private ListViewAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
ListView listView = findViewById(R.id.lv);
mAdapter = new ListViewAdapter(this, mData);
// 设置列表适配器
listView.setAdapter(mAdapter);
// 初始化数据
for (int i = 0; i < 16; i++) {
int count = mData.size();
int iconType = count % 4;
int iconRes;
String title;
String tips;
switch (iconType) {
case ICON_SPRING:
iconRes = R.drawable.icon_1;
title = "春天";
tips = "细听来,句句是乡音。";
break;
case ICON_SUMMER:
iconRes = R.drawable.icon_2;
title = "夏天";
tips = "午饭后,纳凉大树下。";
break;
case ICON_AUTUMN:
iconRes = R.drawable.icon_3;
title = "秋天";
tips = "东海岸,相约看海鸥。";
break;
case ICON_WINTER:
iconRes = R.drawable.icon_4;
title = "冬天";
tips = "大街上,传来爆竹声。";
break;
default:
iconRes = 0;
title = "";
tips = "";
}
DataBean dataBean = new DataBean(iconRes, title, tips);
mData.add(dataBean);
}
mAdapter.notifyDataSetChanged();
}
/**
* ListView ViewHolder优化列表适配器.
*/
public class ListViewAdapter extends BaseAdapter {
private Context mContext;
private List<DataBean> mDataBeans;
public ListViewAdapter(Context context, List<DataBean> dataBeans) {
mContext = context;
mDataBeans = dataBeans;
}
@Override
public int getCount() {
return mDataBeans == null ? 0 : mDataBeans.size();
}
@Override
public DataBean getItem(int position) {
return mDataBeans == null ? null : mDataBeans.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
// 通过position位置,区分奇偶数类型
return position % 2 == 0 ? TYPE_EVEN : TYPE_ODD;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int itemViewType = getItemViewType(position);
Log.i(TAG, "getView: position = " + position + ", itemViewType = " + itemViewType);
switch (itemViewType) {
// 奇数选项
case TYPE_ODD: {
// 加载奇数项布局
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.layout_listview_item_type1, parent, false);
TextView tv1 = convertView.findViewById(R.id.tv_item1);
tv1.setText(getItem(position).getTitle());
break;
}
// 偶数选项
case TYPE_EVEN: {
// 加载偶数项布局
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.layout_listview_item_type2, parent, false);
ImageView iv2 = convertView.findViewById(R.id.iv_item2);
TextView tv2 = convertView.findViewById(R.id.tv_item2);
iv2.setBackgroundResource(getItem(position).getIcon());
tv2.setText(getItem(position).getTips());
break;
}
default:
break;
}
return convertView;
}
}
/**
* 定义列表数据对象.
*/
public class DataBean {
// 图标
private int icon;
// 标题
private String title;
// 内容
private String tips;
public DataBean(int icon, String title, String tips) {
this.icon = icon;
this.title = title;
this.tips = tips;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTips() {
return tips;
}
public void setTips(String tips) {
this.tips = tips;
}
}
}
- 效果图
# 结束
本节介绍了 Android 中默认的列表控件(ListView)常用属性以及相关使用方法,自定义实现的布局和适配器的方法,更多属性和用法请参考:ListView (opens new window)。