一. 前言

1. 什么是 ListView ?

它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示。

1
2
3
4
5
6
java.lang.Object
↳android.view.View
↳android.view.ViewGroup
↳android.widget.AdapterView<T extends android.widget.Adapter>
↳android.widget.AbsListView
↳android.widget.ListView

二. ListView的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android:cacheColorHint="#00000000":去除listview的拖动背景色

android:listSelector="#00000000":设置item被选中之后的颜色

android:divider:分割线的颜色
android:dividerHeight:分隔线的高度

android:scrollbars="none" 取消右侧滑动条

android:entries:对将填充ListView的数组资源的引用。

android:footerDividersEnabled:当设置为false时,ListView不会在每个页脚视图之前绘制分隔符。
android:headerDividersEnabled:当设置为false时,ListView不会在每个标题视图之后绘制分隔符。


addHeaderView()方法:主要是向listView的头部添加布局。
addFooterView()方法:主要是向listView的底部添加布局。

三. ListView的Adapter

1. ArrayAdapter

用来绑定一个数组,支持泛型操作,最简单的一个Adapter,只能展现一行文字。

2. SimpleAdapter

用来绑定在xml中定义的控件对应的数据,同样具有良好扩展性的一个Adapter,可以自定义多种效果。

3. SimpleCursorAdapter

用来绑定游标得到的数据。可以认为是 SimpleAdapter 和数据库的简单集合。

4. BaseAdapter

通用的基础适配器,抽象类。实际开发中我们会继承这个类并且重写BaseAdapter的四个方法,可以完成自己定义的Adapter,可以将任何复杂组合的数据和资源,以任何你想要的显示效果展示给大家用得最多的一个Adapter

使用参考:Android ListView入门知识–各种Adapter配合使用

四. BaseAdapter的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
//返回item的数量
}

@Override
public Object getItem(int position) {
//返回对应位置的item
}

@Override
public long getItemId(int position) {
//返回对应位置item的id
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
//返回对应位置item的view
}
}

1. MVC的设计思想

M-Model:模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Model:好友模型
*/
public class FriendBean {
//头像资源id
public int icon_id;
//好友名称
public String name;

/**
* 构造方法
*/
public FriendBean(int icon_id, String name) {
this.icon_id = icon_id;
this.name = name;
}
}

V-View:视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<!--View:视图-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<swu.xl.listview.XLImageView
android:tag="@string/icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitXY"
/>

<TextView
android:tag="@string/name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#000"
/>

</LinearLayout>

C-Controller:控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Controller:控制器
*/
public class FriendItem {
//item对应的模型
private FriendBean bean;

//item对应的视图
private int layout;

//存储关联好的视图
private View item_view;

/**
* 构造方法
*/
public FriendItem(FriendBean bean, Context context) {
this.bean = bean;
this.layout = R.layout.friend_layout;

//关联
initView(context);
}

//将模型和视图关联
private void initView(Context context){
//1.获取布局
View inflate = LayoutInflater.from(context).inflate(layout, null);

//2.获取子视图
ImageView icon_view = inflate.findViewWithTag(context.getResources().getString(R.string.icon));
TextView name_view = inflate.findViewWithTag(context.getResources().getString(R.string.name));

//3.设置数据
icon_view.setImageResource(bean.icon_id);
name_view.setText(bean.name);

//4.保存视图
item_view = inflate;
}

//get方法
public View getItem_view() {
return item_view;
}
}

2. 数据管理

数据的来源有很多,我们需要用一个单例类来管理我们的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 数据管理者
*/
public class DataManager {
//数据
private List<FriendBean> beans;

//单例模式
private static DataManager instance = null;
//私有化构造方法
private DataManager(){
loadData();
}
public static synchronized DataManager getDataManager(){
if (instance == null){
instance = new DataManager();
}
return instance;
}

//加载数据
private void loadData() {
beans = DataUtil.loadData();
}

//get方法
public List<FriendBean> getBeans() {
return beans;
}
}

