自定义下拉刷新

效果如下:


代码如下:

/*
 * Copyright (C) 2016 The AndroidSupport Project
 */

package com.hyena.framework.app.widget;

import android.content.Context;
import android.os.Build;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.AbsListView;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

import com.hyena.framework.R;
import com.hyena.framework.clientlog.LogUtil;
import com.hyena.framework.debug.InvokeHelper;
import com.hyena.framework.utils.AnimationUtils;
import com.hyena.framework.utils.UIUtils;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ValueAnimator;

/**
 * Created by yangzc on 16/9/14.
 */

public class RefreshableLayout extends RelativeLayout {

    private static final int MODE_PULL_FROM_NONE = 0;
    private static final int MODE_PULL_FROM_START = 1;
    private static final int MODE_PULL_FROM_END = 2;

    private static final int MAX_MOVE_DISTANCE = UIUtils.dip2px(120);

    private View mTarget = null;
    private View mScrollerView = null;

    private int mTouchSlop = 0;
    private float mInitialDownY = -1;
    private float mInitialMotionY = -1;
    private boolean mIsBeingDragged;
    private int mCurrentMode = MODE_PULL_FROM_NONE;

    private boolean mRefreshing, mLoadingMore;

    private boolean mEnableRefresh = true, mEnableLoadMore = true;
    private AbsRefreshablePanel mHeaderPanel = null;
    private AbsRefreshablePanel mFooterPanel = null;

    public RefreshableLayout(Context context) {
        super(context);
        init();
    }

