首页 > 代码库 > ListView下拉刷新

ListView下拉刷新

public class MyListView extends ListView implements OnScrollListener {
private static final int STATE_NORMAL = 0;  // 正常状态
private static final int STATE_PULL = 1;    // 下来状态
private static final int STATE_RELEASE = 2; // 释放状态
private static final int STATE_LOADING = 3; // 数据加载状态
 
private View mHeaderView;   // listview的headerview
private ImageView mImage;   // headerview中图片
private ProgressBar mProgress; // headerview中的progressbar
private TextView mText;      // headerview中的文本
 
private RotateAnimation mAnim1;  // headerview中图片的旋转动画
private RotateAnimation mAnim2;  // 同上
private boolean isNowAtUp;  // 判断当前是不是在listview的最上面
 
private int mHeaderHeight;  // headerview的高度
private int mFirstVisibleItem;  // 保存listview中第一个可见项
private int mScrollState;  // 保存listview滑动的状态
private int mState;  // 保存判断的状态,同上面的常量进行比较,
private int mStartY; // 开始下拉的y值
private OnPullRefreshListener mListener;  // 回调接口
 
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
 
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
 
// 初始化view
private void initView() {
// 加载headerview
mHeaderView = LayoutInflater.from(getContext()).inflate(
R.layout.header_layout, null);
//获取headerview中组件
mImage = (ImageView) mHeaderView.findViewById(R.id.image);
mProgress = (ProgressBar) mHeaderView.findViewById(R.id.progress);
mText = (TextView) mHeaderView.findViewById(R.id.text);
// 初始化动画
initAnimation();
// 因为下面要用到headerview的高度,但在处理化的时候还没有进行onMeasure
// 所以要手工进行测量
measureView(mHeaderView);
// 获取headerview的高度
mHeaderHeight = mHeaderView.getMeasuredHeight();
// 设置headerview的上padding
// 为负的高度,则隐藏掉headerview
setPaddingTop(-mHeaderHeight);
// 将headerview添加到listview中
addHeaderView(mHeaderView);
 
// 监听滚动
setOnScrollListener(this);
}
 
// 初始化动画
private void initAnimation() {
mAnim1 = new RotateAnimation(180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f); 
mAnim2 = new RotateAnimation(0, 180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mAnim1.setDuration(500);
mAnim2.setDuration(500);
mAnim1.setFillAfter(true);
mAnim2.setFillAfter(true);
}
 
// 设置headerview的padding
private void setPaddingTop(int top) {
mHeaderView.setPadding(
mHeaderView.getPaddingLeft(),
top,
mHeaderView.getPaddingRight(),
mHeaderView.getPaddingBottom());
 
invalidate();
}
 
// 测量view
private void measureView(View view) {
// 先获取LayoutParams
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (null == lp) {
lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
 
// 第一个参数是spec
// 第二个参数是padding
// 第三个参数是我期望的大小
// 如果第三个参数小于0
// 则返回的结果是第一个参数减去第二个参数
// 如果第三个参数不小于0
// 则返回第三个参数
int widthMeasureSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
int height = lp.height;
int heightMeasureSpec;
 
// 生成height的Spec
if (height > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
} else {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
 
// 测量view,这样就可以获取headerview的高度了
// 否则headerview的高度获取出来为0
// MATCH_PARENT=-1, WRAP_CONTENT=-2
view.measure(widthMeasureSpec, heightMeasureSpec);
}
 
// 滑动状态该类
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 保存下当前的状态
mScrollState = scrollState;
}
 
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// 保存当前第一个可见项
mFirstVisibleItem = firstVisibleItem;
}
 
// 监听touch事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 如果是按下操作
// 并且现在第一个可见项是listview的第一项
// 即:现在在最上面
if (mFirstVisibleItem == 0) {
isNowAtUp = true;  // 记录现在是在最上面
mStartY = (int) ev.getY(); // 获取点击的y坐标
}
break;
case MotionEvent.ACTION_MOVE:
onMove(ev);  // 在onMove中处理移动事件
break;
case MotionEvent.ACTION_UP:
// 如果当前状态是可释放状态
if(mState == STATE_RELEASE) {
// 那个up后, 修改状态为数据加载状态
mState = STATE_LOADING;
// 刷新界面
refreshView();
// 调用回调接口中的方法
// 具体实现在activity中
mListener.onPull();
}else if(mState == STATE_PULL){  // 如果是下拉状态
mState = STATE_NORMAL; // 则更改状态为正常状态,因为还没有拉到加载数据的程度
refreshView(); // 刷新界面
}
break;
}
return super.onTouchEvent(ev);
}
 
