实现ListView的下拉刷新和上拉加载,需要先添加headerView和footerView,通过在拖动的过程中,控制头尾布局的paddingTop实现。先把paddingTop设为负值,来隐藏header,在下拉的过程中,不断改变headerView的paddingTop,实现下拉过程中headerView慢慢显示的效果。
下拉刷新,是先将listview的头部隐藏,然后用手势拖动屏幕刷新。
那么,listview的头部如何隐藏呢?这里用的是header.setPadding(0,-headerViewHeight,0,0)来隐藏头部。
当MotionEvent.ACTION_DOWN(手势按下)触发时,需要一个downY记录手势按下时y的偏移值,
然后随着手势的滑动即 MotionEvent.ACTION_MOVE(手势滑动)触发时,又需要一个moveY记录移动的偏移值,
然后dy=moveY- downY得到的就是手指滑动的距离。然后用 paddingTop = (int) (dy - mHeadViewHeight)得到头部的顶部到屏幕的顶部距离。如果想paddingTop >= 0则说明头部完全显示,如果paddingTop <0 则头部没有完全显示。
下拉刷新的几种状态:
/**
* 下拉刷新
*/
public static final int STATE_PULL_TO_REFRESH = 1;
/**
* 释放刷新
*/
public static final int STATE_RELASE_TO_REFRESH = 2;
/**
* 正在刷新
*/
public static final int STATE_REFRESHING = 3;
自定义包含下拉刷新和上拉加载功能的的ListView:
package com.example.a01_pulltorefreshview.widget;
import android.content.Context;
import android.util.AttributeSet;
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.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.a01_pulltorefreshview.R;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 自定义包含下拉刷新和上拉加载功能的的ListView
* Created by xiaoyehai on 2016/11/24.
*/
public class PullToRefreshListView extends ListView implements AbsListView.OnScrollListener {
/**
* 下拉刷新
*/
public static final int STATE_PULL_TO_REFRESH = 1;
/**
* 释放刷新
*/
public static final int STATE_RELASE_TO_REFRESH = 2;
/**
* 正在刷新
*/
public static final int STATE_REFRESHING = 3;
/**
* 当前状态
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;
/**
* 下拉刷新头布局
*/
private View mHeadView;
/**
* 下拉刷新状态
*/
private TextView tvState;
/**
* 下拉刷新时间
*/
private TextView tvTime;
/**
* 下拉刷新旋转箭头图片
*/
private ImageView ivArrow;
/**
* 下拉刷新ProgressBar
*/
private ProgressBar pbProgress;
/**
* 下拉刷新按下时Y抽坐标
*/
private float startY;
//private int startY = -1;
/**
* 下拉刷新移动时Y抽坐标
*/
private float endY;
/**
* 下拉刷新头布局的高度
*/
private int mHeadViewHeight;
/**
* 下拉刷新箭头旋转动画
*/
private RotateAnimation animUp;
/**
* 下拉刷新箭头旋转动画
*/
private RotateAnimation animDown;
/**
* 下拉刷新过程中和顶部的padding
*/
private int paddingTop;
/**
* 上拉加载底部布局
*/
private View mFooterView;
/**
* 上拉加载底部布局高度
*/
private int mFooterViewHeight;
/**
* 是否正在加载更多
*/
private boolean isLoadMore;
/**
* 下拉刷新和上拉加载事件监听
*/
private onRefreshListener onRefreshListener;
public PullToRefreshListView(Context context) {
this(context, null);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* 初始化头布局,脚布局
*/
private void init() {
initHeadView();
initFoodView();
initAnimation();
}
/**
* 初始化头布局
*/
private void initHeadView() {
mHeadView = View.inflate(getContext(), R.layout.pull_to_refresh_head, null);
this.addHeaderView(mHeadView);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time);
pbProgress = (ProgressBar) mHeadView.findViewById(R.id.pd_loading);
//按照设置的规则测量宽高
mHeadView.measure(0, 0);
//控件高度
mHeadViewHeight = mHeadView.getMeasuredHeight();
//隐藏头布局
mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
}
/**
* 初始化底部,加载更多
*/
private void initFoodView() {
mFooterView = View.inflate(getContext(), R.layout.pull_to_refresh_footer, null);
this.addFooterView(mFooterView);
//测量控件
mFooterView.measure(0, 0);
//控件的高度
mFooterViewHeight = mFooterView.getMeasuredHeight();
//隐藏控件
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
//设置监听,加载更多
this.setOnScrollListener(this);
}
/**
* 初始化头布局箭头动画
*/
private void initAnimation() {
animUp = new RotateAnimation(0, -180,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animUp.setDuration(200);
animUp.setFillAfter(true);
animDown = new RotateAnimation(-180, 0,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animDown.setDuration(200);
animDown.setFillAfter(true);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录起点按下的Y坐标
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//添加了广告轮播头布局:
//当用户按住广告图片进行下拉时ACTION_DOWN会被viewpager消费掉,
//导致startY没有被赋值,此处需要重新获取一下
// if (startY == -1) {
// startY = (int) ev.getY();
// }
//如果此时正在刷新,跳出循环,不让再次刷新
if (mCurrentState == STATE_REFRESHING) {
break;
//return super.onTouchEvent(ev); //或者这样写,执行父类的处理
}
endY = ev.getY();
//手指滑动偏移量
float dy = endY - startY;
//当前显示第一个条目的位置
int firstVisiblePosition = getFirstVisiblePosition();
//往下拉,并且显示的是第一个可见item时第一个的时候,才能划出控件
if (dy > 0 && firstVisiblePosition == 0) {
//计算当前控件的padding
paddingTop = (int) (dy - mHeadViewHeight);
mHeadView.setPadding(0, paddingTop, 0, 0);
//当控件拉至完全显示,改为松开刷新
if (paddingTop >= 0 && mCurrentState != STATE_RELASE_TO_REFRESH) {
mCurrentState = STATE_RELASE_TO_REFRESH; //松开刷新状态
//刷新头布局
refreshState();
} else if (paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH) {
//不完全显示,下拉刷新
mCurrentState = STATE_PULL_TO_REFRESH;//下拉刷新状态
//刷新头布局
refreshState();
}
return true; //当前事件被消费,拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
}
break;
case MotionEvent.ACTION_UP:
// startY = -1;
if (mCurrentState == STATE_PULL_TO_REFRESH) { //下拉的时候没完全显示就松开或者回去的时候,就恢复
mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
} else if (mCurrentState == STATE_RELASE_TO_REFRESH) { //松开刷新状态的时候抬起手要变成正在刷新
mCurrentState = STATE_REFRESHING;
refreshState();
//正在刷新时完整展示头布局
mHeadView.setPadding(0, 0, 0, 0);
//接口回调,通知加载数据
if (onRefreshListener != null) {
onRefreshListener.onRefresh();
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 根据当前状态刷新控件
*/
private void refreshState() {
switch (mCurrentState) {
case STATE_PULL_TO_REFRESH:
// TODO: 2016/11/24 下拉刷新状态
tvState.setText("下拉刷新");
pbProgress.setVisibility(INVISIBLE);
ivArrow.setVisibility(VISIBLE);
ivArrow.startAnimation(animDown);
break;
case STATE_RELASE_TO_REFRESH:
// TODO: 2016/11/24 松开刷新状态
tvState.setText("释放刷新");
pbProgress.setVisibility(INVISIBLE);
ivArrow.setVisibility(VISIBLE);
ivArrow.startAnimation(animUp);
break;
case STATE_REFRESHING:
// TODO: 2016/11/24 正在刷新状态
tvState.setText("正在刷新...");
pbProgress.setVisibility(VISIBLE);
ivArrow.setVisibility(INVISIBLE);
ivArrow.clearAnimation(); //清除箭头动画,否则无法隐藏
break;
}
}
/**
* 刷新结束,收起控件
*
* @param success:是否下拉刷新成功
*/
public void onRefreshComplete(boolean success) {
if (isLoadMore) { //加载更多
//加载更多完成后,收起控件
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
isLoadMore = false;
} else { //下拉刷新
mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
mCurrentState = STATE_PULL_TO_REFRESH;
tvState.setText("下拉刷新");
pbProgress.setVisibility(INVISIBLE);
ivArrow.setVisibility(VISIBLE);
//设置刷新时间,只有刷新成功之后才更新时间
if (success) {
tvTime.setText("最后刷新时间:" + getCurrentTime());
}
}
}
/**
* 设置下拉刷新里面的时间
*/
private String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
return time;
}
/**
* SCROLL_STATE_IDLE:当屏幕停止滚动时
* SCROLL_STATE_TOUCH_SCROLL:当屏幕以触屏方式滚动并且手指还在屏幕
* SCROLL_STATE_FLING:当用户之前滑动屏幕并抬起手指,屏幕以惯性滚动
*
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO: 2016/11/19 滑动状态发生变化的回调
if (scrollState == SCROLL_STATE_IDLE) { //空闲状态
int lastVisiblePosition = getLastVisiblePosition();
//显示最后一个item并且没有加载更多
if (lastVisiblePosition == getCount() - 1 && !isLoadMore) {
isLoadMore = true;
mFooterView.setPadding(0, 0, 0, 0); //显示
//setSelection(getCount() - 1); //显示在最后一个item上
setSelection(getCount()); //显示在最后一个item上
//通知主界面加载下一页数据
if (onRefreshListener != null) {
onRefreshListener.onLoadMore();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO: 2016/11/19 滑动过程的回调
}
/**
* 下拉刷新的回调接口
*/
public interface onRefreshListener {
//通知刷新(正在刷新)
void onRefresh();
//通知加载更多
void onLoadMore();
}
/**
* 暴露接口,设置监听
*
* @param onRefreshListener
*/
public void setOnRefreshListener(PullToRefreshListView.onRefreshListener onRefreshListener) {
this.onRefreshListener = onRefreshListener;
}
}
头布局:
<?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:orientation="horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@mipmap/common_listview_headview_red_arrow" />
<ProgressBar
android:id="@+id/pd_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/custom_progress"
android:visibility="invisible" />
</FrameLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新"
android:textColor="#f00"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="最后刷新时间:2015-10-21 09:00:30"
android:textColor="#a000"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
custom_progress:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
<shape
android:innerRadius="18dp"
android:shape="ring"
android:thickness="5dp"
android:useLevel="false">
<gradient
android:centerColor="#9f00"
android:endColor="#f00"
android:startColor="#fff"
android:type="sweep" />
</shape>
</rotate>
底部布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/pd_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/custom_progress" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载中..."
android:textColor="#f00"
android:layout_marginLeft="10dp"
android:textSize="18sp" />
</LinearLayout>
用法:
<?xml version="1.0" encoding="utf-8"?>
<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="com.example.a01_pulltorefreshview.MainActivity">
<com.example.a01_pulltorefreshview.widget.PullToRefreshListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
package com.example.a01_pulltorefreshview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Window;
import com.example.a01_pulltorefreshview.widget.PullToRefreshListView;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义下拉刷新和上拉加载的ListView
*/
public class MainActivity extends AppCompatActivity {
private PullToRefreshListView mListView;
private List<String> list;
private ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
mListView = (PullToRefreshListView) findViewById(R.id.listview);
list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
list.add("这是一条ListView数据:" + i);
}
//添加头部,要在setAdapter之前添加
adapter = new ListViewAdapter(this, list);
mListView.setAdapter(adapter);
mListView.setOnRefreshListener(new PullToRefreshListView.onRefreshListener() {
@Override
public void onRefresh() {
//TODO: 2016/11/24 下拉刷新的时候回调该方法,加载数据
loadData();
}
@Override
public void onLoadMore() {
// TODO: 2016/11/24 上拉加载下一页数据的回调
loadMoreData();
}
});
}
private void loadMoreData() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//下拉刷新的数据
list.add("上拉加载的数据");
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
//加载结束,收起控件
mListView.onRefreshComplete(true);
}
});
}
}).start();
}
private void loadData() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//下拉刷新的数据
list.add(0, "下拉刷新的数据");
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
//加载结束,收起控件
mListView.onRefreshComplete(true);
}
});
}
}).start();
}
}