转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/50830639
本文出自:【顾林海的博客】

##前言
下拉刷新控件,想必大家都用过,现在网上下拉刷新的库很多像PullToRefresh(https://github.com/chrisbanes/Android-PullToRefresh)等等,类似的控件有很多,开源是个好东西,能使我们节省时间,但一味的去使用,不去了解实现的原理,只会让我们程序员成为代码的搬运工。

下图是我们今天要实现的效果:

##刷新原理

整个刷新控件包括两个部分:刷新的头部+ListView。
在实现我们的下拉刷新控件时,需要注意一下几点:

  1. 一开始进入我们的列表页时,刷新的头部是需要隐藏的。
  2. 滑动ListView时,是什么时候该下拉刷新,什么时候又是纯粹的往上滑动。也就是说只有在ListView滑动到顶部时,才是决定是否下拉。
  3. 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 implementsView.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);}}

上面的代码很简单,定义几个刷新的状态:

  1. STATUS_PULL_TO_REFRESH 下拉状态
  2. STATUS_RELEASE_TO_REFRESH 释放准备刷新状态
  3. STATUS_REFRESHING 正在刷新状态
  4. STATUS_REFRESH_FINISHED 刷新完成状态

用于保存我们在滑动时的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;@Overrideprotected 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;}}@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubreturn false;}

onLayout方法中主要做了以下几步:

  1. 获取头部header的高度
  2. 获取头部header的布局参数MarginLayoutParams
  3. 为header设置布局参数(-header.getHeight()),进行隐藏。
  4. 拿到我们的ListView,并给它设置触摸事件。

运行程序,显示如下:

到这里为止,已经将头部视图给隐藏了,接下来就是我们的滑动监听了,实现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;@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int marginValue = Integer.parseInt(animation.getAnimatedValue().toString());setHeaderTopMarign(marginValue);}});rbToHeaderAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {currentStatus = STATUS_REFRESHING;header.loading();Executors.newSingleThreadExecutor().submit(new Runnable() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int marginValue = Integer.parseInt(animation.getAnimatedValue().toString());/*** 当刷新完毕后,进行headerView的隐藏*/setHeaderTopMarign(-marginValue + tempHeaderTopMargin);}});rbAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic 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;

这里主要分为顶部时的几种场景:

  1. 当手指向上滑动时(distance <= 0),并且头部视图小于等于头部视图本身的高度时,我们不做处理。
  2. 当滑动距离小于我们限定的最小距离,也不做处理。
  3. 最后判断头部视图是否完全显示出来。分两种状态,一种是已经将头部视图显示出来,并准松手释放;还有一种是处于下拉状态,但还没有完全显示出来。