3. 加载数据的方式

加载数据的方式有很多,为了便于扩展,才有了这个工具类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 数据加载工具类
*/
public class DataUtil {
/**
* 正常加载
* @return
*/
public static List<FriendBean> loadData(){
List<FriendBean> friendBeans = new ArrayList<>();

for (int i = 0; i < 30; i++) {
FriendBean friendBean = new FriendBean(R.drawable.ic_launcher_background, "第" + i + "个");
friendBeans.add(friendBean);
}

return friendBeans;
}

/**
* 本地文件加载数据
* @return
*/
public static List<FriendBean> loadDateByFile(){
return null;
}

/**
* 数据库加载数据
* @return
*/
public static List<FriendBean> loadDateBySQL(){
return null;
}

/**
* 网络加载数据
* @return
*/
public static List<FriendBean> loadDateByServer(){
return null;
}

}

4. ListView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 自定义的ListView
*/
public class XLListView extends ListView {


/**
* 构造方法:Java代码初始化
* @param context
*/
public XLListView(Context context) {
super(context);

//初始化操作
initData();
}

/**
* 构造方法:Xml代码初始化
* @param context
* @param attrs
*/
public XLListView(Context context, AttributeSet attrs) {
super(context, attrs);

//初始化操作
initData();
}

/**
* 初始化操作-设置适配器
*/
private void initData() {
setAdapter(new MyAdapter());
}

/**
* 自定义的适配器
*/
private class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
return DataManager.getDataManager().getBeans().size();
}

@Override
public Object getItem(int position) {
return DataManager.getDataManager().getBeans().get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
//获取模型
FriendBean bean = DataManager.getDataManager().getBeans().get(position);
//绑定视图和模型
FriendItem item = new FriendItem(bean, getContext());

return item.getItem_view();
}
}
}

5. 简单的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.listview.XLListView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</RelativeLayout>

6. Github地址

ListView

五. ListView的优化

1. 使用convertView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class FriendItem {
//item对应的模型
private FriendBean bean;
//item对应的视图
private int layout;

//存储关联好的视图
private View item_view;

/**
* 构造方法
*/
public FriendItem(FriendBean bean, Context context, View convertView) {
this.bean = bean;
this.layout = R.layout.friend_layout;

//关联
initView(context,convertView);
}

//将模型和视图关联
private void initView(Context context,View convertView){
//1.获取布局
if (convertView == null){
convertView = LayoutInflater.from(context).inflate(layout, null);
}

//2.获取子视图
ImageView icon_view = convertView.findViewWithTag(context.getResources().getString(R.string.icon));
TextView name_view = convertView.findViewWithTag(context.getResources().getString(R.string.name));

//3.设置数据
icon_view.setImageResource(bean.icon_id);
name_view.setText(bean.name);

//4.保存视图
item_view = convertView;
}

//get方法
public View getItem_view() {
return item_view;
}
}
1
2
3
4
5
6
7
8
9
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//获取模型
FriendBean bean = DataManager.getDataManager().getBeans().get(position);
//绑定视图和模型
FriendItem item = new FriendItem(bean, getContext(), convertView);

return item.getItem_view();
}

2. ViewHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* Controller:控制器
*/
public class FriendItem {
//item对应的模型
private FriendBean bean;
//item对应的视图
private int layout;

//存储关联好的视图
private View item_view;

/**
* 构造方法
*/
public FriendItem(FriendBean bean, Context context, View convertView) {
this.bean = bean;
this.layout = R.layout.friend_layout;

//关联
initView(context,convertView);
}

//将模型和视图关联
private void initView(Context context,View convertView){
//1.获取布局
ViewHolder viewHolder;
if (convertView == null){
//2.获取子视图

//加载布局
convertView = LayoutInflater.from(context).inflate(layout, null);
//完善ViewHolder
viewHolder = new ViewHolder();
viewHolder.icon_view = convertView.findViewWithTag(context.getResources().getString(R.string.icon));
viewHolder.name_view = convertView.findViewWithTag(context.getResources().getString(R.string.name));
convertView.setTag(viewHolder);
}else {
viewHolder = (ViewHolder) convertView.getTag();
}

//3.设置数据
viewHolder.icon_view.setImageResource(bean.icon_id);
viewHolder.name_view.setText(bean.name);

//4.保存视图
item_view = convertView;
}

/**
* 内部类:ViewHolder
*/
private class ViewHolder{
public ImageView icon_view;
public TextView name_view;
}

//get方法
public View getItem_view() {
return item_view;
}

}