    public RefreshableLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        relayout();
    }

    public void setEnableRefresh(boolean enableRefresh) {
        this.mEnableRefresh = enableRefresh;
    }

    public void setEnableLoadMore(boolean enableLoadMore) {
        this.mEnableLoadMore = enableLoadMore;
    }

    private void relayout() {
        relayoutHeader();
        relayoutFooter();
    }

    private void relayoutHeader() {
        int paddingTop = 0;
        if (mHeaderPanel != null) {
            LayoutParams headerParams = new LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, mHeaderPanel.getContentHeight());
            headerParams.addRule(ALIGN_PARENT_TOP);
            mHeaderPanel.setId(R.id.refresh_header);
            addView(mHeaderPanel, headerParams);
            paddingTop = -mHeaderPanel.getContentHeight();
        }
        setPadding(0, paddingTop, 0, getPaddingBottom());
    }

    private void relayoutFooter() {
        int paddingBottom = 0;
        if (mFooterPanel != null) {
            LayoutParams footerParams = new LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, mFooterPanel.getContentHeight());
            footerParams.addRule(ALIGN_PARENT_BOTTOM);
            mFooterPanel.setId(R.id.refresh_footer);
            addView(mFooterPanel, footerParams);
            paddingBottom = -mFooterPanel.getContentHeight();
        }
        setPadding(0, getPaddingTop(), 0, paddingBottom);
    }

    public void setHeaderPanel(AbsRefreshablePanel headerPanel) {
        if (mHeaderPanel != null) {
            removeView(mHeaderPanel);
        }
        this.mHeaderPanel = headerPanel;
        relayoutHeader();
    }

    public void setFooterPanel(AbsRefreshablePanel footerPanel) {
        if (mFooterPanel != null) {
            removeView(mFooterPanel);
        }
        this.mFooterPanel = footerPanel;
        relayoutFooter();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (mTarget == null)
            insureTarget();

        if (mTarget != null) {
            LayoutParams params = (LayoutParams) mTarget.getLayoutParams();
            params.addRule(RelativeLayout.BELOW, R.id.refresh_header);
            params.addRule(RelativeLayout.ABOVE, R.id.refresh_footer);
        }
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        super.addView(child, params);
        if (mTarget == null)
            insureTarget();

        if (mTarget != null) {
            LayoutParams targetParams = (LayoutParams) mTarget.getLayoutParams();
            targetParams.addRule(RelativeLayout.BELOW, R.id.refresh_header);
            targetParams.addRule(RelativeLayout.ABOVE, R.id.refresh_footer);
        }
    }

    private void insureTarget() {
        if (getChildCount() > 0) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!(child instanceof AbsRefreshablePanel)) {
                    mTarget = child;
                    break;
                }
            }
        }

        insureScrollView();
    }

    private void insureScrollView() {
        if (mScrollerView == null) {
            if (mTarget != null && mTarget instanceof SwipeRefreshLayout) {
                mScrollerView = (View) InvokeHelper.getFieldValue(mTarget, "mTarget");
            } else {
                mScrollerView = mTarget;
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        insureScrollView();
    }

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mRefreshing || mLoadingMore || (mHeaderPanel == null && mFooterPanel == null)
                || (!mEnableRefresh && !mEnableLoadMore)) {
            return false;
        }

        if (mTarget == null) {
            insureTarget();
        }
        insureScrollView();

        if (mTarget != null && mTarget instanceof SwipeRefreshLayout) {
            if (((SwipeRefreshLayout) mTarget).isRefreshing())
                return false;
        }

        int action = MotionEventCompat.getActionMasked(ev);
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mIsBeingDragged = false;
                mInitialDownY = ev.getY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                float y = ev.getY();
                float yDiff = y - mInitialDownY;
                if (Math.abs(yDiff) > mTouchSlop && !mIsBeingDragged) {
                    if (mHeaderPanel != null && mEnableRefresh && !canChildScrollUp() && yDiff > 1) {
                        //top
                        mCurrentMode = MODE_PULL_FROM_START;
                        mInitialMotionY = mInitialDownY + mTouchSlop;
                        mIsBeingDragged = true;
                    } else if (mFooterPanel != null && mEnableLoadMore && !canChildScrollDown() && yDiff < -1) {
                        //bottom
                        mCurrentMode = MODE_PULL_FROM_END;
                        mInitialMotionY = mInitialDownY + mTouchSlop;
                        mIsBeingDragged = true;
                    } else {
                        //NO-OP
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                mIsBeingDragged = false;
                break;
            }
        }
        return mIsBeingDragged;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mRefreshing || mLoadingMore || (mHeaderPanel == null && mFooterPanel == null)
                || (!mEnableRefresh && !mEnableLoadMore))
            return false;

        if (mTarget != null && mTarget instanceof SwipeRefreshLayout) {
            if (((SwipeRefreshLayout) mTarget).isRefreshing())
                return false;
        }

        int action = MotionEventCompat.getActionMasked(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mIsBeingDragged = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                float y = event.getY();
                float overScroll = (mInitialMotionY - y) * .5f;
                if (mIsBeingDragged) {
                    if (mCurrentMode == MODE_PULL_FROM_START) {
                        overScroll = Math.max(Math.min(0, overScroll), -MAX_MOVE_DISTANCE);
                    } else if (mCurrentMode == MODE_PULL_FROM_END) {
                        overScroll = Math.min(Math.max(0, overScroll), MAX_MOVE_DISTANCE);
                    } else {
                        overScroll = 0;
                    }
                    moveTarget(overScroll);
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                mIsBeingDragged = false;
                moveRelease();
                break;
            }
        }
        return true;
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
        if ((Build.VERSION.SDK_INT < 21 && mScrollerView instanceof AbsListView)
                || (mScrollerView != null && !ViewCompat.isNestedScrollingEnabled(mScrollerView))) {
            // Nope.
        } else {
            super.requestDisallowInterceptTouchEvent(b);
        }
    }

    public void setRefreshing(boolean isRefreshing) {
        if (mHeaderPanel != null) {
            this.mRefreshing = isRefreshing;
            mCurrentMode = MODE_PULL_FROM_START;
            if (isRefreshing) {
                scroll(0, -mHeaderPanel.getContentHeight());
                mHeaderPanel.setStatus(AbsRefreshablePanel.STATUS_REFRESH);
            } else {
                scroll(getScrollY(), 0);
                mHeaderPanel.setStatus(AbsRefreshablePanel.STATUS_RESET);
            }
        }
    }

    public void setLoadingMore(boolean isLoading) {
        if (mFooterPanel != null) {
            this.mLoadingMore = isLoading;
            mCurrentMode = MODE_PULL_FROM_END;
            if (isLoading) {
                scroll(0, mFooterPanel.getContentHeight());
                mFooterPanel.setStatus(AbsRefreshablePanel.STATUS_REFRESH);
            } else {
                scroll(getScrollY(), 0);
                mFooterPanel.setStatus(AbsRefreshablePanel.STATUS_RESET);
            }
        }
    }

    /**
     * 手势释放
     */
    private void moveRelease() {
        AbsRefreshablePanel pullItem = null;
        int toScrollY = 0;
        if (mCurrentMode == MODE_PULL_FROM_START) {
            pullItem = mHeaderPanel;
            toScrollY = -pullItem.getContentHeight();
        } else if (mCurrentMode == MODE_PULL_FROM_END) {
            pullItem = mFooterPanel;
            toScrollY = pullItem.getContentHeight();
        }

        if (pullItem != null) {
            if (Math.abs(getScrollY()) >= pullItem.getContentHeight()) {
                pullItem.setStatus(AbsRefreshablePanel.STATUS_REFRESH);
                scroll(getScrollY(), toScrollY);
                if (mCurrentMode == MODE_PULL_FROM_START) {
                    mRefreshing = true;
                    if (mRefreshListener != null) {
                        mRefreshListener.onRefresh();
                    }
                } else if (mCurrentMode == MODE_PULL_FROM_END) {
                    mLoadingMore = true;
                    if (mRefreshListener != null) {
                        mRefreshListener.onLoadMore();
                    }
                }
            } else {
                scroll(getScrollY(), 0);
            }
        }
    }

    /**
     * 手势拖动
     */
    private void moveTarget(float overScroll) {
        scrollTo(0, (int) overScroll);
        AbsRefreshablePanel pullItem = null;
        if (mCurrentMode == MODE_PULL_FROM_START) {
            pullItem = mHeaderPanel;
        } else if (mCurrentMode == MODE_PULL_FROM_END) {
            pullItem = mFooterPanel;
        }

        if (pullItem != null) {
            pullItem.setScrolling(overScroll, pullItem.getContentHeight());
            if (Math.abs(overScroll) >= pullItem.getContentHeight()) {
                pullItem.setStatus(AbsRefreshablePanel.STATUS_READY_REFRESH);
            } else {
                pullItem.setStatus(AbsRefreshablePanel.STATUS_START_PULL);
            }
        }
    }

    /**
     * 滚动回退
     *
     * @param fromScroll 开始位置
     * @param toScroll   结束位置
     */
    private void scroll(final float fromScroll, final float toScroll) {
        ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
        animator.setDuration(200);
        animator.setInterpolator(new LinearInterpolator());
        AnimationUtils.ValueAnimatorListener listener = new AnimationUtils.ValueAnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                scrollTo(0, (int) fromScroll);
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                scrollTo(0, (int) toScroll);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                scrollTo(0, (int) toScroll);
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Float value = (Float) valueAnimator.getAnimatedValue();
                float newScrollValue = value * (toScroll - fromScroll) + fromScroll;
                scrollTo(0, (int) newScrollValue);
            }
        };
        animator.addUpdateListener(listener);
        animator.addListener(listener);
        animator.start();
    }

    /**
     * 是否可以向上滚动
     */
    private boolean canChildScrollUp() {
        if (Build.VERSION.SDK_INT < 14) {
            if (mScrollerView instanceof AbsListView) {
                AbsListView listView = (AbsListView) mScrollerView;
                return listView.getChildCount() > 0
                        && (listView.getFirstVisiblePosition() > 0
                        || listView.getChildAt(0).getTop() < listView.getPaddingTop());
            } else {
                return mScrollerView.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mScrollerView, -1);
        }
    }

    /**
     * 是否可以向下滚动
     */
    private boolean canChildScrollDown() {
        if (!canChildScrollUp()) {//less data
            return true;
        }
        if (mScrollerView instanceof RecyclerView) {
            RecyclerView recyclerView = (RecyclerView) mScrollerView;
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            int count = recyclerView.getAdapter().getItemCount();
            if (layoutManager instanceof LinearLayoutManager && count > 0) {
                LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == count - 1) {
                    return false;
                }
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                int[] lastItems = new int[4];
                staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(lastItems);
                int lastItem = max(lastItems);
                if (lastItem == count - 1) {
                    return false;
                }
            }
            return true;
        } else if (mScrollerView instanceof AbsListView) {
            final AbsListView absListView = (AbsListView) mScrollerView;
            int count = absListView.getAdapter().getCount();
            int firstVisiblePosition = absListView.getFirstVisiblePosition();
            if (firstVisiblePosition == 0
                    && absListView.getChildAt(0).getTop() >= absListView
                    .getPaddingTop()) {
                return false;
            }
            int lastPos = absListView.getLastVisiblePosition();
            return lastPos > 0 && count > 0 && lastPos == count - 1;
        } else if (mScrollerView instanceof ScrollView) {
            ScrollView scrollView = (ScrollView) mScrollerView;
            View view = scrollView.getChildAt(scrollView.getChildCount() - 1);
            if (view != null) {
                int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));
                if (diff == 0) {
                    return false;
                }
            }
        } else {
            return ViewCompat.canScrollVertically(mScrollerView, 1);
        }
        return true;
    }

    private int max(int[] a) {
        // 返回数组最大值
        int x;
        int aa[] = new int[a.length];
        System.arraycopy(a, 0, aa, 0, a.length);
        x = aa[0];
        for (int i = 1; i < aa.length; i++) {
            if (aa[i] > x) {
                x = aa[i];
            }
        }
        return x;
    }

    private OnRefreshListener mRefreshListener;

    public void setRefreshListener(OnRefreshListener listener) {
        this.mRefreshListener = listener;
    }

    public interface OnRefreshListener {
        void onRefresh();

        void onLoadMore();
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值