Android实现下拉刷新和上拉加载更多的RecyclerView和ScrollView

PullRefreshRecyclerView.java
/**
 * 类说明:下拉刷新上拉加载更多的RecyclerView
 * Author: gaobaiq
 * Date: 2016/5/9 18:09
 */
public class PullRefreshRecyclerView extends PullRefreshView {

    /**
     * 内置的RecyclerView;
     */
    private RecyclerView mRecyclerView;

    /**
     * 可见的最后一个item
     */
    private int lastVisibleItem;

    /**
     * 可见的第一个item
     */
    private int firstVisibleItem;

    /**
     * 空数据提示布局容器
     */
    private LinearLayout mEmptyLayout;

    /**
     * 是否自动上拉刷新
     */
    private boolean isAutomaticUp = false;

    public PullRefreshRecyclerView(Context context) {
        this(context, null);
    }

    public PullRefreshRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullRefreshRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mRecyclerView = new RecyclerView(context);
        mRecyclerView.setClipToPadding(false);
        this.addView(mRecyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        initListener();
    }

    private void initListener() {
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (isAutomaticUp && newState == RecyclerView.SCROLL_STATE_IDLE && pullUp()) {
                    triggerPullUpRefresh();
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                getVisibleItem();
            }
        });
    }

    private int getMax(int[] arr) {
        int max = arr[0];
        for (int x = 1; x < arr.length; x++) {
            if (arr[x] > max)
                max = arr[x];
        }
        return max;
    }

    private int getMin(int[] arr) {
        int min = arr[0];
        for (int x = 1; x < arr.length; x++) {
            if (arr[x] < min)
                min = arr[x];
        }
        return min;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getVisibleItem();
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 获取当前第一个显示的item 和 最后一个显示的item.
     */
    private void getVisibleItem() {
        RecyclerView.LayoutManager layout = mRecyclerView.getLayoutManager();

        if (layout != null) {
            if (layout instanceof LinearLayoutManager) {
                lastVisibleItem = ((LinearLayoutManager) layout).findLastVisibleItemPosition();
                firstVisibleItem = ((LinearLayoutManager) layout).findFirstVisibleItemPosition();
            } else if (layout instanceof GridLayoutManager) {
                lastVisibleItem = ((GridLayoutManager) layout).findLastVisibleItemPosition();
                firstVisibleItem = ((GridLayoutManager) layout).findFirstVisibleItemPosition();
            } else if (layout instanceof StaggeredGridLayoutManager) {
                int[] lastPositions = new int[((StaggeredGridLayoutManager) layout).getSpanCount()];
                ((StaggeredGridLayoutManager) layout).findLastVisibleItemPositions(lastPositions);
                lastVisibleItem = getMax(lastPositions);
                int[] firstPositions = new int[((StaggeredGridLayoutManager) layout).getSpanCount()];
                ((StaggeredGridLayoutManager) layout).findFirstVisibleItemPositions(firstPositions);
                firstVisibleItem = getMin(lastPositions);
            }
        }
    }

    @Override
    protected boolean isChildBottom(ViewGroup viewGroup) {

        boolean resultValue = false;
        int childNum = viewGroup.getChildCount();

        if (childNum == 0 || mRecyclerView.getChildCount() == 0) {
            resultValue = false;
        } else {

            if (mRecyclerView.equals(getChildAt(2)) && lastVisibleItem != mRecyclerView.getAdapter().getItemCount() - 1) {
                return false;
            }

            View view = viewGroup.getChildAt(childNum - 1);
            int bottomMargin = ((MarginLayoutParams) view.getLayoutParams()).bottomMargin;
            if (view.getBottom() + bottomMargin + viewGroup.getPaddingBottom() <= getHeight()) {
                resultValue = true;
            }
        }
        return resultValue;
    }

    @Override
    protected boolean isChildTop(ViewGroup viewGroup) {
        boolean resultValue = false;
        int childNum = viewGroup.getChildCount();

        if (childNum == 0) {
            resultValue = true;
        } else {
            if (mRecyclerView.equals(getChildAt(2)) && firstVisibleItem != 0) {
                return false;
            }

            View view = viewGroup.getChildAt(0);
            int topMargin = ((MarginLayoutParams) view.getLayoutParams()).topMargin;

            if (view.getTop() - topMargin - viewGroup.getPaddingTop() >= 0) {
                resultValue = true;
            }
        }
        return resultValue;
    }

    @Override
    public void triggerPullDownRefresh() {
        mRecyclerView.scrollToPosition(0);
        super.triggerPullDownRefresh();
    }

    /**
     * 设置空布局
     *
     * @param emptyView
     * @param layoutGravity 空布局在父布局的方向
     */
    public void setEmptyView(View emptyView, int layoutGravity) {
        if (mEmptyLayout == null) {
            initEmptyLayout();
        }
        mEmptyLayout.setGravity(layoutGravity);
        mEmptyLayout.addView(emptyView);
    }

    /**
     * 显示空布局
     */
    public void showEmptyView() {
        if (mEmptyLayout != null && mEmptyLayout.getParent() == null) {
            addView(mEmptyLayout, 2);
        }
        if (mRecyclerView.getParent() != null) {
            removeView(mRecyclerView);
        }
    }

    /**
     * 隐藏空布局
     */
    public void hideEmptyView() {
        if (mRecyclerView.getParent() == null) {
            addView(mRecyclerView, 2);
        }
        if (mEmptyLayout != null && mEmptyLayout.getParent() != null) {
            removeView(mEmptyLayout);
        }
    }

    private void initEmptyLayout() {
        mEmptyLayout = new LinearLayout(mContext);
        LayoutParams lp = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        mEmptyLayout.setLayoutParams(lp);
        mEmptyLayout.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });
    }

    /**
     * 滑动到底部时,是否自动触发上拉加载更多。
     *
     * @param isAutomaticUp
     */
    public void isAutomaticUp(boolean isAutomaticUp) {
        this.isAutomaticUp = isAutomaticUp;
    }

    //提供获取内置RecyclerView的方法
    public RecyclerView getRecyclerView() {
        return mRecyclerView;
    }

    //********* 提供一系列对内置RecyclerView的操作方法 *********//

    public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
        mRecyclerView.setLayoutManager(layoutManager);
    }

    public void setAdapter(RecyclerView.Adapter adapter) {
        mRecyclerView.setAdapter(adapter);
    }

    public void setItemAnimator(RecyclerView.ItemAnimator animator) {
        mRecyclerView.setItemAnimator(animator);
    }

    public void addItemDecoration(RecyclerView.ItemDecoration decor) {
        mRecyclerView.addItemDecoration(decor);
    }

    public void addItemDecoration(RecyclerView.ItemDecoration decor, int index) {
        mRecyclerView.addItemDecoration(decor, index);
    }

    public void setViewPadding(int left, int top, int right, int bottom) {
        mRecyclerView.setPadding(left, top, right, bottom);
    }

}

