ListView的分批加载

为什么需要分批加载呢,一般我们在浏览数据的时候,往往要浏览的数据是很多的,例如你上淘宝买东西,一搜索可能会有好几万条结果,但是这些数据如果一下子全部加载出来,先不说你手机内存是否吃的消,假如你是流量上网,那么你的流量会瞬间消耗多少呢,况且一句手机的网速,需要加载多久也不久不用我多说了把。

因此就需要分批加载,一般来说每次加载20条数据即可。

既然是分批加载,那么肯定得有加载数据的逻辑,根据封装的思想,最好能够传入起始加载位置和加载的数量就能够返回数据的集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 查询部分数据
* @param maxNum 需要查询的条目数量
* @param startIndex 查询的起始位置
* @return 查询到的部分数据的集合 list
*/
public List<BlackNumInfo> queryPartBlackNum(int maxNum,int startIndex){
List<BlackNumInfo> infoList = new ArrayList<>();
SQLiteDatabase database = blackNumOpenHelper.getReadableDatabase();
Cursor cursor = database.rawQuery("select blackNum,mode from info order by _id desc " +
"limit ? offset ?", new String[]{maxNum + "", startIndex + ""});

while (cursor.moveToNext()) {
String num = cursor.getString(0);
int mode = cursor.getInt(1);
infoList.add(new BlackNumInfo(num, mode));
}
cursor.close();
database.close();
return infoList;
}

有了加载部分数据的逻辑,我们还需要判断加载数据的时机,仅在需要的时候再加载下一批数据。因为我们的数据是用 ListView 来显示的,那么就需要从 ListView 的状态入手。我们可以认为,当用户滑到 ListView 最底部的时候,她还想继续浏览,这个时候我们就可以放心地加载下一批数据。

因此,加载的时机就是当 ListView 窗口显示的数据条目是其对应的 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
 /**
* 最大分批加载数量
*/
private static final int MAXNUM = 20;
/**
* 分批加载起始位置
*/
private int startIndex=0;
/**
* 为列表的滑动添加监听事件,当屏幕显示的最后一条数据是列表的最后一条数据时,加载下一批数据
*/
blackList.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//必须是处于静止状态下(或者说滑动停止后)
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
//AbsListView.OnScrollListener.SCROLL_STATE_IDLE表示列表属于空闲状态(静止)
//当屏幕显示的最后一条数据是列表的最后一条数据时,加载下一批数据
if (blackList.getLastVisiblePosition() == list.size() - 1) {
startIndex += MAXNUM;
//加载数据
fillData();
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}
});

接下来要做什么呢,时机到了,新的数据也查到了,那么就要把数据更新到 ListView 中。在这里需要注意一些小细节:

  • 当 ListView 中不存在数据和已经存在数据的情况;
  • ListView 的 adapter 为空和不为空的情况;

为什么要注意这两点呢,首先一开始的时候,我们也是要采用分批加载数据的方法才加载第一批数据的。其次如果 ListView 中已经存在数据,那么就不能简单的用新查询到的数据直接替换原来的 list 数据集合,否则原先的数据就会消失,而只剩下新查询到的数据,这可不是我们的本意;最后还得对 adapter 进行判断,如果 adapter 不为空,那么就得采用通知更新的方法,而不能直接为 list 集合设置新的 adapter,否则每次加载完数据,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
private void fillData() {
new MyAsycTask() {

@Override
public void preTask() {
//显示进度条
callLoading.setVisibility(View.VISIBLE);
}

@Override
public void doInBack() {
//若list已经存在数据,只需要将新的数据添加到原有数据集合中
if (list == null) {
list = blackNumDao.queryPartBlackNum(MAXNUM, startIndex);
}else {
list.addAll(blackNumDao.queryPartBlackNum(MAXNUM, startIndex));
}
}

@Override
public void postTask() {
//判断原来的adapter中是否已经存在数据,如果没有则重新设置,否则更新数据即可
if (myAdapter == null) {
myAdapter = new MyAdapter();
blackList.setAdapter(myAdapter);
} else {
myAdapter.notifyDataSetChanged();
}
callLoading.setVisibility(View.INVISIBLE);
}
}.execute();
}

至此,一个简单但却完整的分批加载框架就完成了。
完整的源码参见BlackNumDao.javaCallSafeActivity.java