下拉刷新控件,网上有很多版本,有自定义Layout布局的,也有封装控件的,各种实现方式的都有。但是很少有人告诉你具体如何实现的,今天我们就来一步步实现自己封装的 PullToRefreshLayout 完美的解决下拉刷新,上拉加载问题。

首先来分析一下原理,为什么一下拉就可以拉出来一个布局,请看下图,从图中可以看到整个屏幕来说有可见部分,有隐藏部分,当我们手指在屏幕上下拉的时候滑动距离到一定程度了就会拉出 下拉头布局,这样就达到了下拉效果。那么具体代码如何实现待我慢慢像大家解析。

1、想要实现  PullToRefreshLayout 下拉刷新控件那么我们就必须要有个容器,也就是如上图的容器,知道了需要什么那么我们就开始自定义一个容器。

这里如果不会自定义控件的同学可以参考博客 http://blog.csdn.net/cscfas/article/details/51330505

/*** Created by ZQY on 2016/5/17.* <p/>* 这个是上拉加载和下拉刷新的 View* <p/>* 注:这里的 android:orientation="vertical" 只能为这个值*/
public class PullToRefreshLayout extends LinearLayout {public PullToRefreshLayout(Context context) {super(context);initAnim();
}public PullToRefreshLayout(Context context, AttributeSet attrs) {super(context, attrs);initAnim();
}public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initAnim();
}}

2、有了容器,接下来就拉实现下拉头,上拉脚。LinearLayout 我们都用过线性布局嘛,在这里要注意 android:orientation=“vertical” 只能是垂直布局。这里重写了该控件,目的是在代码中动态添加布局到控件中,实现组合控件,就是PullToRefreshLayout ,这里调用了LinearLayout 的addView()  方法将布局添加到PullToRefreshLayout中。

(1)、添加头部布局,这里也就是下拉头

   private void addHeaderView() {mHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);mHeaderImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_refresh_image);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);mHeaderUpdateTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);mHeaderUpdateTextView.setText(DataUtil.getRefreshCompleteTime());mHeaderProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);measureView(mHeaderView);mHeaderViewHeight = mHeaderView.getMeasuredHeight();LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderViewHeight);//设置 topMargin 的值为负的 header View 高度,即将其隐藏在最上方params.topMargin = -(mHeaderViewHeight);//添加头部到布局addView(mHeaderView, params);}

(2)、添加脚部布局,这里也就是 上拉脚


private void addFooterView() {mFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);mFooterImageView = (ImageView) mFooterView.findViewById(R.id.pull_to_load_image);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pull_to_load_text);mFooterProgressBar = (ProgressBar) mFooterView.findViewById(R.id.pull_to_load_progress);// 底部布局measureView(mFooterView);mFooterViewHeight = mFooterView.getMeasuredHeight();LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,mFooterViewHeight);/***     int top = getHeight();params.topMargin=getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0;getHeight()什么时候会赋值,稍候再研究一下由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏*/addView(mFooterView, params);}