PullRefreshView.java

/**
 * 类说明: 自定义的上下拉刷新控件
 * Author: gaobaiq
 * Date: 2016/5/9 17:27
 */
public class PullRefreshView extends ViewGroup {

    private static final String TAG = PullRefreshView.class.getSimpleName();

    protected Context mContext;

    //头部下拉的最小高度
    private static final int HEAD_DEFAULT_HEIGHT = 100;

    //尾部上拉的最小高度
    private static final int TAIL_DEFAULT_HEIGHT = 100;

    /**
     * 头部容器
     */
    private LinearLayout mHeadLayout;

    /**
     * 头部View
     */
    private View mHead;

    /**
     * 头部的高度
     */
    private int mHeadHeight = HEAD_DEFAULT_HEIGHT;

    /**
     * 尾部容器
     */
    private LinearLayout mTailLayout;

    /**
     * 尾部View
     */
    private View mTail;

    /**
     * 尾部的高度
     */
    private int mTailHeight = TAIL_DEFAULT_HEIGHT;

    /**
     * 滑动的偏移量
     */
    private int mScrollOffset = 0;

    /**
     * 标记 无状态(既不是上拉 也 不是下拉)
     */
    private final int STATE_NOT = -1;

    /**
     * 标记 上拉状态
     */
    private final int STATE_UP = 1;

    /**
     * 标记 下拉状态
     */
    private final int STATE_DOWN = 2;

    /**
     * 当前状态
     */
    private int mCurrentState = STATE_NOT;

    /**
     * 是否处于下拉 正在更新状态
     */
    private boolean mIsPullDown = false;

    /**
     * 是否处于上拉 正在加载状态
     */
    private boolean mIsPullUp = false;

    /**
     * 是否启用下拉功能(默认不开启)
     */
    private boolean mIsDownRefresh = true;

    /**
     * 是否启用上拉功能(默认不开启)
     */
    private boolean mIsUpRefresh = false;

    /**
     * 加载状态
     */
    private boolean mIsLoading = false;

    private int mDamp = 4;

    /**
     * 头部状态监听器
     */
    private OnHeadStateListener mHeadStateListener;

    /**
     * 尾部状态监听器
     */
    private OnTailStateListener mTailStateListener;

    /**
     * 上拉监听器
     */
    private OnPullUpRefreshListener mPullUpRefreshListener;

    /**
     * 下拉监听器
     */
    private OnPullDownRefreshListener mPullDownRefreshListener;

    /**
     * 是否还有更多数据。
     */
    private boolean isMore = true;

    public PullRefreshView(Context context) {
        this(context, null);
    }