优化的方式都是尽量避免多次inflate和findView这些耗时操作。

六. 监听点击的item

1. 监听item被点击

1
2
3
4
5
6
7
8
XLListView listView = findViewById(R.id.list_view);

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "位置"+position+"被点击了", Toast.LENGTH_SHORT).show();
}
});

2. 监听item被长按了

1
2
3
4
5
6
7
8
9
XLListView listView = findViewById(R.id.list_view);

listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "位置"+position+"被长按了", Toast.LENGTH_SHORT).show();
return true;
}
});

七. 刷新ListView

1. 重写getAdapter获取自己写的Adapter类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* 自定义的ListView
*/
public class XLListView extends ListView {

//适配器
private MyAdapter adapter;

/**
* 构造方法:Java代码初始化
* @param context
*/
public XLListView(Context context) {
super(context);

//初始化操作
initData();
}

/**
* 构造方法:Xml代码初始化
* @param context
* @param attrs
*/
public XLListView(Context context, AttributeSet attrs) {
super(context, attrs);

//初始化操作
initData();
}

/**
* 初始化操作-设置适配器
*/
private void initData() {
adapter = new MyAdapter();
setAdapter(adapter);
}

/**
* 自定义的适配器
*/
public class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
return DataManager.getDataManager().getBeans().size();
}

@Override
public Object getItem(int position) {
return DataManager.getDataManager().getBeans().get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
//获取模型
FriendBean bean = DataManager.getDataManager().getBeans().get(position);
//绑定视图和模型
FriendItem item = new FriendItem(bean, getContext(), convertView);

return item.getItem_view();
}
}

//set,get方法
@Override
public MyAdapter getAdapter() {
return adapter;
}
}

2. 调用 Adapter类 的notifyDataSetChanged方法刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

final XLListView listView = findViewById(R.id.list_view);

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "位置"+position+"被点击了", Toast.LENGTH_SHORT).show();

XLListView.MyAdapter adapter = listView.getAdapter();
if (position == 0){
//更新数据
List<FriendBean> beans = DataManager.getDataManager().getBeans();
if (beans.size() == 1){
//更新数据
beans.clear();
beans.addAll(DataUtil.loadData());//不能使用beans = DataUtil.loadData();
//刷新
adapter.notifyDataSetChanged();
}else {
//更新数据
beans.clear();
beans.add(new FriendBean(R.drawable.ic_launcher_foreground,"新的item"));
//刷新
adapter.notifyDataSetChanged();
}
}
}
});
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "位置"+position+"被长按了", Toast.LENGTH_SHORT).show();
return true;
}
});
}
}

3. 运行结果

4. 参考:

ListView的动态刷新问题——用notifyDataSetChanged没作用

Android开发中更新UI时runOnUIthread(Runnable)与Handler.post(Runnable)的区别及如何选择

Adapter的notifyDataSetInvalidated()和notifyDataSetChanged()的区别

七. 小问题

1. 设置每一个item的高度

如何设置listview每个item高度

2. 取消item被点击的水波纹效果

ListView布局中加上android:listSelector=”@android:color/transparent” 。

八. 进阶

1. 风格不同的item

参考:Android之ListView的getItemViewType和getViewTypeCount

参考文章

这个控件你必须会用!—ListView+GirdView

ListView中BaseAdapter的详细使用以及优化