看完以上代码你肯定会想就这么简单嘛!当然不是,细心的同学会发现两个函数都有调用 measureView()函数,它是干嘛的呢!下面就来看下这个函数,这个函数看起来代码和注释很多,这里的功能无非就是计算子控件在父控件中的大小。

 private void  measureView(View child) {/*** child.getLayoutParams();** 返回  该视图的布局参数** 此视图的父视图指定如何安排它的供应参数**/ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {/*** 用指定的 宽度和高度 创建一组新的布局参数** @param width 宽度,或者 {@link #WRAP_CONTENT},*        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in*        API Level 8),或一个固定大小的像素* @param height  高度,或者 {@link #WRAP_CONTENT},*        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in*        API Level 8), 或一个固定大小的像素*/p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}/*** 是否measureChildren困难的部分:搞清楚MeasureSpec传递给特定的子控件。这种方法计算出正确的MeasureSpec一个子视图中的一维(高度或宽度)。* 目标是信息从我们MeasureSpec与子控件的的LayoutParams结合,以获得最佳的可能结果。例如,如果这个观点知道它的大小(因为它MeasureSpec有整整模式),* 子控件在其的LayoutParams已经表示,它想成为的尺寸与父控件一样,父控件应让子控件布置给精确的尺寸。* @param spec 该视图的要求* @param padding  该视图为当前维的填充和利润(如果适用)** @param childDimension  希望为子控件设置的尺寸* @return  MeasureSpec   一个MeasureSpec整数为孩子**/int childWidthSpec=ViewGroup.getChildMeasureSpec(0,0+0,p.width);int lpHeight = p.height;int childHeightSpec;if (lpHeight > 0) {/***创建基于所提供的大小和模式的量度规范。该模式必须是下列之一:UNSPECIFIEDEXACTLYAT_MOST* @param size 该措施说明书的大小* @param mode 该措施规范的模式* @return 基于规模和模式的措施规范*/childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,MeasureSpec.EXACTLY);} else {childHeightSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);}/*** 这就是所谓的大一个视图应该如何。父控件 约束信息的宽度和高度参数。一个视图的实际测量工作是在onMeasure(int,int),称为该方法。因此,只有onMeasure(int,int)可以而且必须由子类重写。@param widthMeasureSpec 横向空间的需求添加到的父控件大小@param heightMeasureSpec 垂直间距需求添加到的父控件大小*/child.measure(childWidthSpec, childHeightSpec);}

3、知道了 下拉头,上拉脚 怎么实现了,接下来就看在哪里加入到 PullToRefreshLayout控件中,又是如何实现动画的。请看下面代码。