    public PullRefreshView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullRefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        setClipToPadding(false);
        initHeadLayout();
        initTailLayout();
    }

    /**
     * 初始化头部
     */
    private void initHeadLayout() {
        mHeadLayout = new LinearLayout(mContext);
        LayoutParams lp = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        mHeadLayout.setGravity(Gravity.CENTER | Gravity.BOTTOM);
        mHeadLayout.setLayoutParams(lp);
        addView(mHeadLayout);
    }

    /**
     * 设置头部View
     *
     * @param head
     */
    public void setHead(View head) {
        mHead = head;
        mHeadLayout.removeAllViews();
        mHeadLayout.addView(mHead);

        //获取头部高度
        mHeadLayout.post(new Runnable() {
            @Override
            public void run() {
                if (mHead.getHeight() > HEAD_DEFAULT_HEIGHT) {
                    mHeadHeight = mHead.getHeight();
                } else {
                    mHeadHeight = HEAD_DEFAULT_HEIGHT;
                }
                Log.e(TAG, "mHeadHeight" + mHeadHeight);
                //当获取到头部高度的时候,如果正处于下拉刷新状态,应该把头部打开。
                if (mIsPullDown) {
                    scroll(-mHeadHeight);
                }
                invalidate();
            }
        });
    }

    /**
     * 初始化尾部
     */
    private void initTailLayout() {
        mTailLayout = new LinearLayout(mContext);
        LayoutParams lp = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        mTailLayout.setGravity(Gravity.CENTER | Gravity.BOTTOM);
        mTailLayout.setLayoutParams(lp);
        addView(mTailLayout);
    }

    /**
     * 设置尾部View
     *
     * @param tail
     */
    public void setTail(View tail) {
        mTail = tail;
        mTailLayout.removeAllViews();
        mTailLayout.addView(mTail);

        //获取尾部高度
        mTailLayout.post(new Runnable() {
            @Override
            public void run() {
                if (mTail.getHeight() > TAIL_DEFAULT_HEIGHT) {
                    mTailHeight = mTail.getHeight();
                } else {
                    mTailHeight = TAIL_DEFAULT_HEIGHT;
                }
                Log.e(TAG, "mTailHeight" + mTailHeight);
                //当获取到尾部高度的时候,如果正处于上拉刷新状态,应该把尾部打开。
                if (mIsPullUp) {
                    scroll(mTailHeight);
                }
            }
        });
        invalidate();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        //布局头部
        View head = getChildAt(0);
        head.layout(getPaddingLeft(), -mHeadHeight, getPaddingLeft() + head.getMeasuredWidth(), 0);

        //布局尾部
        View tail = getChildAt(1);
        tail.layout(getPaddingLeft(), getMeasuredHeight(), getPaddingLeft() + tail.getMeasuredWidth(), getMeasuredHeight() + mTailHeight);

        //布局内容容器
        int count = getChildCount();
        if (count > 2) {
            View content = getChildAt(2);
            content.layout(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + content.getMeasuredWidth(), getPaddingTop() + content.getMeasuredHeight());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //测量头部高度
        View head = getChildAt(0);
        measureChild(head, widthMeasureSpec, heightMeasureSpec);

        //测量尾部高度
        View tail = getChildAt(1);
        measureChild(tail, widthMeasureSpec, heightMeasureSpec);

        //测量内容容器宽高
        int count = getChildCount();
        int contentHeight = 0;
        int contentWidth = 0;
        if (count > 2) {
            View content = getChildAt(2);
            measureChild(content, widthMeasureSpec, heightMeasureSpec);
            contentHeight = content.getMeasuredHeight();
            contentWidth = content.getMeasuredWidth();
        }

        //设置PullRefresView的宽高
        setMeasuredDimension(measureWidth(widthMeasureSpec, contentWidth), measureHeigth(heightMeasureSpec, contentHeight));
    }

    private int measureWidth(int measureSpec, int contentWidth) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = contentWidth + getPaddingLeft() + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }

        return result;
    }

    private int measureHeigth(int measureSpec, int contentHeight) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = contentHeight + getPaddingTop() + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }

        return result;
    }

    /**
     * 设置是否启用上下拉功能
     *
     * @param isDownRefresh 是否开启下拉功能 默认开启
     * @param isUpRefresh   是否开启上拉功能 默认不开启
     */
    public void setRefresh(boolean isDownRefresh, boolean isUpRefresh) {
        mIsDownRefresh = isDownRefresh;
        mIsUpRefresh = isUpRefresh;
    }

    /**
     * 还原
     */
    private void restore() {
        mCurrentState = STATE_NOT;
        scroll(0);
    }

    /**
     * 通知刷新完成
     */
    public void refreshFinish() {
        restore();
        if (mIsLoading) {
            mIsLoading = false;
            if (mIsPullUp) {
                mIsPullUp = false;
                if (mTailStateListener != null && isMore) {
                    mTailStateListener.onRetractTail(mTail);
                }
            } else if (mIsPullDown) {
                mIsPullDown = false;
                if (mHeadStateListener != null) {
                    mHeadStateListener.onRetractHead(mHead);
                }
            }
        }
    }

    public void isMore(boolean isMore) {
        this.isMore = isMore;
        if (mTailStateListener != null) {
            if (isMore) {
                mTailStateListener.onHasMore(mTail);
            } else {
                mTailStateListener.onNotMore(mTail);
            }
        }
    }

    /**
     * 触发下拉刷新
     */
    public void triggerPullDownRefresh() {

        if (!mIsDownRefresh) {
            return;
        }

        if (!mIsLoading) {
            mIsLoading = true;
            mIsPullDown = true;
            mCurrentState = STATE_NOT;
            scroll(-mHeadHeight);
            if (mHeadStateListener != null) {
                mHeadStateListener.onRefreshHead(mHead);
            }

            if (mPullDownRefreshListener != null) {
                mPullDownRefreshListener.onRefresh();
            }
        }
    }

    /**
     * 触发上拉刷新
     */
    public void triggerPullUpRefresh() {

        if (!mIsUpRefresh) {
            return;
        }

        if (!mIsLoading) {
            mIsLoading = true;
            mIsPullUp = true;
            mCurrentState = STATE_NOT;
            scroll(mTailHeight);
            if (mTailStateListener != null && isMore) {
                mTailStateListener.onRefreshTail(mTail);
            }

            if (isMore) {
                if (mPullUpRefreshListener != null) {
                    mPullUpRefreshListener.onRefresh();
                }
            } else {
                refreshFinish();
            }

        }
    }

    /**
     * @param offset
     */
    private void scroll(int offset) {

        if (offset < 0 && !mIsDownRefresh) {
            return;
        }

        if (offset > 0 && !mIsUpRefresh) {
            return;
        }

        scrollTo(0, offset);
        mScrollOffset = Math.abs(offset);

        if (mCurrentState == STATE_DOWN && mHeadStateListener != null) {
            mHeadStateListener.onScrollChange(mHead, mScrollOffset, mScrollOffset >= mHeadHeight ? 100 : mScrollOffset * 100 / mHeadHeight);
        }

        if (mCurrentState == STATE_UP && mTailStateListener != null && isMore) {
            mTailStateListener.onScrollChange(mTail, mScrollOffset, mScrollOffset >= mTailHeight ? 100 : mScrollOffset * 100 / mTailHeight);
        }
    }

    /**
     * 设置拉动阻力 (1到10)
     *
     * @param damp
     */
    public void setDamp(int damp) {
        if (damp < 1) {
            mDamp = 1;
        } else if (damp > 10) {
            mDamp = 10;
        } else {
            mDamp = damp;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_MOVE:
                if (mY > y) {
                    if (mCurrentState == STATE_UP) {
                        scroll((mY - y) / mDamp);
                    }
                } else if (mCurrentState == STATE_DOWN) {
                    scroll((mY - y) / mDamp);
                }
                break;
            case MotionEvent.ACTION_UP:
                if (!mIsPullDown && !mIsPullUp) {
                    if (mCurrentState == STATE_DOWN) {
                        if (mScrollOffset < mHeadHeight) {
                            restore();
                        } else {
                            triggerPullDownRefresh();
                        }
                    } else if (mCurrentState == STATE_UP) {
                        if (mScrollOffset < mTailHeight) {
                            restore();
                        } else {
                            triggerPullUpRefresh();
                        }
                    } else {
                        restore();
                    }
                }
                mY = 0;

                break;
            default:

                break;
        }
        return super.onTouchEvent(event);
    }

    int mY = 0;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        int y = (int) ev.getY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mY = (int) ev.getY();
                return false;
            case MotionEvent.ACTION_MOVE:
                if (mIsLoading) {
                    return false;
                }

                if (pullDown() && y - mY > 20) {
                    mCurrentState = STATE_DOWN;
                    return true;
                }

                if (pullUp() && mY - y > 20) {
                    mCurrentState = STATE_UP;
                    return true;
                }

                return false;
            case MotionEvent.ACTION_UP:

                return false;
        }

        return false;
    }

    protected boolean pullDown() {
        return mCurrentState != STATE_UP && mIsDownRefresh && isTop();
    }

    protected boolean pullUp() {
        return mCurrentState != STATE_DOWN && mIsUpRefresh && isBottom();
    }

    protected boolean isTop() {

        if (getChildCount() < 2) {
            return true;
        }

        View view = getChildAt(2);

        if (view instanceof ViewGroup) {

            if (view instanceof ScrollView) {
                ScrollView scrollView = (ScrollView) view;
                return scrollView.getScrollY() <= 0;
            } else {
                return isChildTop((ViewGroup) view);
            }
        } else {
            return true;
        }
    }

    protected boolean isChildTop(ViewGroup viewGroup) {
        int minY = 0;
        int count = viewGroup.getChildCount();
        for (int i = 0; i < count; i++) {
            View view = viewGroup.getChildAt(i);
            int topMargin = 0;
            LayoutParams lp = view.getLayoutParams();
            if (lp instanceof MarginLayoutParams) {
                topMargin = ((MarginLayoutParams) lp).topMargin;
            }
            int top = view.getTop() - topMargin;
            minY = Math.min(minY, top);
        }
        return minY >= 0;
    }

    protected boolean isBottom() {

        if (getChildCount() < 2) {
            return false;
        }

        View view = getChildAt(2);

        if (view instanceof ViewGroup) {
            if (view instanceof ScrollView) {
                ScrollView scrollView = (ScrollView) view;
                if (scrollView.getChildCount() > 0) {
                    return scrollView.getScrollY() >= scrollView.getChildAt(0).getHeight() - scrollView.getHeight();
                } else {
                    return true;
                }
            } else {
                return isChildBottom((ViewGroup) view);
            }
        } else {
            return true;
        }
    }

    protected boolean isChildBottom(ViewGroup viewGroup) {
        int maxY = 0;
        int count = viewGroup.getChildCount();

        if (count == 0) {
            return false;
        }

        for (int i = 0; i < count; i++) {
            View view = viewGroup.getChildAt(i);
            int bottomMargin = 0;
            LayoutParams lp = view.getLayoutParams();
            if (lp instanceof MarginLayoutParams) {
                bottomMargin = ((MarginLayoutParams) lp).bottomMargin;
            }
            int bottom = view.getBottom() + bottomMargin;
            maxY = Math.max(maxY, bottom);
        }

        int h = viewGroup.getMeasuredHeight() - viewGroup.getPaddingBottom();

        return maxY <= h;
    }

    /**
     * 设置头部监听器
     *
     * @param listener
     */
    public void setOnHeadStateListener(OnHeadStateListener listener) {
        mHeadStateListener = listener;
    }

    /**
     * 设置尾部监听器
     *
     * @param listener
     */
    public void setOnTailStateListener(OnTailStateListener listener) {
        mTailStateListener = listener;
    }

    /**
     * 设置上拉监听器
     *
     * @param listener
     */
    public void setOnPullUpRefreshListener(OnPullUpRefreshListener listener) {
        mPullUpRefreshListener = listener;
    }

    /**
     * 设置下拉监听器
     *
     * @param listener
     */
    public void setOnPullDownRefreshListener(OnPullDownRefreshListener listener) {
        mPullDownRefreshListener = listener;
    }

    /**
     * 移除头部监听器
     */
    public void removeOnHeadStateListener() {
        mHeadStateListener = null;
    }

    /**
     * 移除尾部监听器
     */
    public void removeOnTailStateListener() {
        mTailStateListener = null;
    }

    /**
     * 移除上拉监听器
     */
    public void removeOnPullUpRefreshListener() {
        mPullUpRefreshListener = null;
    }

    /**
     * 移除下拉监听器
     */
    public void removeOnPullDownRefreshListener() {
        mPullDownRefreshListener = null;
    }

    //----------------  监听接口  -------------------//

    /**
     * 头部状态监听器
     */
    public interface OnHeadStateListener {

        /**
         * 头部滑动变化
         *
         * @param head         头部View
         * @param scrollOffset 滑动距离
         * @param scrollRatio  从开始到触发阀值的滑动比率(0到100)如果滑动到达了阀值,就算在滑动,这个值也是100
         */
        void onScrollChange(View head, int scrollOffset, int scrollRatio);

        /**
         * 头部处于刷新状态 (触发下拉刷新的时候调用)
         *
         * @param head 头部View
         */
        void onRefreshHead(View head);

        /**
         * 头部收起
         *
         * @param head 头部View
         */
        void onRetractHead(View head);

    }

    /**
     * 头部状态监听器
     */
    public interface OnTailStateListener {

        /**
         * 尾部滑动变化
         *
         * @param tail         尾部View
         * @param scrollOffset 滑动距离
         * @param scrollRatio  从开始到触发阀值的滑动比率(0到100)如果滑动到达了阀值,就算在滑动,这个值也是100
         */
        void onScrollChange(View tail, int scrollOffset, int scrollRatio);

        /**
         * 尾部处于加载状态 (触发上拉加载的时候调用)
         *
         * @param tail 尾部View
         */
        void onRefreshTail(View tail);

        /**
         * 尾部收起
         *
         * @param tail 尾部View
         */
        void onRetractTail(View tail);

        /**
         * 没有更多
         *
         * @param tail
         */
        void onNotMore(View tail);

        /**
         * 有更多
         *
         * @param tail
         */
        void onHasMore(View tail);
    }

    /**
     * 上拉加载监听器
     */
    public interface OnPullUpRefreshListener {
        void onRefresh();
    }

    /**
     * 下拉更新监听器
     */
    public interface OnPullDownRefreshListener {
        void onRefresh();
    }
}

