今天给大家讲讲android开发中比较常见的listView的下拉加载,其实也可以叫做分页加载。为什么会有这个叫法呢?说说我的理解吧!
- 从字面上很好理解。当你滑动一个列表到底部的时候,这个时候就会出现正在加载的底部加载布局去加载更多的数据。这里拿微信作为一个例子,如下图所示:
如上图所示红色方框的部分就是底部的加载布局。从一定程度上这样做是必要的,为了优化用户的体验。你可以想想,假如你点开微信的朋友圈的时候,如果没有做分页加载,那么你需要等待很久的时间才能够看到你和朋友发的一些状态。这是因为这个时候代码需要给服务器发送请求获取数据并呈现这些数据。这个时候获取数据就成了最耗时的问题。为什么这么说?如果没有分页加载的功能,那么,你肯定需要获取全部的数据,你可以想想,好几年的数据,数据量可不少啊!发请求去获取这些数据并呈现它们是会很耗时间的。所以分页加载就很好的解决了这个问题。你先获取其中的十几条数据并呈现它们。当你一直从头浏览这十几条数据到结束的时候,这个时候再去加载更多的数据并呈现它们。如上图所示,这样才是好的用户体验。
- 概念清晰后,具体实现的部分到了。这里实现微信的下拉加载效果(个人感觉很不错,简洁明了),大概需要以下几个步骤:
- first — 首先构建下拉加载时的底部加载布局(这里命名为view_more)
- second — 获取数据并给listView设置adapter;设置好adapter后,调用方法:
listView.addFooterView(view_more);// TODO 添加底部记载布局
再往listView的底部添加底部加载布局。最后对listView设置滑动监听器:listView.setOnScrollListener(this);// TODO listView这是滑动监听
并重写相关方法:
public void onScrollStateChanged(AbsListView view, int scrollState) {}
和public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {}
这两个重写的方法一个是监听listView的滑动状态改变,另一个是监听listView的滑动。 - third — 当滑动监听设置好后,在重写的两个方法里面判断listView是否滑动到底部并且停止滑动(即滑动状态为停止滑动)。如果此时满足这个条件,那么就加载更多的数据。数据加载完成后就刷新数据源,此时会有更多的数据在底部出现。
- fourth — 最后当数据全部加载好后,移除第二步中添加的底部加载布局view_more,调用方法:
listView.removeFooterView(view_more);// TODO 移除底部的加载布局
下面写代码一步一步来实现:
- 1、首先准备一个底部加载布局,命名为view_more
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#8888" >
<TextView
android:id="@+id/tv_Load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="正在加载..."
android:textSize="14sp"
android:visibility="visible" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/tv_Load"
android:padding="10dp"
android:visibility="visible" />
</RelativeLayout>
很简单的布局,显示了一个进度条和一个文本控件。如图:
- 第二,给listView设置数据并设置滑动监听,设置好后判断是否滑动到listView的底部并停止滑动,如果是那么加载更多的数据:
package com.example.drop_down_load;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity implements OnScrollListener {
private ListView listView;
private int totalCount;// 数据总条数
private List<String> lists = new ArrayList<String>();
private ArrayAdapter<String> adapter;
// 创建handler接收消息并处理消息
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 创建adapter
adapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, lists);
// 设置adapter
listView.setAdapter(adapter);
// 添加底部加载布局
listView.addFooterView(view_more);
// 设置监听
setListeners();
break;
}
};
};
private View view_more;
private ProgressBar pb;
private TextView tvLoad;
private int lastVisibleIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 控件初始化
initViews();
// 初始化数据
initData();
}
private void initData() {
// 模拟网络请求获取数据,一次获取15条
new Thread() {
public void run() {
try {
totalCount = 100;// 假设数据一共有100条,将来调接口可以获取到这个值
for (int i = 0; i < 15; i++) {
lists.add("数据" + (i + 1));
}
// 给handler发消息更新UI,子线程不可以更新UI
Message message = new Message();
message.what = 0;
handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
private void setListeners() {
if (totalCount > 15) {
// listView设置滑动简监听
listView.setOnScrollListener(this);
} else {
// 假如数据总数少于等于15条,直接移除底部的加载布局,不需要再加载更多的数据
listView.removeFooterView(view_more);
}
}
private void initViews() {
listView = (ListView) findViewById(R.id.listView);
// 构建底部加载布局
view_more = (View) getLayoutInflater()
.inflate(R.layout.view_more, null);
// 进度条
pb = (ProgressBar) view_more.findViewById(R.id.progressBar);
// “正在加载...”文本控件
tvLoad = (TextView) view_more.findViewById(R.id.tv_Load);
}
/**
* 监听listView的滑动状态的改变
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
Log.e("TAG", "lastVisibleIndex = " + lastVisibleIndex);
Log.e("TAG", "adapter.getCount() = " + adapter.getCount());
// 滑到底部后自动加载,判断listView已经停止滚动并且最后可视的条目等于adapter的条目
// 注意这里在listView设置好adpter后,加了一个底部加载布局。
// 所以判断条件为:lastVisibleIndex == adapter.getCount()
if (scrollState == SCROLL_STATE_IDLE
&& lastVisibleIndex == adapter.getCount()) {
/**
* 这里也要设置为可见,是因为当你真正从网络获取数据且获取失败的时候。
* 我在失败的方法里面,隐藏了底部的加载布局并提示用户加载失败。所以再次监听的时候需要
* 继续显示隐藏的控件。因为我模拟的获取数据,失败的情况这里不给出。实际中简单的加上几句代码就行了。
*/
pb.setVisibility(View.VISIBLE);
tvLoad.setVisibility(View.VISIBLE);
loadMoreData();// 加载更多数据
}
}
private void loadMoreData() {
}
/**
* 监听listView的滑动
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// 计算最后可见条目的索引
lastVisibleIndex = firstVisibleItem + visibleItemCount - 1;
// 当adapter中的所有条目数已经和要加载的数据总条数相等时,则移除底部的View
if (totalItemCount == totalCount + 1) {
// 移除底部的加载布局
listView.removeFooterView(view_more);
}
}
}
以上代码就是我上面步骤2和步骤2中所说的东西。加上我写了注释,所以相信大家看起来很简单。关键点是怎么判断listView是否滑动到了底部并停止了滑动。我在代码中添加了两行log,可以打印日志信息。大家去自己调试就能明白我对关键点是怎么判断的了,但是也有聪明的!嘿嘿!大家都懂,自己人,我就不接话了。如图:
- 最后就是加载更多的数据了,即完成第二步中未完成的方法:
loadMoreData();// 加载更多数据
具体代码实现为:
private void loadMoreData() {
// 获取此时adapter中的总条目数
int count = adapter.getCount();
// 一次加载15条数据,即下拉加载的执行
if (count + 15 < totalCount) {
start = count;
end = start + 15;
initData(start, end);// 模拟网络获取数据操作
} else {// 数据不足15条直接加载到结束
start = count;
end = totalCount;
initData(start, end);// 模拟网络获取数据曹祖
// 数据全部加载完成后,移除底部的view
listView.removeFooterView(view_more);
Toast.makeText(MainActivity.this, "数据已经全部加载", 1).show();
}
}
private void initData(final int start, final int end) {
// 模拟网络请求获取数据,一次获取15条
new Thread() {
public void run() {
try {
Thread.sleep(4000);// 模拟获取数据时的耗时3s
for (int i = start; i < end; i++) {
lists.add(i, "数据" + (i + 1));
}
// 给handler发消息更新UI,子线程不可以更新UI
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
此时handler中还要再加一个case语句,用来刷新数据源,如图红色方框标注地方,大家自己加一下。最后我会给项目源码,大家可以下载看我的代码:
好了下面给出几张项目的展示图片:
- 15条数据后加载更多数据,如图:
- 30条数据后加载更多数据,如图:
- 45条数据后加载更多数据,如图:
- 数据全部加载完后,移除底部加载布局,如图:
谈谈我的感悟:你需要有自己的想法思路,别人的东西可以借鉴,但是你需要从中学到点什么。要是什么都没学到,那就失去了分享的意义了!分享是为了让大家学到更多,收获更多。希望我的这篇能够给大家一点点收获!
项目的源码下载地址:点击我下载项目源码