(1)、这里动画实现的是刷新箭头的方向旋转,最后一行 addHeaderView() 实现了头部的添加。

  /*** 初始化动画*/private void initAnim() {//加载所有的动画,我们需要的代码,而不是通过 XMLmFlipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF, 0.5f);//设置动画 均速mFlipAnimation.setInterpolator(new LinearInterpolator());/*** 动画应该持续多久,持续时间不能为负*@param durationMillis*  @throws java.lang.IllegalArgumentException  如果 durationMillis < 0*  @attr 参考 R.styleable #Animation_duration*/mFlipAnimation.setDuration(250);/*** 如果 fillafter 是 true ,这个动画进行改造将坚持当它完成。* 默认为 false ,如果不设置。**请注意,这适用于个别动画,当使用 {@link android.view.animation.AnimationSet AnimationSet} 链动画** @param fillAfter  如果动画结束后,动画应该应用它的转换* @attr ref android.R.styleable#Animation_fillAfter** @see #setFillEnabled(boolean)*/mFlipAnimation.setFillAfter(true);/***构造函数使用时建立一个rotateanimation 对象*** @param fromDegrees 在动画开始时应用旋转偏移。** @param toDegrees   在动画结束时应用旋转偏移。** @param pivotXType 指定如何pivotxvalue应解释。什么之中的一个*        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or*        Animation.RELATIVE_TO_PARENT.** @param pivotXValue  X坐标的对象被旋转的点,指定一个绝对数量,0是左边缘。这个值可以是绝对数如果pivotxtype是绝对的,或一个百分比(1是100%)否则。*** @param pivotYType 指定如何pivotyvalue应解释。什么之中的一个*        Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or*        Animation.RELATIVE_TO_PARENT.** @param pivotYValue  X坐标的对象被旋转的点,指定一个绝对数量,0是左边缘。这个值可以是绝对数如果pivotxtype是绝对的,或一个百分比(1是100%)否则。*/mReverseFlipAnimation = new RotateAnimation(-180, 0,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);//设置此动画的加速曲线。默认为线性插值。 这里是匀速mReverseFlipAnimation.setInterpolator(new LinearInterpolator());mReverseFlipAnimation.setDuration(250);mReverseFlipAnimation.setFillAfter(true);mInflater = LayoutInflater.from(getContext());// header view 在此添加,保证是第一个添加到linearlayout的最上端addHeaderView();}

(2)、知道了头部如何加入PullToRefreshLayout中,那么底部是如何添加的呢!其实底部的加入是有技巧的,接下来请看代码。onFihishInflate()  看到@Override 你就知道这个函数是 LinearLayout 提供,那么它有何作用呢,它的作用就是在所有的XML和头部布局都添加了的情况下加入 脚部布局。

    /***完成 填充 XML格式的视图。这就是所谓的 UI填充 的最后阶段,所有子视图已被添加之后。即使子类覆盖onFinishInflate,他们应始终确保调用超级方法,使我们得到调用。 既必须调用  super.onFinishInflate();*/@Overrideprotected void onFinishInflate() {super.onFinishInflate();// footer view 在此添加保证添加到linearlayout中的最后addFooterView();initContentAdapterView();}

(3)、在上面的代码中你会看到 initContentAdapterView()  这个函数,你会想它又是什么鬼,它有什么作用呢?请看代码。

如果你有了解过,我的上一篇博客:http://blog.csdn.net/cscfas/article/details/51330505 ;那么你就知道在自定义控件中,如果XML布局中引入了控件,会加载该自定义控件的第二个构造函数,那么addHeaderView() 会被加载到布局中,PullToRefreshLayout 在xml 中加入的布局也会被添加到控件中。该布局可以包裹 ListView 和 GridView 及 ScrollView 控件。

   /**** 初始化 adapterview像ListView,GridView等;或init ScrollView*/private void initContentAdapterView(){int count=getChildCount();if (count<3)throw new IllegalArgumentException("this layout must contain 3 child views,and AdapterView or ScrollView must in the second position!");View  view=null;for (int i=0;i<count-1;++i){view=getChildAt(i);if (view instanceof AdapterView<?>){System.out.println("the type is AdapterView");mAdapterView=(AdapterView<?>)view;}if (view instanceof  ScrollView){System.out.println("thie type is ScrollView");mScrollView= (ScrollView) view;}}if (mAdapterView==null&&mScrollView==null){throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");}}

4、接下来看下项目中用到的常量和变量注释,这对阅读后续代码有帮助。

   /*** 下拉刷新*/private static final int PULL_TO_REFRESH = 2;/*** 释放刷新*/private static final int RELEASE_TO_REFRESH = 3;/*** 刷新*/private static final int REFRESHING = 4;/*** 上拉加载*/private static final int PULL_UP_STATE = 10;/*** 下拉刷新*/private static final int PULL_DOWN_STATE = 11;/*** 最后Y轴距离*/private int mLastMotionY;/*** 锁定*/private boolean mLock;

5、了解了布局如何实现,接下来就到了手势如何实现,也就是我们下拉为什么可以拉出 下拉头,这里涉及到手势相关的概念,如果不了解手可以参考博客:http://blog.csdn.net/cscfas/article/details/51372342

这里就不讲事件是如何拦截,如何分发的了,我们重点来看如下代码,这里在 ACTION_DOWN时并没有拦截事件只是记录下了 Y轴坐标,为什么呢?因为PullToRefreshLayout 是属于 ViewGroup 容器型的控件,如果ACTION_DOWN 直接被拦截了那么 ListVeiw 和 GridView 中的 item点击事件及 ScrollView中点击事件和长按事件将无法触发。

    /*** 事件拦截* @param ev* @return*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {int y = (int) ev.getRawY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:   //手指按下时记录 Y轴坐标// 首先拦截down事件,记录y坐标mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:  //滑动时 拿到移动距离 判断是否拦截手势// deltaY > 0 是向下运动,< 0是向上运动int deltaY = y - mLastMotionY;if (isRefreshViewScroll(deltaY)) {//             System.out.println("正在移动:返回true");return true;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:break;}return false;}

细心的同学会发现在 ACTION_MOVE 中有调用 isRefreshViewScroll() 函数,那么它又有什么功能呢!仔细看代码会发现它返回了一个 boolean 类型的值,是它控制这事件是否拦截,看到这里你是不是觉得它至关重要,那么就来分析一下它的结构吧!

mAdapterView 这个控件从何而来,有认真看过上面代码你就应该知道了。那么它是何方神圣呢?它就是 适配器填充控件后得到的结果,AdapterView 是适配器和控件的组合,这里主要是拿到AdapterView中的子控件,也就是ListView或 GridView中的Item,通过获取子控件的状态来动态设置 是否要拦截手势,以及设置 mPullState 状态。

mScrollView 控件也是同理,拿到子控件的状态来判断是否要拦截事件。具体代码都有注释请看代码,这里就不详解了。

  /*** 是否应该到了父View,即PullToRefreshView滑动** @param deltaY*            , deltaY > 0 是向下运动,< 0是向上运动* @return*/private boolean isRefreshViewScroll(int deltaY) {// 当头部状态是 刷新 或 底部状态是刷新时 返回 false 不拦截if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {return false;}//对于ListView和GridViewif (mAdapterView != null) {// 子view(ListView or GridView)滑动到最顶端if (deltaY > 0) {View child = mAdapterView.getChildAt(0);if (child == null) {//设置状态为下拉刷新mPullState = PULL_DOWN_STATE;//设置状态为拦截return true;}// 适配中 第一个控件高度为 0 且 第一个控件可见if (mAdapterView.getFirstVisiblePosition() == 0&& child.getTop() == 0) {//设置状态为下拉刷新mPullState = PULL_DOWN_STATE;return true;}int top = child.getTop();int padding = mAdapterView.getPaddingTop();if (mAdapterView.getFirstVisiblePosition() == 0&& Math.abs(top - padding) <= 8) {//这里之前用3可以判断,但现在不行,还没找到原因mPullState = PULL_DOWN_STATE;return true;}} else if (deltaY < 0) {  //如果移动的距离为 负值//获取适配中最后一个控件View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);if (lastChild == null) {mPullState = PULL_UP_STATE;// 如果mAdapterView中没有数据,不拦截return true;}// 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,// 等于父View的高度说明mAdapterView已经滑动到最后if (lastChild.getBottom() <= getHeight()&& mAdapterView.getLastVisiblePosition() == mAdapterView.getCount() - 1) {mPullState = PULL_UP_STATE;return true;}}}// 对于ScrollViewif (mScrollView != null) {// 子scroll view滑动到最顶端View child = mScrollView.getChildAt(0);//当移动距离为 正值  且滚动条没有滚动if (deltaY > 0 && mScrollView.getScrollY() == 0) {mPullState = PULL_DOWN_STATE;  //设置状态为下拉刷新return true;} else if (deltaY < 0&& child.getMeasuredHeight() <= getHeight()+ mScrollView.getScrollY()) {mPullState = PULL_UP_STATE;   //设置为上拉加载return true;}}return false;}

6、接下来就要见证奇迹了,就是具体如何实现 下拉刷新上拉加载更多效果的业务了,还记得上面我们有讲到手势拦截吧!如果你了解手势就知道被拦截后会执行什么函数,那就是 onTouchEvent() 函数了。

(1)、首先看下 ACTION_MOVE 这里我们来计算用户手指在屏幕上的滑动距离,还记得在 onInterceptTounchEvent()中已经对 mPullState 状态做过改变,这里开始就通过判断当前状态是下拉还是上拉来处理 HeaderView 和 FootView的显示及动画效果。

    /** 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return false)** 则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理)*/@Overridepublic boolean onTouchEvent(MotionEvent event) {if (mLock) {   //当处于锁定状态时return true;}//拿到Y轴坐标int y = (int) event.getRawY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:    //手指按下时触发  ACTION_DOWN// onInterceptTouchEvent已经记录// mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:  //手指在屏幕上滑动时触发  ACTION_MOVE//拿到用户滑动的距离int deltaY = y - mLastMotionY;if (mPullState == PULL_DOWN_STATE) {   //如果当前状态处于下拉刷新 PULL_DOWN_STATE  那么执行 headerPrepareToRefresh() 函数实现刷新效果// PullToRefreshView执行下拉Log.i(TAG, " pull down!parent view move!");headerPrepareToRefresh(deltaY);// setHeaderPadding(-mHeaderViewHeight);} else if (mPullState == PULL_UP_STATE) { //如果当前状态处于上拉加载 PULL_UP_STATEif (pullUpLoad) {   //判断用户是否启用上拉加载// PullToRefreshView执行上拉Log.i(TAG, "pull up!parent view move!");footerPrepareToRefresh(deltaY);}}mLastMotionY = y;break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: //当事件被取消时//获取当前header view 的topMargin 值int topMargin = getHeaderTopMargin();if (mPullState == PULL_DOWN_STATE) {  //如果当前状态是下拉刷新if (topMargin >= 0) {// 开始刷新headerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}} else if (mPullState == PULL_UP_STATE) {  //如果当前状态处于上拉加载if (pullUpLoad) {if (Math.abs(topMargin) >= mHeaderViewHeight+ mFooterViewHeight) {// 开始执行footer 刷新footerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}}}break;}return super.onTouchEvent(event);}

(2)、处理下拉或上拉布局被拉出效果,接下来看 headPrepareToRefresh() 和 footerPrepareToRefresh() 这两个函数实现了上拉及下拉效果 ,这里要注意 mHeaderState、mFooterState 的状态改变,它决定这是否释放刷新

  /*** header 准备刷新,手指移动过程,还没有释放** @param deltaY*            ,手指滑动的距离*/private void headerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {mHeaderTextView.setText(R.string.pull_to_refresh_release_label);mHeaderUpdateTextView.setVisibility(View.VISIBLE);mHeaderImageView.clearAnimation();mHeaderImageView.startAnimation(mFlipAnimation);//改变状态为释放刷新mHeaderState = RELEASE_TO_REFRESH;} else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放mHeaderImageView.clearAnimation();mHeaderImageView.startAnimation(mFlipAnimation);mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderState = PULL_TO_REFRESH;}}
 /*** footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view* 高度是一样,都是通过修改header view的topmargin的值来达到** @param deltaY*            ,手指滑动的距离*/private void footerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 如果header view topMargin 的绝对值大于或等于header + footer 的高度// 说明footer view 完全显示出来了,修改footer view 的提示状态if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight)&& mFooterState != RELEASE_TO_REFRESH) {mFooterTextView.setText(R.string.pull_to_refresh_footer_release_label);mFooterImageView.clearAnimation();mFooterImageView.startAnimation(mFlipAnimation);mFooterState = RELEASE_TO_REFRESH;} else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) {mFooterImageView.clearAnimation();mFooterImageView.startAnimation(mFlipAnimation);mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);mFooterState = PULL_TO_REFRESH;}}

(3)、仔细阅读上面代码,会发现所有的判断跟随着这个 headerPrepareToRefresh() 函数的返回值决定,接下来看下这个函数。判断当前 mPullState 状态 及 拉动距离是否大于设置距离,动态返回 TopMargin 及拉出的距离

  /*** 修改Header view top margin的值** @description* @param deltaY*/private int changingHeaderViewTopMargin(int deltaY) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();float newTopMargin = params.topMargin + deltaY * 0.4f;//这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了//表示如果是在上拉后一段距离,然后直接下拉if(deltaY>0&&mPullState == PULL_UP_STATE&&Math.abs(params.topMargin) <= mHeaderViewHeight){return params.topMargin;}//同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bugif(deltaY<0&&mPullState == PULL_DOWN_STATE&&Math.abs(params.topMargin)>=mHeaderViewHeight){return params.topMargin;}params.topMargin = (int) newTopMargin;mHeaderView.setLayoutParams(params);/*** 无效整个视图。如果视图是可见的,**  {@link #onDraw(android.graphics.Canvas)} 将在某个时候被调用**  这必须从UI线程调用。从非UI线程,致电致电**  {@link #postInvalidate()}.*/invalidate();return params.topMargin;}

(4)、ACTION_UP、ACTION_CANCEL 处理释放刷新和取消执行刷新,首先拿到 topMargin 既拉动的距离,通过判断拉动距离和 mPullState 状态来决定是释放刷新还是取消执行刷新。

            case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: //当事件被取消时//获取当前header view 的topMargin 值int topMargin = getHeaderTopMargin();if (mPullState == PULL_DOWN_STATE) {  //如果当前状态是下拉刷新if (topMargin >= 0) {// 开始刷新headerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}} else if (mPullState == PULL_UP_STATE) {  //如果当前状态处于上拉加载if (pullUpLoad) {if (Math.abs(topMargin) >= mHeaderViewHeight+ mFooterViewHeight) {// 开始执行footer 刷新footerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}}}break;

(5)、headerRefreshing() 、footerRefreshing() 释放刷新,这里将 Runnable 添加到UI线程中,延迟1500 毫秒达到,下拉头或上拉脚停顿效果,这里主要回调监听接口,该接口是调用 PullToRefreshLayout 控件的 Activity或FrangMent 中实现。

    /***  下拉头释放刷新**/private void headerRefreshing() {mHeaderState = REFRESHING;setHeaderTopMargin(0);mHeaderImageView.setVisibility(View.GONE);mHeaderImageView.clearAnimation();mHeaderImageView.setImageDrawable(null);mHeaderProgressBar.setVisibility(View.VISIBLE);mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);if (mOnHeaderRefreshListener != null) {/*** 使Runnable被添加到消息队列,经过规定的时间之后运行。** 运行将运行在用户界面线程。既UI线程中*/this.postDelayed(new Runnable() {@Overridepublic void run() {mOnHeaderRefreshListener.onHeaderRefresh(PullToRefreshView.this);}}, 1500);}}
    /*** 底部释放刷新*/private void footerRefreshing() {mFooterState = REFRESHING;int top = mHeaderViewHeight + mFooterViewHeight;setHeaderTopMargin(-top);mFooterImageView.setVisibility(View.GONE);mFooterImageView.clearAnimation();mFooterImageView.setImageDrawable(null);mFooterProgressBar.setVisibility(View.VISIBLE);mFooterTextView.setText(R.string.pull_to_refresh_footer_refreshing_label);if (mOnFooterRefreshListener != null) {this.postDelayed(new Runnable() {@Overridepublic void run() {mOnFooterRefreshListener.onFooterRefresh(PullToRefreshView.this);}}, 1500);}}

(5)、注意在刷新失败的时候会执行 setHeaderMargin() 该函数作用主要是实现布局的隐藏

 /*** 设置header view 的topMargin的值** @description* @param topMargin*            ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了*/private void setHeaderTopMargin(int topMargin) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();params.topMargin = topMargin;mHeaderView.setLayoutParams(params);invalidate();}

7、以上步骤基本实现了整个下拉刷新,上拉加载的功能,但是美中不足,刷新完成后我们还需要隐藏我们的布局,下面的代码是更新完后恢复初始化状态

  /*** header view 完成更新后恢复初始状态** @description hylin 2012-7-31上午11:54:23*/public void onHeaderRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mHeaderImageView.setVisibility(View.VISIBLE);mHeaderImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderProgressBar.setVisibility(View.GONE);mHeaderState = PULL_TO_REFRESH;}
    /*** footer view 完成更新后恢复初始状态*/public void onFooterRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mFooterImageView.setVisibility(View.VISIBLE);mFooterImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow_up);mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);mFooterProgressBar.setVisibility(View.GONE);mFooterState = PULL_TO_REFRESH;}

8、以上基本实现了下拉刷新上拉加载,博客也写累了,剩余的功能我就不贴代码了,可以参看Demo

下载地址:http://download.csdn.net/detail/cscfas/9524306

Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout相关推荐

  1. android 列表上拉加载更多,Android 下拉刷新,上拉加载更多控件–支持ListView,GridView和ScrollView...

    麦洛遇到这样一个需求,实现类似于IOS下拉刷新,上拉加载更多的控件.麦洛google,baidu了一番,网上有不少实现,比较常见的是国外牛人的实现,不过国外的实现基本上都是扩展于ListView,所以 ...

  2. listview下拉刷新上拉加载扩展(二)-仿美团外卖

    经过前几篇的listview下拉刷新上拉加载讲解,相信你对其实现机制有了一个深刻的认识了吧,那么这篇文章我们来实现一个高级的listview下拉刷新上拉加载-仿新版美团外卖的袋鼠动画: 项目结构: 是 ...

  3. (仿头条APP项目)6.点击过的新闻列表文字变灰和下拉刷新与滚动加载新闻数据

    文章目录 一.点击过的新闻列表文字变灰 效果图 实现思路 导入ormlite数据库类依赖 利用ormlite创建数据库和表 创建数据库类MyDbHelper 创建数据库中的新闻实体类NewInfo 页 ...

  4. android listview下拉刷新动画,android 安卓 listview 支持下拉刷新 上拉加载更多

    [1]重写listViewimport java.text.SimpleDateFormat; import java.util.Date; import com.example.testdddlea ...

  5. Android 下拉刷新上拉载入 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉载入,网上的Demo太多太多了,这 ...

  6. Android ListView 实现下拉刷新上拉加载

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/39965327 1.简介 无疑,在Android开发中,ListView是使用非常频 ...

  7. listview下拉刷新上拉加载扩展(三)-仿最新版美团外卖

    本篇是基于上篇listview下拉刷新上拉加载扩展(二)-仿美团外卖改造而来,主要调整了headview的布局,并加了两个背景动画,看似高大上,其实很简单: as源码地址:http://downloa ...

  8. vue 仿B站下拉刷新上拉加载

    vue 仿B站下拉刷新上拉加载 功能大部分都是跟B站一样的,还是有一些瑕疵和小bug的,φ(>ω<*) 先上demo连接和gitHub项目地址吧 demo展示 https://github ...

  9. Android 下拉刷新上拉加载可以左右滑动

    下面是下拉刷新上拉加载可以左右滑动的实例,下面是效果图: GitHub 下载地址:https://github.com/wuqingsen/MySlidingNested CSDN 下载地址:http ...

最新文章

  1. Pandas把dataframe中的整数数值(integer)转化为时间(日期、时间)信息实战
  2. pdf.js 使用实例
  3. PAT-1124. Raffle for Weibo Followers (20)
  4. 数据库-优化-检查慢日志是否开启
  5. 接口设计的幂等性考虑
  6. 多表操作查询 一对一
  7. Jw-alipay 1.0.0版本发布,开源支付窗管理平台
  8. 通过QMP/QGA与虚拟机进行交互
  9. 《Algorithms》Comparable 实现插入排序
  10. JSTL获取session中的值
  11. Linux下禁止使用swap及防止OOM机制导致进程被kill掉
  12. Windows7安装PADS2007详细步骤____亲自实验总结
  13. android弹出窗背景透明,Android Dialog 弹框之外的区域 默认透明背景色修改
  14. php做购物商品库存解决方法
  15. 无法打开虚拟磁盘服务器,win2008R2 修改了带有快照的父虚拟磁盘;导致启动不了...
  16. excel股票今日走势计算机,有没有可以在excel上自动显示股票实时数据的方法
  17. 能转16进制的计算机软件,16进制计算器能否完成各种不同进制的转换?
  18. KPS同意约21亿美元巨资收购Garrett全部资产
  19. 如何验证有效的身份证格式
  20. Windows使用笔记_Windows xp之开机启动项设置

热门文章

  1. mysql的主从同步问题_mysql主从同步问题梳理
  2. 通过父级id获取到其下所有子级(无穷级)——Mysql函数实现
  3. python实现excel相同条件单元格合并
  4. 失速OPPO破局:自主造芯、入局IoT
  5. switch结构语句,for循环,while循环,死循环实现猜数字小游戏,方法实现nn乘法表,数组,逆序,冒泡排序
  6. (转)Java调用Zebra条码打印机打印条码、中英文数字条码混合标签,可自由控制格式和排版...
  7. git登录失败与自动保存密码等(Windows凭据问题)
  8. 如何手动清除常见的小病毒
  9. 刷题网站LeetCode/牛客/LintCode介绍
  10. 哔哩哔哩网页数据清洗