HeadView.java

/**
 * 类说明: 下拉刷新头部View
 * Author: gaobaiq
 * Date: 2016/5/9 19:07
 */
public class HeadView extends LinearLayout implements PullRefreshView.OnHeadStateListener {

    ImageView ivHeaderDownArrow;
    ImageView ivHeaderLoading;
    TextView textView;

    AnimationDrawable animationDrawable;

    private boolean isReach = false;

    public HeadView(Context context) {
        super(context);
        animationDrawable = (AnimationDrawable) getResources().getDrawable(R.drawable.progress_round);
        init(context);
    }

    private void init(Context context) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View layout = inflater.inflate(R.layout.head_view_layout, this, false);
        this.addView(layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        initView(layout);
        restore();
        this.setPadding(0, 30, 0, 20);
    }

    private void initView(View view){
        ivHeaderDownArrow = (ImageView)view.findViewById(R.id.iv_header_down_arrow);
        ivHeaderLoading = (ImageView)view.findViewById(R.id.iv_header_loading);
        textView = (TextView)view.findViewById(R.id.tv_header_state);
    }

    @Override
    public void onScrollChange(View head, int scrollOffset, int scrollRatio) {

        if (scrollRatio == 100 && !isReach) {
            textView.setText("松开刷新");
            ivHeaderDownArrow.setRotation(180);
            isReach = true;
        } else if (scrollRatio != 100 && isReach) {
            textView.setText("下拉刷新");
            ivHeaderDownArrow.setRotation(0);
            isReach = false;
        }
    }

