Android下拉刷新已经被很多人写过了,网上的开源代码也很多,写这篇文章只是想记录一下自己学习的过程。

首先自定义一个下拉刷新的布局layout,布局分为2部分,一部分是开发者自己添加的layout、占据整个屏幕,一部分是隐藏在屏幕上方的刷新headview。下拉的时候把headview显示出来,实现下拉刷新的效果。


首先我定义了一个抽象类

public abstract class HeadLayout {

    //得到headview
    public abstract View getHeadView();

    //设置下拉刷新过程中headview的状态
    public abstract void setHeadViewStatus(Context context, View headView, int status);

}

 getHeadView() 就是得到头部的headview。

 setHeadViewStatus() 就是设置下拉 过程当中headview的状态变更

 本例中我实现了2个类

public class  OrdinaryHeadLayout extends HeadLayout {

    private View headView;

    public OrdinaryHeadLayout(View headView) {
        this.headView = headView;
    }

    @Override
    public View getHeadView() {
        return headView;
    }

    @Override
    public void setHeadViewStatus(Context context, View headView, int status) {
        if(context == null || headView == null) return;
        TextView textView = (TextView) headView.findViewById(R.id.text);
        switch (status) {
            case PullDownRefreshLayout.INIT_STATUS:
                textView.setText("下拉刷新");
                break;
            case PullDownRefreshLayout.TIP_STATUS:
                textView.setText("释放刷新");
                break;
            case PullDownRefreshLayout.REFRESHING_STATUS:
                textView.setText("正在刷新");
                break;
            case PullDownRefreshLayout.REFRESHING_END_STATUS:
                textView.setText("下拉刷新");
                break;
        }
    }
}

在下拉过程当中这个就是简单的设置textview的内容,在每个状态设置不同的内容。


public class PersonalityHeadLayout extends HeadLayout {

    private View headView;

    private Integer[] image = {R.drawable.icon_loading_one, R.drawable.icon_loading_two, R.drawable.icon_loading_three, R.drawable.icon_loading_four, R.drawable.icon_loading_five,
    R.drawable.icon_loading_six, R.drawable.icon_loading_seven, R.drawable.icon_loading_eight};

    private int height;

    private AnimationDrawable animationDrawable;

    public PersonalityHeadLayout(View headView) {
        this.headView = headView;
    }

    @Override
    public View getHeadView() {
        return headView;
    }

    @Override
    public void setHeadViewStatus(Context context, View headView, int status) {
        if(context == null || headView == null) return;
        if(height == 0)
            height = headView.getMeasuredHeight() / 8;
        ImageView imageView = (ImageView) headView.findViewById(R.id.image);
        switch (status) {
            case PullDownRefreshLayout.INIT_STATUS:
                if(headView.getTop() > -headView.getMeasuredHeight() && headView.getTop() < -headView.getMeasuredHeight() + height) {
                    imageView.setImageResource(R.drawable.icon_loading_one);
                }
                if(headView.getTop() > -headView.getMeasuredHeight() + height && headView.getTop() < -headView.getMeasuredHeight() + height * 2) {
                    imageView.setImageResource(R.drawable.icon_loading_two);
                }
                if(headView.getTop() > -headView.getMeasuredHeight() + height * 2 && headView.getTop() < -headView.getMeasuredHeight() + height * 3) {
                    imageView.setImageResource(R.drawable.icon_loading_three);
                }
                if(headView.getTop() >  -headView.getMeasuredHeight() + height * 3 && headView.getTop() < -headView.getMeasuredHeight() + height * 4) {
                    imageView.setImageResource(R.drawable.icon_loading_four);
                }
                if(headView.getTop() > -headView.getMeasuredHeight() + height * 4 && headView.getTop() < -headView.getMeasuredHeight() + height * 5) {
                    imageView.setImageResource(R.drawable.icon_loading_five);
                }
                if(headView.getTop() > -headView.getMeasuredHeight() + height * 5 && headView.getTop() < -headView.getMeasuredHeight() + height * 6) {
                    imageView.setImageResource(R.drawable.icon_loading_six);
                }
                if(headView.getTop() > -headView.getMeasuredHeight() + height * 6 && headView.getTop() < -headView.getMeasuredHeight() + height * 7) {
                    imageView.setImageResource(R.drawable.icon_loading_seven);
                }
                if(headView.getTop() > -headView.getMeasuredHeight() + height * 7 && headView.getTop() < -headView.getMeasuredHeight() + height * 8) {
                    imageView.setImageResource(R.drawable.icon_loading_eight);
                }
                break;
            case PullDownRefreshLayout.TIP_STATUS:
                imageView.setImageResource(R.drawable.icon_loading_eight);
                break;
            case PullDownRefreshLayout.REFRESHING_STATUS:
                animationDrawable = (AnimationDrawable) context.getResources().getDrawable(R.drawable.loading);
                imageView.setImageDrawable(animationDrawable);
                animationDrawable.start();
                break;
            case PullDownRefreshLayout.REFRESHING_END_STATUS:
                if(animationDrawable == null) return;
                   animationDrawable.stop();
                   animationDrawable = null;
                break;
        }
    }
}