头部视图被逐渐往下来拉动的效果,查看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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int marginValue = Integer.parseInt(animation.getAnimatedValue().toString());/*** 当刷新完毕后,进行headerView的隐藏*/setHeaderTopMarign(-marginValue + tempHeaderTopMargin);}});rbAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int marginValue = Integer.parseInt(animation.getAnimatedValue().toString());setHeaderTopMarign(marginValue);}});rbToHeaderAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {currentStatus = STATUS_REFRESHING;header.loading();Executors.newSingleThreadExecutor().submit(new Runnable() {@Overridepublic 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 implementsView.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);}@Overrideprotected 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;}}@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int marginValue = Integer.parseInt(animation.getAnimatedValue().toString());setHeaderTopMarign(marginValue);}});rbToHeaderAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {currentStatus = STATUS_REFRESHING;header.loading();Executors.newSingleThreadExecutor().submit(new Runnable() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int marginValue = Integer.parseInt(animation.getAnimatedValue().toString());/*** 当刷新完毕后,进行headerView的隐藏*/setHeaderTopMarign(-marginValue + tempHeaderTopMargin);}});rbAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic 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;@Overrideprotected 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() {@Overridepublic void onRefreshing() {try {// 模拟网络请求耗时动作Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}mHandler.sendEmptyMessage(0);}});}private Handler mHandler = new Handler() {@Overridepublic 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.PullToRefreshViewandroid:id="@+id/refresh_hit_block"android:layout_width="match_parent"android:layout_height="wrap_content" ><ListViewandroid: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项目源码地址:点击【项目源码】

Android之一起玩转下拉刷新控件相关推荐

  1. 打造Android微信朋友圈下拉刷新控件

    打造Android微信朋友圈下拉刷新控件> 转载于:https://www.cnblogs.com/zhujiabin/p/5707789.html

  2. android 下拉刷新 组件,android系统自带下拉刷新控件的实现

    android系统自带的下拉刷新控件SwipeRefreshLayout位于android.support.v4.widget包下,实现步骤如下: 1.在布局文件中添加该控件,该控件一般作为父控件,而 ...

  3. android google 下拉刷新 csdn,android SwipeRefreshLayout google官方下拉刷新控件

    下拉刷新功能之前一直使用的是XlistView很方便我前面的博客有介绍 SwipeRefreshLayout是google官方推出的下拉刷新控件使用方法也比较简单 今天就来使用下SwipeRefres ...

  4. Android自定义View实现下拉刷新控件

    路过的老铁同志可以微信搜索"Android小菜",不定期更新Android技术文章.比CSDN更快一步阅读. 本文实现的功能如下: 1.支持下拉刷新: 2.支持上拉加载更多 3.刷 ...

  5. Android SwipeRefreshLayout 官方下拉刷新控件介绍

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24521483 下面App基本都有下拉刷新的功能,以前基本都使用XListView ...

  6. Android 解决下拉刷新控件和ScrollVIew的滑动冲突问题。

    最近项目要实现ScrollView中嵌套广告轮播图+RecyleView卡片布局,并且RecyleView按照header和内容的排列样式,因为RecyleView的可扩展性很强,所以我毫无疑问的选择 ...

  7. android多个下拉控件,Android实现支持所有View的通用的下拉刷新控件

    下拉刷新对于一个app来说是必不可少的一个功能,在早期大多数使用的是chrisbanes的PullToRefresh,或是修改自该框架的其他库.而到现在已经有了更多的选择,github上还是有很多体验 ...

  8. Android 怎么实现支持所有View的通用的下拉刷新控件

    转载请标明出处: http://blog.csdn.net/u010386612/article/details/51372696 本文出自:[AItsuki的博客] 下拉刷新对于一个app来说是必不 ...

  9. android禁止下拉刷新,Android开发之无痕过渡下拉刷新控件的实现思路详解

    相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下 ...

最新文章

  1. 零基础入门学习Python(34) 面向对象
  2. 宁波城市职业技术学院Linux,宁波城市职业技术学院拟聘用人员公示
  3. GameBryo Shader 组织方式
  4. 新概念0820:我只有一种生活
  5. table表头固定4种方法_4种细砂回收的方法分析
  6. 记录mysql中的隐患特性
  7. 本地笔记软件_到底哪个笔记软件适合你
  8. Mac中安装NetBeans方法
  9. Spring MVC验证器:Validator接口和ValidationUtils类
  10. 带宽和下载速率的关系
  11. iOS连续上传多张图片
  12. solaris系统中的’format’程序
  13. QT信号(signal)和槽(slot)问题
  14. 走进R语言的世界——简单数据处理
  15. ubuntu12.10 使用lync
  16. 大数据学习基础知识总纲
  17. JAVA 生成二维码 并设置 +失效机制
  18. P1138 第k小整数
  19. 基于sklearn的贝叶斯文本分类
  20. java 组合实现汽车类 问题描述:一辆Car有(has)四个轮子(Wheels)和一个发动机(Engine)。现在要求用组合方法设计类Car、类Wheel和类Engine。

热门文章

  1. 联想x250为什么这么贵_为什么ThinkPad这么贵?
  2. springboot rabbitmq direct exchange和topic exchange 写法上关于路由键的区别
  3. Hibernate学习5—Hibernate操作对象
  4. debain安装lyx2.2.2,以及报错整理:
  5. SQLHelper类
  6. Java中读取某个目录下的所有文件和文件夹
  7. 组策略部署软件----将部署的软件分类
  8. tensorflow 代码阅读
  9. CF797E. Array Queries
  10. tomcat版本号的修改