    @Override
    public void onRefreshHead(View head) {
        ivHeaderLoading.setVisibility(VISIBLE);
        ivHeaderDownArrow.setVisibility(GONE);
        ivHeaderLoading.setImageDrawable(animationDrawable);
        animationDrawable.start();
        textView.setText("正在刷新");
    }

    @Override
    public void onRetractHead(View head) {
        restore();
        animationDrawable.stop();
        isReach = false;
    }

    private void restore() {
        ivHeaderLoading.setVisibility(GONE);
        ivHeaderDownArrow.setVisibility(VISIBLE);
        ivHeaderLoading.setImageResource(R.drawable.loading1);
        ivHeaderDownArrow.setImageResource(R.drawable.icon_down_arrow);
        ivHeaderDownArrow.setRotation(0);
        textView.setText("下拉刷新");
    }
}

TailView.java

/**
 * 类说明: 尾部上拉加载更多
 * Author: gaobaiq
 * Date: 2016/4/18 10:12
 */
public class TailView extends LinearLayout implements PullRefreshView.OnTailStateListener {

    ImageView ivHeaderDownArrow;
    ImageView ivHeaderLoading;
    TextView textView;

    AnimationDrawable animationDrawable;

    private boolean isReach = false;
    private boolean isMore = true;

    public TailView(Context context) {
        super(context);
        animationDrawable = (AnimationDrawable) getResources().getDrawable(R.drawable.progress_round);
        init(context);
    }