这个就是一个图片动态的动画效果的实现。


通过 

public void setHeadLayout(HeadLayout headLayout) {
         this.headLayout = headLayout;
         mHead.addView(headLayout.getHeadView(), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
     }


方法来设置不同的HeadLayout来实现不同的动画效果


下来就要自定义PullDownRefreshLayout

package com.qingcong.pulldownrefresh;

import android.animation.IntEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.LinearLayout;

/**
 * Created by chenpengfei on 2016/4/25.
 * 下拉刷新
 *  HeadLayout 抽象类是用来做自定义headview效果的,实现HeadLayout 抽象类,实现里面的方法来定义属于自己的动画效果
 *  本例子实现了2中效果,普通效果的OrdinaryHeadLayout, 个性效果PersonalityHeadLayout
 *  本例实现逻辑是:
 *                          初始化view的时候添加一个空的linearlayout类,用来添加自定义的headview,通过实现HeadLayout抽象类来添加headview,详情查看setHeadLayout()方法
 *                          手指滑动触摸屏幕的时候重写了拦截方法,判断如果当前view正在执行动画或者当前view还没有滚动到最顶部还可以向下滚动,就不拦截事件,否则拦截事件
 *                          拦截以后在onTouchEvent实现滑动逻辑,计算滑动的总距离,然后requestLayout();方法通知布局重绘,通过不断的调用view的layout方法来实现动画的视觉效果
 */
public class PullDownRefreshLayout extends ViewGroup {

    public final static int INIT_STATUS = 0; //初始状态
    public final static int TIP_STATUS = 1; //提示状态
    public final static int REFRESHING_STATUS = 2; //正在刷新
    public final static int REFRESHING_END_STATUS = 3; //刷新完成状态

    private HeadLayout headLayout;
    //是否正在拖
    private boolean mIsBeingDragged;
     //是否正在刷新
    private boolean mIsRefreshing;
    //是否正在重置
    private boolean mIsReturnToStart;
    //正准备滑动到加载转态
    private boolean mScrollToLoading;
    //拖的对象
    private View mTarget;
    //head view
    private LinearLayout mHead;
    // 检测到手机的最小滑动值
    private int mTouchSlop;
    private float mInitialMotionY;
    private float mInitalMotionX;
    //拖动的总大小
    private float offsetSumTop;
    private OnRefreshListener onRefreshListener;
    private Context context;
    private ValueAnimator valueAnimator;


    public PullDownRefreshLayout(Context context) {
        super(context);
    }

    public PullDownRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        addView(createHeadLayout(context));
    }

    public PullDownRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     *  创建一个空的linearlayout,用来添加自定义的headview,不同的headview会呈现不同的滑动视觉
     */
    private LinearLayout createHeadLayout(Context context) {
        LinearLayout linearLayout = new LinearLayout(context);
        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        linearLayout.setTag("head");
        linearLayout.setLayoutParams(layoutParams);
        return linearLayout;
    }

    /**
     * 设置自定义headview的实现滑动效果类
     */
    public void setHeadLayout(HeadLayout headLayout) {
        this.headLayout = headLayout;
        mHead.addView(headLayout.getHeadView(), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    }


    /**
     *  得到拖的目标view
     */
    private void obtainTarget() {
        if(mTarget == null) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if(childView.getTag() == null) {
                    mTarget = childView;
                } else {
                    //得到headview
                    mHead = (LinearLayout) childView;
                }
            }
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        obtainTarget();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(mTarget == null)
            obtainTarget();
        if(mTarget == null)
            return;
        mHead.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
                getChildMeasureSpec(heightMeasureSpec, 0, mHead.getLayoutParams().height));
        mTarget.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int mHleft = getPaddingLeft();
        final int mTleft = getPaddingLeft();
        int mHeadTop = (int) (-mHead.getMeasuredHeight() + offsetSumTop);
        if(mHeadTop < -mHead.getMeasuredHeight()) {
            mHeadTop = -mHead.getMeasuredHeight();
        }
        mHead.layout(mHleft, mHeadTop, mHleft + mHead.getMeasuredWidth(), mHeadTop + mHead.getMeasuredHeight());
        mTarget.layout(mTleft, mHead.getBottom(), mTleft + mTarget.getMeasuredWidth(), mTarget.getMeasuredHeight() + mHead.getBottom());
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        obtainTarget();
        //如果下拉操作在执行就不拦截
        if(mIsRefreshing || mIsReturnToStart || mScrollToLoading || canChildScrollUp()) {
            return false;
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                offsetSumTop = 0;
                mIsBeingDragged = false;
                mInitialMotionY = ev.getY();
                mInitalMotionX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                final float y = ev.getY();
                final float yDiff = y - mInitialMotionY;
                final float x = ev.getX();
                final float xDiff = x - mInitalMotionX;
                //如果是X轴事件就不拦截
                if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                        mIsBeingDragged = false;
                } else {
                    if (yDiff > mTouchSlop && !mIsBeingDragged) {
                        mInitialMotionY = mInitialMotionY + yDiff;
                        mIsBeingDragged = true;
                    }
                }
                break;
        }
        return mIsBeingDragged;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                final float y = event.getY();
                final float yDiff = (y - mInitialMotionY) / 2;
                offsetSumTop += yDiff;
                mInitialMotionY = mInitialMotionY + yDiff * 2;
                headLayout.setHeadViewStatus(context, mHead, mHead.getTop() >=0 ? TIP_STATUS : INIT_STATUS);
                //发起绘制
                requestLayout();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if(mHead.getTop() > 0) {
                    mScrollToLoading = true;
                }
                if(mHead.getTop() <= 0) {
                    mIsReturnToStart = true;
                }
                performAnimate(mHead.getTop(), mHead.getTop() > 0 ? 0 : -mHead.getMeasuredHeight());
                break;
        }
        return true;
    }

    /**
     * 滑动动画
     */
    private void performAnimate(final int start, final int end) {
        valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            private IntEvaluator mEvaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                if(mHead == null || mTarget == null) return;
                //获得当前动画的进度值,整型,1-100之间
                int currentValue = (Integer) animator.getAnimatedValue();
                float fraction = currentValue / 100f;
                int top = mEvaluator.evaluate(fraction, start, end);
                mHead.layout(mHead.getLeft(), top, mHead.getRight(), mHead.getMeasuredHeight() + top);
                mTarget.layout(mTarget.getLeft(), mHead.getBottom(), mTarget.getRight(), mHead.getBottom() + mTarget.getMeasuredHeight());
                setAnimationEndStatus(currentValue, end);
            }
        });
        valueAnimator.setDuration(Math.abs(start - end) / mTouchSlop * 20).start();
    }

    /**
     *  还原到初始状态位置
     */
    public void returnToStart() {
        mIsRefreshing = false;
        mIsReturnToStart = true;
        performAnimate(mHead.getTop(), - mHead.getMeasuredHeight());
    }

    /**
     * 设置动画结束后参数的变更
     */
    public void setAnimationEndStatus(int currentValue, int end) {
        if(currentValue == 100) {
            if(end == 0) {
                if(onRefreshListener != null) {
                    mIsRefreshing = true;
                    mScrollToLoading = false;
                    offsetSumTop = mHead.getMeasuredHeight();
                    headLayout.setHeadViewStatus(context, mHead, REFRESHING_STATUS);
                    onRefreshListener.onRefresh();
                }
            } else {
                mIsReturnToStart = false;
                offsetSumTop = 0;
                headLayout.setHeadViewStatus(context, mHead, REFRESHING_END_STATUS);
            }
            valueAnimator = null;
        }
    }

    /**
     *  还原所有参数
     */
    public void destory() {
        if(valueAnimator != null) {
            valueAnimator.cancel();
            valueAnimator = null;
        }
        offsetSumTop = 0;
        headLayout.setHeadViewStatus(context, mHead, REFRESHING_END_STATUS);
        requestLayout();
        mIsRefreshing = false;
        mIsReturnToStart = false;
        mScrollToLoading = false;
        mIsBeingDragged = false;
        offsetSumTop = 0;
    }

    /**
     *  是否滚动到了顶端
     */
    public boolean canChildScrollUp() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mTarget, -1);
        }
    }

    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
        this.onRefreshListener = onRefreshListener;
    }

    public interface OnRefreshListener {
        public void onRefresh();
    }
}



有不对的地方希望大家可以说出来,一起进步

代码地址

https://github.com/chenpengfei88/PullDownRefresh