ListView
- ListView是一种用来显示多个可滑动项列表的ViewGroup
- 需要使用Adapter将集合数据和每一个item所对应的布局动态适配到ListView中显示
- 显示列表:listView.setAdapter(adapter)
适配器
ListView
仅作为容器(列表),用于装载 & 显示数据(即 列表项Item
)- 而容器内的具体数据(列表项
Item
)则是由 适配器(Adapter
)提供
适配器(Adapter):作为View
和 数据之间的桥梁 & 中介,将数据映射到要展示的View
中
- 当需显示数据时,
ListView
会向Adapter
取出数据,从而加载显示,具体如下图
数组适配器 ArrayAdapter
适用于列表项只含有文本信息的情况
构建步骤:
创建数据源
final String[] items= {"java","c++","android","flutter","dart"};
创建适配器中的每一项数据的样式布局:
<!-- 适配器中每一项数据的样式布局 -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/star"/>
<TextView
android:id="@+id/arry_tx"
android:text="测试"
android:layout_marginLeft="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
实例化一个数组适配器
- 参数1 Context:环境上下文
- 参数2 resource:item布局资源索引
- 参数3 int textViewResourceId:指定数据中文本需要放在布局中的文本控件的id
- 参数4 objects:数据源
这是数组适配器的第三种构造方法,可以在单项里放置许多其他内容,此时不会因为根布局不是TextView而报错。
ArrayAdapter arrayAdapter = new ArrayAdapter(this, R.layout.array_item_layout,R.id.arry_tx,items);
将数组适配器绑定在对话框上
创建一个按钮,并添加点击事件,打开添加了适配器的对话框
<Button
android:id="@+id/adpter_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="popAlert"
android:text="数组适配器 ArrayAdapter" />
在对话框里添加适配器 .setAdapter()
- 参数1 - ListAdapter 适配器对象(对数据显示样式的规则制定器):The ListAdapter to supply the list of items
- 参数2 - OnClickListener 监听器 (可给可不给,不给则设为null)
// 实例化对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
ArrayAdapter arrayAdapter = new ArrayAdapter(this, R.layout.array_item_layout,R.id.arry_tx,items);
builder.setTitle("请选择")
/* .setAdapter() 设置适配器
* 参数1 - ListAdapter 适配器对象(对数据显示样式的规则制定器):The ListAdapter to supply the list of items
* 参数2 - OnClickListener 监听器 (可给可不给,不给则设为null)*/
.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
@Override
/*int which 当前点击的item索引*/
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(ButtonActivity.this,items[which],Toast.LENGTH_SHORT).show();
}
})
.show();
简单适配器 SimpleAdapter
既可以处理列表项全是文本的情况,又可以处理列表项包含其他控件(如图片、文本、按钮等)情况
集合数据数据必须是List<Map<String,Object>> 类型
使用步骤
-
准备数据源
private void initData() {
Map<String,Object> data1 = new HashMap<>();
data1.put("icon",R.mipmap.star);
data1.put("name","沈成林");
data1.put("age",23);
Map<String,Object> data2 = new HashMap<>();
data2.put("icon",R.mipmap.star);
data2.put("name","杨滨溶");
data2.put("age",23);
Map<String,Object> data3 = new HashMap<>();
data3.put("icon",R.mipmap.star);
data3.put("name","朱一龙");
data3.put("age",23);
data.add(data1);
data.add(data2);
data.add(data3);
}
-
准备布局(适配器每一项数据的显示样式布局)
R.layout.simpleadapeter_item:
<?xml version="1.0" encoding="utf-8"?>
<!-- SimpleAdapter 的item样式-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/headIcon"
android:layout_width="50dp"
android:layout_height="65dp"
android:background="@mipmap/study" />
<TextView
android:id="@+id/itemName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="28dp"
android:layout_toEndOf="@+id/headIcon"
android:text="name"
android:textSize="26sp" />
<TextView
android:id="@+id/itemAge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/itemName"
android:layout_marginLeft="30dp"
android:layout_marginTop="13dp"
android:layout_toRightOf="@id/headIcon"
android:text="TextView"
android:textColor="#F44336"
android:textStyle="bold" />
</RelativeLayout>
-
实例化一个简单适配器
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data, @LayoutRes int resource, String[] from, @IdRes int[] to)
参数1:环境上下文
参数2:数据源
参数3:每一项布局(列表项布局) R.layout.simpleadapeter_item
参数4:集合数据数据是List<Map<String,Object>> 类型,参数4就是map对象中的key数组,用来对应Map上的每个<key,value>的key值.
参数5:是一个int数组,对应resource列表项布局文件里面每个控件的id,需要与上面String[] from的from相对应.
key所指代的数据会在to数组中id所代表的控件上显示出来
String[] from = {"icon","name","age"}; // 参数4
int[] to = {R.id.headIcon,R.id.itemName,R.id.itemAge}; // 参数5
SimpleAdapter simpleAdapter = new SimpleAdapter(this,data,R.layout.simpleadapeter_item,from,to);
通过ListView来展示
1.添加ListView控件,xml文件
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4">
<ListView
android:id="@+id/list1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
2.为ListView控件绑定 SimpleAdapter
ListView listView = findViewById(R.id.list1);
listView.setAdapter(simpleAdapter);
3.为listView设置item点击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
/*数据源data是一个List<Map<String,Object>>的列表,
*onItemClick方法,返回的position就是当前点击的 item 的位序,通过它可以拿到item的数据源
*view 就是点击的item的视图*/
Map<String,Object> data1 = data.get(position);
Toast.makeText(ListViewActivity.this,"姓名"+data1.get("name")+",年龄:"+data1.get("age"),Toast.LENGTH_SHORT).show();
}
});
基本适配器 BaseAdapter
BaseAdapter是一个抽象类。
根据xml文件中定义的样式进行列表项的填充,完全自定义数据适配方式,适用性最强
集合数据可以是List<xxxx> 类型,xxxx自定义类型
准备列表项布局
R.layout.baseadapter_item
<?xml version="1.0" encoding="utf-8"?>
<!-- 1. BaseAdapter item的样式设计-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/base_icon"
android:src="@mipmap/study"
android:layout_width="80dp"
android:layout_margin="5dp"
android:layout_height="80dp"/>
<TextView
android:id="@+id/base_name"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="nickName"
android:layout_toRightOf="@+id/base_icon"
android:layout_margin="5dp"
android:textSize="26sp" />
<TextView
android:id="@+id/base_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_alignStart="@+id/base_name"
android:layout_toRightOf="@+id/base_icon"
android:layout_below="@+id/base_name"
android:textSize="22sp"
android:text="2023-12-4" />
<TextView
android:id="@+id/base_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/base_icon"
android:textSize="26sp"
android:text="这是内容" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/base_content"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/base_icon1"
android:layout_width="40dp"
android:layout_height="50dp"
android:layout_weight="1"
android:src="@mipmap/star" />
<ImageView
android:id="@+id/base_icon2"
android:layout_width="40dp"
android:layout_height="50dp"
android:layout_weight="1"
android:src="@mipmap/star" />
<ImageView
android:id="@+id/base_icon3"
android:layout_width="40dp"
android:layout_height="50dp"
android:layout_weight="1"
android:src="@mipmap/star" />
</LinearLayout>
</RelativeLayout>
准备数据源
自定义一个数据对象 BaseMsg类
package com.example.androidstudiostudy.data;
// 2.1 准备数据源 - BaseAdapter 的数据源类
public class BaseMsg {
private int icon;
private String nickName;
private String content;
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isLike() {
return isLike;
}
public void setLike(boolean like) {
isLike = like;
}
private boolean isLike;
public BaseMsg(int icon, String nickName, String content, boolean isLike) {
this.icon = icon;
this.nickName = nickName;
this.content = content;
this.isLike = isLike;
}
}
初始化一个 BaseMsg 列表
private List<BaseMsg> lists = new ArrayList<>(); // 定义一个 BaseMsg 对象的列表
private int[] touxiang ={R.mipmap.star,R.mipmap.study,R.mipmap.star,R.mipmap.study,R.mipmap.star,R.mipmap.study,R.mipmap.star,R.mipmap.study,}; // 定义一个int类型的数组,里面存放 图标的id
// 初始化 BaseMsg 对象的列表
public void initBaseListData() {
for (int i = 0; i < 8; i++) {
BaseMsg baseMsg = new BaseMsg(touxiang[i], "用户" + (i+1), "这是第" + (i+1) + "段代码", false);
lists.add(baseMsg);
}
}
构建 自定义的 BaseAdapter 适配器
新建一个继承自 BaseAdapter 的类,在这个类中对每一个item的布局进行设计,要求传入 环境上下文、数据源
需要重写的方法:
- public View getView(int position, View convertView, ViewGroup parent)
返回指定下标所对应的item的view对象,获取视图(设置 listView 每一项的显示效果)
position:下标,当前Item的下标 --- 和数据源的下标相同,可以由此获取数据源配置item
converView:当前Item的view
parent:其实就是listView对象
public View getView(int position, View convertView, ViewGroup parent) {
// 加载item布局
convertView = LayoutInflater.from(context).inflate(R.layout.baseadapter_item,null); // 此时得到的是最初的item布局,没有添加的数据
// 获取数据源[position] 的数据,并将他们设置到item视图中的控件中
BaseMsg m = baseMsgList.get(position); // 获取item的数据列表
ImageView imageView = convertView.findViewById(R.id.base_icon);
imageView.setImageResource(m.getIcon());
// 找到 item 布局的控件,根据数据设置属性
TextView tN = convertView.findViewById(R.id.base_name);
tN.setText(m.getNickName());
TextView tC = convertView.findViewById(R.id.base_content);
tC.setText(m.getContent());
// 可以给item的单个控件设置点击事件
tN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// System.out.printf("点击了"+tN.getText());
Toast.makeText(context,"你点击了"+m.getNickName(),Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
- public int getCount()
- public Object getItem(int position)
- public long getItemId(int position)
package com.example.androidstudiostudy;
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 android.widget.Toast;
import com.example.androidstudiostudy.data.BaseMsg;
import java.util.List;
// 新建一个继承自 BaseAdapter 的类
// 根据准备好的数据源和子项布局完成 ListView 效果的一一设置
public class MyBaseAdapter extends BaseAdapter {
private List<BaseMsg> baseMsgList;
private Context context;
// 构造方法
public MyBaseAdapter(List<BaseMsg> baseMsgList,Context context) {
this.baseMsgList = baseMsgList;
this.context = context;
}
// 获取数量(设置listView的长度)
@Override
public int getCount() {
return baseMsgList.size();
}
@Override
// 获取视图(设置 listView 每一项的显示效果)
/* 参数1:当前Item的下标 --- 和数据源的下标相同,可以由此获取数据源配置item
* 参数2:当前Item的view
* 参数3:当前视图的父视图(可调整当前视图的宽高)*/
public View getView(int position, View convertView, ViewGroup parent) {
// 完成对view的设置
// 将设置好的 item 布局资源转换成view
convertView = LayoutInflater.from(context).inflate(R.layout.baseadapter_item,null); // 此时得到的是最初的item布局,没有添加的数据
// 获取数据源[position] 的数据,并将他们设置到item视图中的控件中
BaseMsg m = baseMsgList.get(position); // 获取item的数据列表
ImageView imageView = convertView.findViewById(R.id.base_icon);
imageView.setImageResource(m.getIcon());
TextView tN = convertView.findViewById(R.id.base_name);
tN.setText(m.getNickName());
TextView tC = convertView.findViewById(R.id.base_content);
tC.setText(m.getContent());
// 可以给item的单个控件设置点击事件
tN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// System.out.printf("点击了"+tN.getText());
Toast.makeText(context,"你点击了"+m.getNickName(),Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
}
实例化 BaseAdapter 适配器
BaseAdapter baseAdapter = new MyBaseAdapter(lists,this);
通过ListView展示
initBaseListData();
listView.setAdapter(baseAdapter);
动态添加item
// 动态添加 BaseAdapter 数据源中的数据
public void addData(View view) {
lists.add(new BaseMsg(R.mipmap.ic_launcher,"新name","这是新增的",false));
// 通知适配器更新
baseAdapter.notifyDataSetChanged();
// 设置listView自动显示到最新的数据
listView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
}
列表自动滑动到底部 :TRANSCRIPT_MODE_ALWAYS_SCROLL
/**
* The list will automatically scroll to the bottom, no matter what items
* are currently visible.
*
* @see #setTranscriptMode(int)
*/
// 列表自动滑动到底部
public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
ListView 优化
ListView 绑定的适配器是 BaseAdapter(自定义适配器)。
// 获取视图(设置 listView 每一项的显示效果)
/* 参数1:当前Item的下标 --- 和数据源的下标相同,可以由此获取数据源配置item
* 参数2:当前Item的view
* 参数3:当前视图的父视图(可调整当前视图的宽高)*/
public View getView(int position, View convertView, ViewGroup parent) {
// 完成对view的设置
// 将设置好的 item 布局资源转换成view
convertView = LayoutInflater.from(context).inflate(R.layout.baseadapter_item,null); // 此时得到的是最初的item布局,没有添加的数据
// 获取数据源[position] 的数据,并将他们设置到item视图中的控件中
BaseMsg m = baseMsgList.get(position); // 获取item的数据列表
ImageView imageView = convertView.findViewById(R.id.base_icon);
imageView.setImageResource(m.getIcon());
TextView tN = convertView.findViewById(R.id.base_name);
tN.setText(m.getNickName());
TextView tC = convertView.findViewById(R.id.base_content);
tC.setText(m.getContent());
// 可以给item的单个控件设置点击事件
tN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// System.out.printf("点击了"+tN.getText());
Toast.makeText(context,"你点击了"+m.getNickName(),Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
问题一:优化view对象
在MyAdapter类中的getView方法中,我们注意到,上面的写法在每个视图出现时都会执行,有多少个item就会调用多少次 getView() 如果item太多就会很浪费资源!!
优化:可以复用convertView
- 在 getView 方法中,首先检查 convertView 是否为空。
- 如果为空,表示当前屏幕上没有可复用的 item 视图,需要创建新的视图并进行布局设置。
- 但是,如果 convertView 不为空,就意味着该视图已经被创建过,并且已经在屏幕上显示过,这时我们可以直接复用 convertView,避免了重复的布局加载和赋值操作
public View getView(int position, View convertView, ViewGroup parent) {
// 优化1:利用进入 RecyclerBin 中的view,减少对view的赋值
/* 当视图第一次构建后,上下滑动到看不见时就会进入 RecyclerBin,此时 convertView 就不为null,
* 我们就可以复用这个 convertView */
if( convertView == null){
// 完成对view的设置
// 将设置好的 item 布局资源转换成view
convertView = LayoutInflater.from(context).inflate(R.layout.baseadapter_item,null); // 此时得到的是最初的item布局,没有添加的数据
System.out.println("当前显示的视图"+(position+1));
}
// 获取数据源[position] 的数据,并将他们设置到item视图中的控件中
BaseMsg m = baseMsgList.get(position); // 获取item的数据列表
ImageView imageView = convertView.findViewById(R.id.base_icon);
imageView.setImageResource(m.getIcon());
TextView tN = convertView.findViewById(R.id.base_name);
tN.setText(m.getNickName());
TextView tC = convertView.findViewById(R.id.base_content);
tC.setText(m.getContent());
// 可以给item的单个控件设置点击事件
tN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// System.out.printf("点击了"+tN.getText());
Toast.makeText(context,"你点击了"+m.getNickName(),Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
问题二:优化view中组件的查找
即使经过优化1的步骤,可以减少view的构建,但是每出现一个view已经会寻找一次控件id,findViewById 依旧十分浪费资源,因此可以进行第二步优化
优化:利用viewHolder
在convertView为null的时候,我们不仅重新inflate出来一个view,并且还需要进行findviewbyId的查找工作,但是同时我们还需要获取一个ViewHolder类的对象,并将findviewById的结果赋值给ViewHolder中对应的成员变量。最后将holder对象与该view对象“绑”在一块--setTag方法
当convertView不为null时,我们让view=converView,同时取出这个view对应的holder对象--getTag方法,就获得了这个view对象中的TextView组件
static class ViewHolder {
public ImageView icon;
public TextView nickName, content;
}
public View getView(int position, View convertView, ViewGroup parent) {
// 优化1:利用进入 RecyclerBin 中的view,减少对view的赋值
/* 当视图第一次构建后,上下滑动到看不见时就会进入 RecyclerBin,此时 convertView 就不为null,
* 我们就可以复用这个 convertView */
// 即使经过优化1的步骤,可以减少view的构建,但是每出现一个view已经会寻找一次控件id,findViewById 依旧十分浪费资源,因此可以进行第二步优化
// 1.定义一个 内部类 ViewHolder,并声明需要用到的控件属性
// 2.当 convertView == null 当前视图没有被创建时,初始化一个 ViewHolder 对象,并为其中的控件赋值
// 3.调用 contvertView.setTag(ViewHolder) 将这个 HolderView 保存到这个view中
// 4.当 convertView != null时,通过 contvertView.getTag() 找到保存的ViewHolder
ViewHolder holder;
if (convertView == null) {
// 完成对view的设置
// 将设置好的 item 布局资源转换成view
convertView = LayoutInflater.from(context).inflate(R.layout.baseadapter_item, null); // 此时得到的是最初的item布局,没有添加的数据
holder = new ViewHolder();
holder.icon = convertView.findViewById(R.id.base_icon);
holder.nickName = convertView.findViewById(R.id.base_name);
holder.content = convertView.findViewById(R.id.base_content);
convertView.setTag(holder);
System.out.println("当前显示的视图" + (position + 1));
} else {
holder = (ViewHolder) convertView.getTag(); // 此时要强转成 ViewHolder 类型
}
// 获取数据源[position] 的数据,并将他们设置到item视图中的控件中
BaseMsg m = baseMsgList.get(position); // 获取item的数据列表
/*ImageView imageView = convertView.findViewById(R.id.base_icon);
imageView.setImageResource(m.getIcon());*/
holder.icon.setImageResource(m.getIcon());
/*TextView tN = convertView.findViewById(R.id.base_name);
tN.setText(m.getNickName());*/
holder.nickName.setText(m.getNickName());
/*TextView tC = convertView.findViewById(R.id.base_content);
tC.setText(m.getContent());*/
holder.content.setText(m.getContent());
// 可以给item的单个控件设置点击事件
holder.nickName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "你点击了" + m.getNickName(), Toast.LENGTH_SHORT).show();
}
});
return convertView;
}