    private void init(Context context) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View layout = inflater.inflate(R.layout.head_view_layout, this, false);
        this.addView(layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        initView(layout);
        restore();
        this.setPadding(0, 20, 0, 30);
    }

    private void initView(View view){
        ivHeaderDownArrow = (ImageView)view.findViewById(R.id.iv_header_down_arrow);
        ivHeaderLoading = (ImageView)view.findViewById(R.id.iv_header_loading);
        textView = (TextView)view.findViewById(R.id.tv_header_state);
    }

    @Override
    public void onScrollChange(View tail, int scrollOffset, int scrollRatio) {
        if (isMore) {
            if (scrollRatio == 100 && !isReach) {
                textView.setText("松开加载");
                ivHeaderDownArrow.setRotation(0);
                isReach = true;
            } else if (scrollRatio != 100 && isReach) {
                textView.setText("上拉加载");
                isReach = false;
                ivHeaderDownArrow.setRotation(180);
            }
        }
    }

    @Override
    public void onRefreshTail(View tail) {
        if (isMore) {
            ivHeaderLoading.setVisibility(VISIBLE);
            ivHeaderDownArrow.setVisibility(GONE);
            ivHeaderLoading.setImageDrawable(animationDrawable);
//            AnimationDrawable animationDrawable = (AnimationDrawable) head.getBackground();
            animationDrawable.start();
            textView.setText("正在加载");
        }
    }

    @Override
    public void onRetractTail(View tail) {
//            animationDrawable = (AnimationDrawable) head.getBackground();
        if (isMore) {
            restore();
            animationDrawable.stop();
            isReach = false;
        }
    }

    @Override
    public void onNotMore(View tail) {
        ivHeaderLoading.setVisibility(GONE);
        ivHeaderDownArrow.setVisibility(GONE);
        textView.setText("已经全部加载完毕");
        isMore = false;
    }

    @Override
    public void onHasMore(View tail) {
        ivHeaderLoading.setVisibility(GONE);
        ivHeaderDownArrow.setVisibility(VISIBLE);
        textView.setText("上拉加载");
        isMore = true;
    }

    private void restore() {
        ivHeaderLoading.setVisibility(GONE);
        ivHeaderDownArrow.setVisibility(VISIBLE);
        ivHeaderLoading.setImageResource(R.drawable.loading1);
        ivHeaderDownArrow.setImageResource(R.drawable.icon_down_arrow);
        ivHeaderDownArrow.setRotation(180);
        textView.setText("上拉加载");
    }
}

PullRefreshUtil.java

/**
 * 类说明:
 * Author: gaobaiq
 * Date: 2016/5/9 20:04
 */
public class PullRefreshUtil {

    /**
     * 刷新控件的基本配件 (头部、尾部都用默认的)
     *
     * @param view          刷新控件
     * @param isDownRefresh 是否开启下拉刷新
     * @param isUpRefresh   是否开启上拉加载
     */
    public static void setRefresh(PullRefreshView view, boolean isDownRefresh, boolean isUpRefresh) {

        HeadView headView = null;
        TailView tailView = null;
        if (isDownRefresh) {
            headView = new HeadView(view.getContext());
        }

        if (isUpRefresh) {
            tailView = new TailView(view.getContext());
        }

        setRefresh(view, isDownRefresh, isUpRefresh, headView, tailView, headView, tailView);
    }

