无疑,在Android开发中,ListView是使用非常频繁的控件之一,ListView提供一个列表的容易,允许我们以列表的形式将数据展示到界面上,但是Google给我们提供的原生ListView的控件,虽然在功能上很强大,但是在用户体验和动态效果上,还是比较差劲的。为了改善用户体验,市面上纷纷出现了各种各样的自定义的ListView,他们功能强大,界面美观,使我们该需要学习的地方。其中,使用最频繁的功能无疑就是ListView的下拉刷新和上拉加载数据了,几乎在每一款内容型的App中都可以找到这种控件的身影,尤其是需要联网获取数据的模块,使用的就更为频繁了,so,我们很有必要了解下这种效果是怎么实现的。
说白了实现ListView的下拉刷新和上拉加载数据的功能无非是对ListView实现加header和加footer,在适当的时候分别显示header和footer,然后又在适当的时候隐藏header和footer。主要是根据ListView的setPadding、addHeaderView和addFooterView等方法来实现。AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.
下面就ListView和AsyncTask如何实现数据加载和下拉刷新和上拉加载数据的具体代码,代码注释很详细:
源码下载源码下载
自定义MyListView :
package cn.zxw.pull.refresh.view;
import java.text.SimpleDateFormat;
import java.util.logging.SimpleFormatter;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import cn.zxw.pull.refresh.R;
public class MyListView extends ListView implements OnScrollListener {
// 加载数据的接口
public interface OnListViewLoadDataListener {
public void onLoadNewData();
// 加载更多数据
public void onLoadMoreData();
}
private OnListViewLoadDataListener onListViewLoadDataListener;
public void setOnListViewLoadDataListener(
OnListViewLoadDataListener onListViewLoadDataListener) {
this.onListViewLoadDataListener = onListViewLoadDataListener;
}
private static final String TAG = "MyListView";
private View header;
private ImageView iv_arrow;
private ProgressBar pb;
private TextView tv_state;
private TextView tv_time;
private int height;
enum HeaderState {
PULL_DOWN_REFRESH, // 下拉刷新
RELEASE_REFRESH, // 释放刷新
REFRESHING// 正在刷新
}
// 设置默认状态
private HeaderState state = HeaderState.PULL_DOWN_REFRESH;
// ListView头文字的信息
private String[] stateInfos = new String[] { "下拉刷新", "释放刷新", "正在刷新" };
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
initHead();
initFooter();
setOnScrollListener(this);
}
/**
* 初始化footer
*/
private void initFooter() {
footer = View.inflate(getContext(), R.layout.footer, null);
footer.measure(0, 0);
footerHeight = footer.getMeasuredHeight();
footer.setPadding(0, -footerHeight, 0, 0);
addFooterView(footer);
}
/**
* 初始化listview的头部
*/
private void initHead() {
header = View.inflate(getContext(), R.layout.header, null);
iv_arrow = (ImageView) header.findViewById(R.id.iv_arrow);
pb = (ProgressBar) header.findViewById(R.id.pb);
tv_state = (TextView) header.findViewById(R.id.tv_state);
tv_time = (TextView) header.findViewById(R.id.tv_time);
pb.setVisibility(View.INVISIBLE);// 隐藏pb
addHeaderView(header);// 添加header,然后要隐藏header
//int height = header.getHeight(); 如果一个控件没有显示 是无法获取宽高 layout
header.measure(0, 0);//Android中控件的位置必须先测量
height = header.getMeasuredHeight();//获取控件的高度
header.setPadding(0, -height, 0, 0);// 隐藏控件,即是将控件设置在屏幕顶部之外
}
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean isLoadingData = false;// 是否加载更多数据
// 滑动状态改变
// 闲置和飞行状态 listview必须是已经显示到最后下处理
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
int lastVisiblePosition = getLastVisiblePosition();// 获取listview最后显示的下标
int count = getAdapter().getCount();
// 闲置状态和飞行状态 还必须是ListView已经显示到最后 没有在加载数据
if ((scrollState == OnScrollListener.SCROLL_STATE_IDLE || scrollState == OnScrollListener.SCROLL_STATE_FLING)
&& lastVisiblePosition == count - 1 && !isLoadingData) {
isLoadingData = true;
footer.setPadding(0, 0, 0, 0);
// 脚其实还是没有显示
setSelection(count);
if (onListViewLoadDataListener != null) {
onListViewLoadDataListener.onLoadMoreData();
}
}
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:// 闲置
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:// 滑动
break;
case OnScrollListener.SCROLL_STATE_FLING:// 飞行
break;
default:
break;
}
}
/**
* firstVisibleItem 屏幕上显示的第一个条目的下标
*visibleItemCount 屏幕上显示的条目总数
* totalItemCount 条目总数
*/
private int firstVisibleItem;// 记录第一个条目位置
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
this.firstVisibleItem = firstVisibleItem;
Log.i(TAG, "firstVisibleItem:" + firstVisibleItem);
}
private int startY;
private int footerHeight;
private View footer;
// 滑动改变header
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "ACTION_DOWN");
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "ACTION_MOVE");
// 显示的是第一个条目
if (firstVisibleItem == 0 && state != HeaderState.REFRESHING) {
int moveY = (int) ev.getY();
int disY = moveY - startY;
// 控制header的显示
int top = -height + disY;
if (top > 0 && state == HeaderState.PULL_DOWN_REFRESH) {// 原来是下拉刷新
// 现在置为释放刷新
state = HeaderState.RELEASE_REFRESH;
RotateAnimation animation = getRotateAnimation1();
iv_arrow.setAnimation(animation);
tv_state.setText(stateInfos[1]);
} else if (top <= 0 && state == HeaderState.RELEASE_REFRESH) {
// 原来是释放刷新 现在变为下拉刷新
state = HeaderState.PULL_DOWN_REFRESH;
RotateAnimation animation = getRotateAnimation2();
iv_arrow.setAnimation(animation);
tv_state.setText(stateInfos[2]);
}
header.setPadding(0, top, 0, 0);
}
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "ACTION_UP");
// 用户松开手指:1 下拉刷新 2 释放刷新
if (state == HeaderState.PULL_DOWN_REFRESH) {
// 把头直接隐藏
header.setPadding(0, -height, 0, 0);
} else if (state == HeaderState.RELEASE_REFRESH) {
// 把头显示在第一行 作为一个正常的显示
// 把头的状态进行改变
header.setPadding(0, 0, 0, 0);
state = HeaderState.REFRESHING;
iv_arrow.clearAnimation();// 清除动画
iv_arrow.setVisibility(View.INVISIBLE);
// 箭头无法隐藏:iv_arrow有动画
pb.setVisibility(View.VISIBLE);
tv_state.setText(stateInfos[2]);
// 通知activity加载数据
if (onListViewLoadDataListener != null) {
onListViewLoadDataListener.onLoadNewData();
}
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
public RotateAnimation getRotateAnimation1() {
RotateAnimation rotateAnimation = new RotateAnimation(0, -180,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
rotateAnimation.setDuration(200);
rotateAnimation.setFillAfter(true);
return rotateAnimation;
}
public RotateAnimation getRotateAnimation2() {
RotateAnimation rotateAnimation = new RotateAnimation(-180, -360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
rotateAnimation.setDuration(200);
rotateAnimation.setFillAfter(true);
return rotateAnimation;
}
// 隐藏头
// 1.状态改为下拉刷新
// 2.隐藏进度条 显示箭头 修改文字
// 3.刷新的事件
// 4.header设置padding
public void hideHeader() {
state = HeaderState.PULL_DOWN_REFRESH;
pb.setVisibility(View.INVISIBLE);
iv_arrow.setVisibility(View.VISIBLE);
tv_state.setText(stateInfos[0]);
long mills = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String dateStr = format.format(mills);
tv_time.setText("上次刷新的时间为:" + dateStr);
header.setPadding(0, -height, 0, 0);
}
// 隐藏footer
public void hideFooter() {
footer.setPadding(0, -footerHeight, 0, 0);
isLoadingData = false;
}
}
MainActivity:
package cn.zxw.pull.refresh;
import java.util.ArrayList;
import java.util.List;
import cn.zxw.pull.refresh.view.MyListView;
import cn.zxw.pull.refresh.view.MyListView.OnListViewLoadDataListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.app.Activity;
import android.widget.ArrayAdapter;
public class MainActivity extends Activity implements
OnListViewLoadDataListener {
protected static final int SUCCESS_LOAD_NEW_DATA = 0;
protected static final int SUCCESS_LOAD_MORE_DATA =1;
private MyListView lv;
private ArrayAdapter<String> adapter;
private List<String> objects;
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case SUCCESS_LOAD_NEW_DATA:
String data = (String) msg.obj;
objects.add(0,data);
adapter.notifyDataSetChanged();
lv.hideHeader();
break;
case SUCCESS_LOAD_MORE_DATA:
List<String> list = (List<String>) msg.obj;
objects.addAll(list);
adapter.notifyDataSetChanged();
lv.hideFooter();
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (MyListView) findViewById(R.id.lv);
// 值的初始化
objects = new ArrayList<String>();
for (int i = 0; i < 13; i++) {
objects.add("初始化的数据" + i);
}
adapter = new ArrayAdapter<String>(getApplication(),
android.R.layout.simple_list_item_1, android.R.id.text1,
objects);
lv.setAdapter(adapter);
lv.setOnListViewLoadDataListener(this);
}
@Override
public void onLoadNewData() {
new Thread() {
public void run() {
SystemClock.sleep(3000);
String data = "加载的数据";
Message msg = Message.obtain();
msg.what = SUCCESS_LOAD_NEW_DATA;
msg.obj = data;
handler.sendMessage(msg);
};
}.start();
}
@Override
public void onLoadMoreData() {
new MyAsyncTask().execute("");
}
//Params 参数:一般都是要执行下载的路径 String Progress 进度条的值 Integer, Result 结果 Bitmap String List<String>
private class MyAsyncTask extends AsyncTask<String , Integer, List<String>>{
//准备
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//执行后台的耗时操作
@Override
protected List<String> doInBackground(String... params) {
SystemClock.sleep(3000);
List<String> list=new ArrayList<String>();
for (int i = 0; i <5; i++) {
list.add("加载更多数据"+i);
}
return list;
}
//下载完成后
@Override
protected void onPostExecute(List<String> result) {
super.onPostExecute(result);
objects.addAll(result);
adapter.notifyDataSetChanged();
lv.hideFooter();
}
//必须是有另外一个方法的调用才会调用该方法
//publishProgress() 该方法一般都是在doInBackground(String... params)里面调用
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
}
}
如果想详细知道AsyncTask可以参考
scott's的博客