转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/50830639
本文出自:【顾林海的博客】
下拉刷新控件,想必大家都用过,现在网上下拉刷新的库很多像PullToRefresh(https://github.com/chrisbanes/Android-PullToRefresh)等等,类似的控件有很多,开源是个好东西,能使我们节省时间,但一味的去使用,不去了解实现的原理,只会让我们程序员成为代码的搬运工。
下图是我们今天要实现的效果:
整个刷新控件包括两个部分:刷新的头部+ListView。
在实现我们的下拉刷新控件时,需要注意一下几点:
ListView滑动到顶部时,往下拉一半的头部View时松手,这时应该判定为刷新行为失败,当整个头部被下拉时松手判定为刷新成功。
基于以上说明,我们先为我们的控件增加头部的View:
package com.example.pulltorefreshlistview.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.TextView;
/** * Created by glh on 2016/3/1. */
public class PullHeaderView extends FrameLayout {
private Context mContext;
private TextView mHeaderText;
public PullHeaderView(Context context) {
this(context, null);
}
public PullHeaderView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
mHeaderText = new TextView(mContext);
mHeaderText.setTextSize(20);
mHeaderText.setPadding(20, 20, 20, 20);
mHeaderText.setGravity(Gravity.CENTER);
addView(mHeaderText);
}
protected void start() {
mHeaderText.setText("下拉刷新");
}
protected void loading() {
mHeaderText.setText("正在刷新...");
}
protected void end() {
mHeaderText.setText("刷新结束");
}
}
头部的视图很简单,只显示一个TextView,用于显示刷新的状态。
刷新的控件PullToRefreshView继承LinearLayout,先进行相应的初始化:
public class PullToRefreshView extends LinearLayout implements View.OnTouchListener {
/** * 下拉头的View */
private PullHeaderView header;
/** * 下拉控件布局参数 */
private MarginLayoutParams headerLayoutParams;
/** * 下拉状态 */
public static final int STATUS_PULL_TO_REFRESH = 0;
/** * 释放准备刷新状态 */
public static final int STATUS_RELEASE_TO_REFRESH = 1;
/** * 正在刷新状态 */
public static final int STATUS_REFRESHING = 2;
/** * 刷新完成状态 */
/** * 在被判定为滚动之前用户手指可以移动的最大值 */
private int touchSlop;
public PullToRefreshView(Context context) {
this(context, null);
}
public PullToRefreshView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullToRefreshView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
header = new PullHeaderView(context, attrs);
header.start();
addView(header, 0);
}
}
上面的代码很简单,定义几个刷新的状态:
用于保存我们在滑动时的ListView状态,initView方法也很容易理解,创建我们的头部视图,并进行初始化,最后添加到我们的PullToRefreshView(继承与LinearLayout)的第一个位置上。构造器里面也只是获取当前屏幕滑动的最小距离。
这个时候运行程序,效果是这样的:
查看上面的图片,我们会看到头部视图被完全显示出来了,这不是我们想要的效果,往上翻看之前说过的注意点1,知道我们要的效果是,一进去头部时被隐藏的,那如何隐藏呢,这里面就需要重写onLayout方法。
我们知道View的工作流程主要包括measure、layout、draw这三大流程,也就是测量、布局和绘制,measure用于确定View的测量宽和高,layout确定View的最终确定宽和高以及位置,而draw将View绘制到屏幕,在这里我们只实现onLayout方法,用于确定我们的头部视图的位置。
/** * 用于控制onLayout中的初始化只需加载一次 */
private boolean once;
/** * 下拉控件高度 */
private int hideHeaderHeight;
/** * 需要去下拉刷新的ListView */
private ListView listView;
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed && !once) {
hideHeaderHeight = -header.getHeight();
headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();
headerLayoutParams.topMargin = hideHeaderHeight;
listView = (ListView) getChildAt(1);
listView.setOnTouchListener(this);
once = true;
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
onLayout方法中主要做了以下几步:
运行程序,显示如下:
到这里为止,已经将头部视图给隐藏了,接下来就是我们的滑动监听了,实现onTouch方法:
/** * 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉 */
private boolean ableToPull;
/** * 当前状态 */
private int currentStatus = STATUS_REFRESH_FINISHED;
/** * 手指按下时屏幕纵坐标 */
private float preDownY;
/** * 下拉拖动的黏性比率 */
private static final float STICK_RATIO = .65f;
/** * 下拉刷新的回调接口 */
private RefreshListener mListener;
private int tempHeaderTopMargin;
@Override
public boolean onTouch(View v, MotionEvent event) {
checkAblePull(event);
if (!ableToPull) {
/** * 只有ListView滚动到头的时候才允许下拉 */
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
preDownY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float currY = event.getRawY();
float distance = currY - preDownY;
float offsetY = distance * STICK_RATIO;
if (distance <= 0
&& headerLayoutParams.topMargin <= hideHeaderHeight) {
return false;
}
if (distance < touchSlop) {
return false;
}
header.start();
if (headerLayoutParams.topMargin > 0) {
currentStatus = STATUS_RELEASE_TO_REFRESH;
} else {
currentStatus = STATUS_PULL_TO_REFRESH;
}
// 通过偏移下拉头的topMargin值,来实现下拉效果
setHeaderTopMarign((int) (offsetY + hideHeaderHeight));
break;
case MotionEvent.ACTION_UP:
if (currentStatus == STATUS_PULL_TO_REFRESH) {
/** * 下拉状态 */
rollbackHeader();
}
if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
/** * 头部向下拉时,松手后的逻辑 */
scrollBackHeader();
}
break;
}
if (currentStatus == STATUS_PULL_TO_REFRESH
|| currentStatus == STATUS_RELEASE_TO_REFRESH) {
// 让ListView失去焦点, 不可被点击
disableListView();
return true;
}
return false;
}
/** * 根据当前ListView的滚动状态来设定 {@link #ableToPull} * 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。 * * @param event */
private void checkAblePull(MotionEvent event) {
View firstChild = listView.getChildAt(0);
if (firstChild != null) {
int firstVisiblePos = listView.getFirstVisiblePosition();
if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
/** * 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新 */
if (!ableToPull) {
preDownY = event.getRawY();
}
ableToPull = true;
} else { // 反之
if (headerLayoutParams.topMargin != hideHeaderHeight) {
setHeaderTopMarign(hideHeaderHeight);
}
ableToPull = false;
}
} else {
/** * 如果ListView中没有元素,也应该允许下拉刷新 */
ableToPull = true;
}
}
/** * 回滚到头部刷新控件的高度,并触发后台刷新任务 */
private void scrollBackHeader() {
/** * 当我们将头部布局一直往下来松手收,使它能平滑的回到顶部。 */
ValueAnimator rbToHeaderAnimator = ValueAnimator.ofInt(
headerLayoutParams.topMargin, 0);
long duration = (long) (headerLayoutParams.topMargin * 1.1f) >= 0 ? (long) (headerLayoutParams.topMargin * 1.1f)
: 0;
rbToHeaderAnimator.setDuration(duration);
rbToHeaderAnimator
.setInterpolator(new AccelerateDecelerateInterpolator());
rbToHeaderAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int marginValue = Integer.parseInt(animation
.getAnimatedValue().toString());
setHeaderTopMarign(marginValue);
}
});
rbToHeaderAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentStatus = STATUS_REFRESHING;
header.loading();
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
if (mListener != null) {
mListener.onRefreshing();
}
}
});
}
});
rbToHeaderAnimator.start();
}
/** * 给header设置topMargin参数 * * @param margin */
private void setHeaderTopMarign(int margin) {
headerLayoutParams.topMargin = margin;
header.setLayoutParams(headerLayoutParams);
}
/** * 禁用ListView,让其失去焦点不可接受点击 */
private void disableListView() {
listView.setPressed(false);
listView.setFocusable(false);
listView.setFocusableInTouchMode(false);
}
/** * 给下拉刷新控件注册一个监听器。 * * @param listener * 监听器的实现。 */
public void setOnRefreshListener(RefreshListener listener) {
mListener = listener;
}
/** * 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。 */
public void finishRefreshing() {
header.end();
/** * 回滚下拉刷新头部控件 */
rollbackHeader();
}
/** * 回滚下拉刷新头部控件 */
private void rollbackHeader() {
tempHeaderTopMargin = headerLayoutParams.topMargin;
ValueAnimator rbAnimator = ValueAnimator.ofInt(0, header.getHeight()
+ tempHeaderTopMargin);
rbAnimator.setDuration(300);
rbAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
rbAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int marginValue = Integer.parseInt(animation
.getAnimatedValue().toString());
/** * 当刷新完毕后,进行headerView的隐藏 */
setHeaderTopMarign(-marginValue + tempHeaderTopMargin);
}
});
rbAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (currentStatus == STATUS_PULL_TO_REFRESH
|| currentStatus == STATUS_REFRESH_FINISHED) {
currentStatus = STATUS_REFRESH_FINISHED;
return;
}
currentStatus = STATUS_REFRESH_FINISHED;
header.start();
}
});
rbAnimator.start();
}
/** * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调。 */
public interface RefreshListener {
/** * 刷新时回调方法 */
void onRefreshing();
}
这里面代码比较多,我们一步一步来看,根据上面提出的注意点2(滑动ListView时,是什么时候该下拉刷新,什么时候又是纯粹的往上滑动。也就是说只有在ListView滑动到顶部时,才是决定是否下拉。),也就是我们每次滑动时,都要去判断当前是否已经滑动到顶部,我们看checkAblePull方法,获取ListView中的第一个View,判断它是否显示在第一个位置并距离屏幕上方为0时,说明我们的ListView已经在顶部,这时获取手指滑动到的Y坐标;相反,如果我们的ListView并没有滑动到顶部,判断头部视图是否正确并进行重新设置位置。
if (!ableToPull) {
/** * 只有ListView滚动到头的时候才允许下拉 */
return false;
}
通过ableToPull进行下拉行为的判定。
接下来就是滑动到顶部时的一系列操作,查看滑动操作时的代码:
case MotionEvent.ACTION_MOVE:
float currY = event.getRawY();
float distance = currY - preDownY;
float offsetY = distance * STICK_RATIO;
if (distance <= 0
&& headerLayoutParams.topMargin <= hideHeaderHeight) {
return false;
}
if (distance < touchSlop) {
return false;
}
header.start();
if (headerLayoutParams.topMargin > 0) {
currentStatus = STATUS_RELEASE_TO_REFRESH;
} else {
currentStatus = STATUS_PULL_TO_REFRESH;
}
// 通过偏移下拉头的topMargin值,来实现下拉效果
setHeaderTopMarign((int) (offsetY + hideHeaderHeight));
break;
这里主要分为顶部时的几种场景:
最后判断头部视图是否完全显示出来。分两种状态,一种是已经将头部视图显示出来,并准松手释放;还有一种是处于下拉状态,但还没有完全显示出来。
头部视图被逐渐往下来拉动的效果,查看setHeaderTopMargin方法:
/** * 给header设置topMargin参数 * * @param margin */
private void setHeaderTopMarign(int margin) {
headerLayoutParams.topMargin = margin;
header.setLayoutParams(headerLayoutParams);
}
传入的值是我们在顶部,并向下滑动的Y坐标加上我们的头部视图的高度(hideHeaderHeight),这里面的高度一开始在onLayout进行初始化时被赋值给hideHeaderHeight,这里的(hideHeaderHeight)值一个负数,通过offsetY + hideHeaderHeight(【offsetY,负数】)不停地为topMargin 进行赋值。
最后就是手指离开屏幕时的操作:
case MotionEvent.ACTION_UP:
if (currentStatus == STATUS_PULL_TO_REFRESH) {
/** * 下拉状态 */
rollbackHeader();
}
if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
/** * 头部向下拉时,松手后的逻辑 */
scrollBackHeader();
}
break;
通过滑动过程中的状态值,在离开屏幕时进行相应的处理,当处于下拉状态时:
/** * 回滚下拉刷新头部控件 */
private void rollbackHeader() {
tempHeaderTopMargin = headerLayoutParams.topMargin;
ValueAnimator rbAnimator = ValueAnimator.ofInt(0, header.getHeight()
+ tempHeaderTopMargin);
rbAnimator.setDuration(300);
rbAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
rbAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int marginValue = Integer.parseInt(animation
.getAnimatedValue().toString());
/** * 当刷新完毕后,进行headerView的隐藏 */
setHeaderTopMarign(-marginValue + tempHeaderTopMargin);
}
});
rbAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (currentStatus == STATUS_PULL_TO_REFRESH
|| currentStatus == STATUS_REFRESH_FINISHED) {
currentStatus = STATUS_REFRESH_FINISHED;
return;
}
currentStatus = STATUS_REFRESH_FINISHED;
header.start();
}
});
rbAnimator.start();
}
当 头部处于向下拉时,松手后的逻辑:
/** * 回滚到头部刷新控件的高度,并触发后台刷新任务 */
private void scrollBackHeader() {
/** * 当我们将头部布局一直往下来松手收,使它能平滑的回到顶部。 */
ValueAnimator rbToHeaderAnimator = ValueAnimator.ofInt(
headerLayoutParams.topMargin, 0);
long duration = (long) (headerLayoutParams.topMargin * 1.1f) >= 0 ? (long) (headerLayoutParams.topMargin * 1.1f)
: 0;
rbToHeaderAnimator.setDuration(duration);
rbToHeaderAnimator
.setInterpolator(new AccelerateDecelerateInterpolator());
rbToHeaderAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int marginValue = Integer.parseInt(animation
.getAnimatedValue().toString());
setHeaderTopMarign(marginValue);
}
});
rbToHeaderAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentStatus = STATUS_REFRESHING;
header.loading();
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
if (mListener != null) {
mListener.onRefreshing();
}
}
});
}
});
rbToHeaderAnimator.start();
}
代码比较简单,都是对向下拉动头部视图时的操作,通过ValueAnimator时其向上滚动时,呈现平滑状态。
结束到这里,差不多已经结束了,以下给出PullToRefreshView的详细代码:
package com.example.pulltorefreshlistview.view;
import java.util.concurrent.Executors;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.ListView;
/** * Created by glh on 2016/3/2. */
public class PullToRefreshView extends LinearLayout implements View.OnTouchListener {
/** * 下拉状态 */
public static final int STATUS_PULL_TO_REFRESH = 0;
/** * 释放准备刷新状态 */
public static final int STATUS_RELEASE_TO_REFRESH = 1;
/** * 正在刷新状态 */
public static final int STATUS_REFRESHING = 2;
/** * 刷新完成状态 */
public static final int STATUS_REFRESH_FINISHED = 4;
/** * 下拉拖动的黏性比率 */
private static final float STICK_RATIO = .65f;
/** * 下拉刷新的回调接口 */
private RefreshListener mListener;
/** * 下拉头的View */
private PullHeaderView header;
/** * 需要去下拉刷新的ListView */
private ListView listView;
/** * 下拉控件布局参数 */
private MarginLayoutParams headerLayoutParams;
/** * 下拉控件高度 */
private int hideHeaderHeight;
/** * 当前状态 */
private int currentStatus = STATUS_REFRESH_FINISHED;
/** * 手指按下时屏幕纵坐标 */
private float preDownY;
/** * 在被判定为滚动之前用户手指可以移动的最大值 */
private int touchSlop;
/** * 用于控制onLayout中的初始化只需加载一次 */
private boolean once;
/** * 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉 */
private boolean ableToPull;
private int tempHeaderTopMargin;
public PullToRefreshView(Context context) {
this(context, null);
}
public PullToRefreshView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullToRefreshView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
header = new PullHeaderView(context, attrs);
header.start();
addView(header, 0);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
/** * <pre> * 只加载一次,用于将头部隐藏,这里面做了以下操作: * 1、获取头部header的高度 * 2、获取头部header的布局参数MarginLayoutParams * 3、为header设置布局参数(-header.getHeight()),进行隐藏。 * 4、拿到我们的ListView,并给它设置触摸事件。 * </pre> */
if (changed && !once) {
hideHeaderHeight = -header.getHeight();
headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();
headerLayoutParams.topMargin = hideHeaderHeight;
listView = (ListView) getChildAt(1);
listView.setOnTouchListener(this);
once = true;
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
checkAblePull(event);
if (!ableToPull) {
/** * 只有ListView滚动到头的时候才允许下拉 */
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
preDownY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float currY = event.getRawY();
float distance = currY - preDownY;
float offsetY = distance * STICK_RATIO;
if (distance <= 0
&& headerLayoutParams.topMargin <= hideHeaderHeight) {
return false;
}
if (distance < touchSlop) {
return false;
}
header.start();
if (headerLayoutParams.topMargin > 0) {
currentStatus = STATUS_RELEASE_TO_REFRESH;
} else {
currentStatus = STATUS_PULL_TO_REFRESH;
}
// 通过偏移下拉头的topMargin值,来实现下拉效果
setHeaderTopMarign((int) (offsetY + hideHeaderHeight));
break;
case MotionEvent.ACTION_UP:
if (currentStatus == STATUS_PULL_TO_REFRESH) {
/** * 下拉状态 */
rollbackHeader();
}
if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
/** * 头部向下拉时,松手后的逻辑 */
scrollBackHeader();
}
break;
}
if (currentStatus == STATUS_PULL_TO_REFRESH
|| currentStatus == STATUS_RELEASE_TO_REFRESH) {
// 让ListView失去焦点, 不可被点击
disableListView();
return true;
}
return false;
}
//
/** * 给header设置topMargin参数 * * @param margin */
private void setHeaderTopMarign(int margin) {
headerLayoutParams.topMargin = margin;
header.setLayoutParams(headerLayoutParams);
}
/** * 禁用ListView,让其失去焦点不可接受点击 */
private void disableListView() {
listView.setPressed(false);
listView.setFocusable(false);
listView.setFocusableInTouchMode(false);
}
/** * 根据当前ListView的滚动状态来设定 {@link #ableToPull} * 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。 * * @param event */
private void checkAblePull(MotionEvent event) {
View firstChild = listView.getChildAt(0);
if (firstChild != null) {
int firstVisiblePos = listView.getFirstVisiblePosition();
if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
/** * 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新 */
if (!ableToPull) {
preDownY = event.getRawY();
}
ableToPull = true;
} else { // 反之
if (headerLayoutParams.topMargin != hideHeaderHeight) {
setHeaderTopMarign(hideHeaderHeight);
}
ableToPull = false;
}
} else {
/** * 如果ListView中没有元素,也应该允许下拉刷新 */
ableToPull = true;
}
}
/** * 给下拉刷新控件注册一个监听器。 * * @param listener * 监听器的实现。 */
public void setOnRefreshListener(RefreshListener listener) {
mListener = listener;
}
/** * 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。 */
public void finishRefreshing() {
header.end();
/** * 回滚下拉刷新头部控件 */
rollbackHeader();
}
/** * 回滚到头部刷新控件的高度,并触发后台刷新任务 */
private void scrollBackHeader() {
/** * 当我们将头部布局一直往下来松手收,使它能平滑的回到顶部。 */
ValueAnimator rbToHeaderAnimator = ValueAnimator.ofInt(
headerLayoutParams.topMargin, 0);
long duration = (long) (headerLayoutParams.topMargin * 1.1f) >= 0 ? (long) (headerLayoutParams.topMargin * 1.1f)
: 0;
rbToHeaderAnimator.setDuration(duration);
rbToHeaderAnimator
.setInterpolator(new AccelerateDecelerateInterpolator());
rbToHeaderAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int marginValue = Integer.parseInt(animation
.getAnimatedValue().toString());
setHeaderTopMarign(marginValue);
}
});
rbToHeaderAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentStatus = STATUS_REFRESHING;
header.loading();
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
if (mListener != null) {
mListener.onRefreshing();
}
}
});
}
});
rbToHeaderAnimator.start();
}
/** * 回滚下拉刷新头部控件 */
private void rollbackHeader() {
tempHeaderTopMargin = headerLayoutParams.topMargin;
ValueAnimator rbAnimator = ValueAnimator.ofInt(0, header.getHeight()
+ tempHeaderTopMargin);
rbAnimator.setDuration(300);
rbAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
rbAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int marginValue = Integer.parseInt(animation
.getAnimatedValue().toString());
/** * 当刷新完毕后,进行headerView的隐藏 */
setHeaderTopMarign(-marginValue + tempHeaderTopMargin);
}
});
rbAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (currentStatus == STATUS_PULL_TO_REFRESH
|| currentStatus == STATUS_REFRESH_FINISHED) {
currentStatus = STATUS_REFRESH_FINISHED;
return;
}
currentStatus = STATUS_REFRESH_FINISHED;
header.start();
}
});
rbAnimator.start();
}
/** * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调。 */
public interface RefreshListener {
/** * 刷新时回调方法 */
void onRefreshing();
}
}
package com.example.pulltorefreshlistview;
import java.util.ArrayList;
import java.util.List;
import com.example.pulltorefreshlistview.view.PullToRefreshView;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class MainActivity extends Activity {
private PullToRefreshView refreshView;
private ListView listView;
private List<String> dataList;
private ArrayAdapter<String> arrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
refreshView = (PullToRefreshView) findViewById(R.id.refresh_hit_block);
listView = (ListView) findViewById(R.id.list_view);
arrayAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_expandable_list_item_1, createDate());
listView.setAdapter(arrayAdapter);
refreshView
.setOnRefreshListener(new PullToRefreshView.RefreshListener() {
@Override
public void onRefreshing() {
try {
// 模拟网络请求耗时动作
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0);
}
});
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
dataList.add("新增内容");
arrayAdapter.notifyDataSetChanged();
refreshView.finishRefreshing();
Toast.makeText(MainActivity.this, "刷新成功!", Toast.LENGTH_SHORT)
.show();
}
};
private List<String> createDate() {
dataList = new ArrayList<String>();
dataList.add("Item1");
dataList.add("Item2");
dataList.add("Item3");
dataList.add("Item4");
dataList.add("Item5");
return dataList;
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" >
<com.example.pulltorefreshlistview.view.PullToRefreshView android:id="@+id/refresh_hit_block" android:layout_width="match_parent" android:layout_height="wrap_content" >
<ListView android:id="@+id/list_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbars="none" >
</ListView>
</com.example.pulltorefreshlistview.view.PullToRefreshView>
</RelativeLayout>
以下是完整的github项目地址
github项目源码地址:点击【项目源码】