    /**
     * 刷新控件的基本配件 (自定义头部、尾部用默认的)
     *
     * @param view              刷新控件
     * @param isDownRefresh     是否开启下拉刷新
     * @param isUpRefresh       是否开启上拉加载
     * @param headView          头部View
     * @param headStateListener 头部监听器
     */
    public static void setRefresh(PullRefreshView view, boolean isDownRefresh, boolean isUpRefresh, View headView, PullRefreshView.OnHeadStateListener headStateListener) {

        TailView tailView = null;
        if (isUpRefresh) {
            tailView = new TailView(view.getContext());
        }
        setRefresh(view, isDownRefresh, isUpRefresh, headView, tailView, headStateListener, tailView);
    }

    /**
     * 刷新控件的基本配件 (自定义尾部 、头部用默认的)
     *
     * @param view              刷新控件
     * @param isDownRefresh     是否开启下拉刷新
     * @param isUpRefresh       是否开启上拉加载
     * @param tailView          尾部View
     * @param tailStateListener 尾部监听器
     */
    public static void setRefresh(PullRefreshView view, boolean isDownRefresh, boolean isUpRefresh, View tailView, PullRefreshView.OnTailStateListener tailStateListener) {

        HeadView headView = null;
        if (isDownRefresh) {
            headView = new HeadView(view.getContext());
        }
        setRefresh(view, isDownRefresh, isUpRefresh, headView, tailView, headView, tailStateListener);
    }

    /**
     * 刷新控件的基本配件 (自定义头部、尾部)
     *
     * @param view              刷新控件
     * @param isDownRefresh     是否开启下拉刷新
     * @param isUpRefresh       是否开启上拉加载
     * @param headView          头部View
     * @param tailView          尾部View
     * @param headStateListener 头部监听器
     * @param tailStateListener 尾部监听器
     */
    public static void setRefresh(PullRefreshView view, boolean isDownRefresh, boolean isUpRefresh, View headView, View tailView, PullRefreshView.OnHeadStateListener headStateListener, PullRefreshView.OnTailStateListener tailStateListener) {

        view.setRefresh(isDownRefresh, isUpRefresh);

        if (isDownRefresh) {
            view.setHead(headView);
            view.setOnHeadStateListener(headStateListener);
        }

        if (isUpRefresh) {
            view.setTail(tailView);
            view.setOnTailStateListener(tailStateListener);
        }
    }
}

progress_round.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/loading1"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading2"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading3"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading4"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading5"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading6"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading7"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading8"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading9"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading10"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading11"
        android:duration="50" />
    <item
        android:drawable="@drawable/loading12"
        android:duration="50" />
</animation-list>

head_view_layout.xml

<?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"
    android:padding="5dp"
    android:gravity="center">

    <ImageView
        android:id="@+id/iv_header_down_arrow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon_down_arrow" />

    <ImageView
        android:id="@+id/iv_header_loading"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:src="@drawable/progress_round"/>

    <TextView
        android:id="@+id/tv_header_state"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingLeft="5dp"
        android:textSize="13sp"
        android:textColor="#979797"
        android:text="Loading" />

</LinearLayout>

调用:

<com.app.gaobaiq.pullrefresh.PullRefreshRecyclerView
                android:id="@+id/list"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

PullRefreshUtil.setRefresh(mList, true, true);
mList.setOnPullDownRefreshListener(new PullRefreshView.OnPullDownRefreshListener() {
      @Override public void onRefresh() {
mList.isMore(true); pageNum = 1; loadData(); } }); 
mList.setOnPullUpRefreshListener( new PullRefreshView.OnPullUpRefreshListener() { @Override public void onRefresh() { pageNum++; loadData(); } }); 

// 加载完成 if (mList!= null) { mList.refreshFinish(); }
<com.app.gaobaiq.pullrefresh.PullRefreshView
            android:id="@+id/refresh_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ScrollView
                android:id="@+id/scrollView"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <LinearLayout android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="vertical">

                    </LinearLayout>

            </ScrollView>

        </com.app.gaobaiq.pullrefresh.PullRefreshView>

PullRefreshUtil.setRefresh(mRefreshView, true, true);
        mRefreshView.setOnPullDownRefreshListener(
                new PullRefreshView.OnPullDownRefreshListener() {
                    @Override public void onRefresh() {
                        pageNum = 1;
                        mRefreshView.isMore(true);
                        loadData();
                    }
                });
        mRefreshView.setOnPullUpRefreshListener(
                new PullRefreshView.OnPullUpRefreshListener() {
                    @Override public void onRefresh() {
                        pageNum++;
                        loadData();
                    }
                });

// 加载完成
if (mRefreshView != null) {
            mRefreshView.refreshFinish();
        }

  

时间: 2024-10-29 19:08:56

Android实现下拉刷新和上拉加载更多的RecyclerView和ScrollView的相关文章