// 刷新界面,其实是更改headerview中子view的状态
private void refreshView() {
switch (mState) {
case STATE_NORMAL:
setPaddingTop(-mHeaderHeight);  // 如果是正常状态,则将headerview隐藏了
break;
case STATE_PULL:  // 如果是下拉状态
mProgress.setVisibility(View.GONE); // 隐藏progressbar
mImage.setVisibility(View.VISIBLE); // 将图片显示出来
mImage.clearAnimation(); // 清除动画
mImage.startAnimation(mAnim1); // 设置旋转动画
mText.setText("下拉刷新!");  // 更新textview的文本
break;
case STATE_RELEASE:  // 释放状态
mProgress.setVisibility(View.GONE);  // 隐藏progressbar
mImage.setVisibility(View.VISIBLE);  // 将图片显示出来
mImage.clearAnimation(); // 清除动画
mImage.startAnimation(mAnim2); // 设置旋转动画
mText.setText("释放加载新数据!"); // 更新textview的文本
break;
case STATE_LOADING:  // 数据加载状态
setPaddingTop(mHeaderHeight);  // 设置paddingtop为headerview的高度
mProgress.setVisibility(View.VISIBLE); // 显示progressbar
mImage.clearAnimation(); // 清除动画
mImage.setVisibility(View.GONE); // 隐藏imageview
mText.setText("正在加载数据..."); // 更新文本
break;
}
}
 
// 处理action_move事件
private void onMove(MotionEvent ev) {
// 如果当前不是在顶部,直接返回
if (!isNowAtUp) {
return;
}
 
int nowY = (int) ev.getY();  // 记录当前的y坐标
int space = nowY - mStartY;  // 计算下拉的程度
int top = space - mHeaderHeight; // 计算top的值
 
switch (mState) {
case STATE_NORMAL:  // 如果是正常状态
if (space > 0) {  // 如果下拉了
mState = STATE_PULL; // 则 修改状态为下拉
refreshView(); // 并刷新headerview
}
break;
case STATE_PULL:  // 如果是下来状态
setPaddingTop(top); // 不断修改headerview的paddingtop
// 如果达到条件,并且还是在下拉中
if (space >= mHeaderHeight + 30
&& mScrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
mState = STATE_RELEASE;  // 修改状态为可释放状态
refreshView(); // 刷新headerview
}
break;
case STATE_RELEASE:  // 如果是可释放状态
setPaddingTop(top); // 不断修改headerview的paddingtop
// 如果下拉程度在STATE_PULL区间
if (space > 0 && space <= mHeaderHeight + 30) {
mState = STATE_PULL; // 则修改状态为下来状态,这样是又慢慢拉回去了
refreshView(); // 刷新headerview
} else if (space <= 0) {  // 如果没有下拉的空间了
mState = STATE_NORMAL; // 修改状态为正常状态,拉了一圈又回退回去了
isNowAtUp = false; // 还原默认值
refreshView(); // 刷新headerview
}
break;
}
}
// 外部调用的方法
// 通知我新数据加载完毕
// 还原状态为正常状态
public void endToRefresh() {
mState = STATE_NORMAL;
refreshView();
}
// 设置加载数据的回调接口
public void setOnPullRefreshListener(OnPullRefreshListener l) {
mListener = l;
}
// 定义一个回调接口
public interface OnPullRefreshListener {
public void onPull();
}
}


MyListView中使用的header_layout.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/darker_gray"
    android:orientation="vertical" >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="10dip"
        android:paddingLeft="30dip"
        android:paddingRight="30dip"
        android:paddingTop="10dip" >
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:orientation="horizontal" >
            <ImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="http://www.mamicode.com/@drawable/arrow" />
            <ProgressBar
                android:id="@+id/progress"
                style="?android:attr/progressBarStyleSmall"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:visibility="gone" />
            <TextView
                android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="下拉可以刷新" />
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>



使用:

在布局文件中:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <org.load.listview.MyListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>


在MainActivity中:

public class MainActivity extends Activity {
private MyListView mListView;
// 模拟数据
private String[] mData = { "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg",
"hhh", "iii" };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (MyListView) findViewById(R.id.list);
// 数据适配器
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, mData);
// 设置适配器
mListView.setAdapter(adapter);
// 这里就用到了回调接口
// 表示当可以加载新数据时干什么
mListView.setOnPullRefreshListener(new OnPullRefreshListener() {
@Override
public void onPull() {
// 模拟获取新数据
// 延迟2s后更新前两个数据
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
mData[i] = "new data " + new Random().nextInt(100);
adapter.notifyDataSetChanged();
mListView.endToRefresh();  // 通知MyListView数据加载完毕了
}
}
}, 2000);
}
});
}
}






ListView下拉刷新