支持下拉刷新和上划加载更多的自定义RecyclerView(仿XListView效果)

首先看效果 下拉刷新:        上划加载        在项目更新的过程中,遇到了一个将XListView换成recyclerView的需求,而且更换完之后大体效果不能变,但是对于下拉刷新这样的效果,谷歌给出的解决方案是把RecyclerView放在一个SwipeRefreshLayout中,但是这样其实是拉下一个小圆形控件实现的,和XListView的header效果不同.在网上找了很多的别人代码,都没有实现我想要的效果,于是自己动手写了一个. 具体实现的效果有以下几条 下拉刷新功能:

Android下拉刷新库,利用viewdraghelper实现,集成了下拉刷新,底部加载更多,数据初始加载显示loading等功能

项目Github地址:https://github.com/sddyljsx/pulltorefresh Android下拉刷新库,利用viewdraghelper实现. 集成了下拉刷新,底部加载更多,以及刚进入加载数据的loadview.包括了listview与gridview的改写. 效果1: 效果2: 效果3: 效果4: 效果5: 使用说明: imageList=(ListView)findViewById(R.id.image_list); imageAdapter=new ImageA

IOS学习之UiTableView下拉刷新与自动加载更多,百年不变的效果

IOS学习之UiTableView下拉刷新与自动加载更多,百年不变的效果(五) 五一劳动节马上来临,小伙伴有妹有很激动哟,首先祝天下所有的程序猿节日快乐!这个五一对于我来说有点不一样,我的人生从这个五一就转弯了,爱情长跑8年的我结婚了,一会支付宝账号我会公布出去,请自觉打款!谢谢合作. 灯光闪起来: 舞蹈跳起来: 歌曲唱起来: -------------------------------------------------------------------------------------

Android ListView 下拉刷新 点击加载更多

最近项目中用到了ListView的下拉刷新的功能,总结了一下前辈们的代码,单独抽取出来写了一个demo作为示例. 效果图 下拉刷新: 加载更多: CustomListView.java [java] view plaincopy package com.example.uitest.view; import java.util.Date; import com.example.uitest.R; import android.content.Context; import android.uti

十分钟实现ListView下拉刷新上滑加载更多

说到ListView下拉刷新几乎每个APP都会用到,所以ListView下拉刷新是很重要的,就像ListView优化一样是你必会的东西. ListView实现下拉刷新如果我们开发人员自己编写相对来说比较费事的,当我们使用第三方库之后我们再来开发这个功能就会省事很多.相比与自己实现可以少编写不少代码,Android-PullToRefresh库可以轻松实现ListView的下拉刷新功能. 要使用Android—PullToRefesh库对ListView实现下拉刷新要经过以下几个步骤: 1.下载A

最新Android ListView 下拉刷新 上滑加载

开发项目过程中基本都会用到listView的下拉刷新和上滑加载更多,之前大家最常用的应该是pull to refresh或它的变种版吧,google官方在最新的android.support.v4包中增加了一个新类SwipeRefreshLayout,地址 这个类的作用就是提供官方的下拉刷新,并且效果相当不错,而上拉加载更多则用我们自定义的listview,也是相当简单. 下拉刷新 简单的介绍下: 首先它是一个viewgroup,但是它只允许有一个子控件,子控件能是任何view,使用的时候,所在

Android 自定义 ListView 上下拉动&ldquo;刷新最新&rdquo;和&ldquo;加载更多&rdquo;歌曲列表

本文内容 环境 测试数据 项目结构 演示 参考资料 本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表.所谓"刷新最新"和"加载更多"是指日期.演示代码太多,点击此处下载,自己调试一下. 下载 Demo 环境 Windows 2008 R2 64 位 Eclipse ADT V22.6.2,Android 4.4.3 SAMSUNG GT-I9008L,Android OS 2.2.2 测试数据 本演示的歌曲信息,共有 20 条,包括歌手名.歌曲名.时长.缩

Android 使用PullToRefresh实现下拉刷新和上拉加载(ExpandableListView)

PullToRefresh是一套实现非常好的下拉刷新库,它支持: 1.ListView 2.ExpandableListView 3.GridView 4.WebView 等多种常用的需要刷新的View类型,而且使用起来也十分方便. (下载地址:https://github.com/chrisbanes/Android-PullToRefresh) 下载完成,将它导入到eclipse中,作为一个library导入到你的工程中就好了. 一.废话少说,下拉刷新Go. 1.在你的布局文件中加上你想用的

Android 5.X新特性之为RecyclerView添加下拉刷新和上拉加载及SwipeRefreshLayout实现原理

RecyclerView已经写过两篇文章了,分别是Android 5.X新特性之RecyclerView基本解析及无限复用 和 Android 5.X新特性之为RecyclerView添加HeaderView和FooterView,既然来到这里还没学习的,先去学习下吧. 今天我们的主题是学习为RecyclerView添加下拉刷新和上拉加载功能. 首先,我们先来学习下拉刷新,google公司已经为我们提供的一个很好的包装类,那就是SwipeRefreshLayout,这个类可以支持我们向下滑动并进