1. 程式人生 > >下拉重新整理,上拉載入更多的SwipeRefreshLayout(可自定義動畫)

下拉重新整理,上拉載入更多的SwipeRefreshLayout(可自定義動畫)

為啥重複搞,搞得還沒人家好,因為除了需求,還有理解。我是這麼認為的。

因為懶所以寫出來留作自用,以後就是修修改改了。

打造自己的“下拉重新整理,上拉載入更多,自定義動畫及佈局”控制元件

(拷貝SwipeRefreshLayout原始碼進行修改)

不鬥圖的碼農你見過嗎?

醜醜爆了,原諒我的審美和能力。。。

新寫了一個自定義載入動畫

設計思路:

也就是一個大的佈局套著一個可滾動的子viewGroup

內外兩個都可以滑動的View,那麼主要問題就是解決滑動衝突了。

當然要重現父類View的攔截器部分:

onInterceptTouchEvent事件:

如果(子view可以向上滾動&&也可以向下滾動){
    //TODO直接放行,觸控事件交給子view處理
}否則{
    //TODO呼叫父類觸控事件OntouchEvent(由onTouchEvnent決定是否攔截)
}

TouchEvent事件:

能觸發onTouchenvet事件有四種情況:
1.已經滑到頂部了,還要繼續下滑動(觸發頂部重新整理)
2.已經滑到頂部了,這時候上劃則滾動子view
3.已經滑到底部了,還要繼續滑動(觸發底部重新整理)
4.已經滑到底部了,這時候下滑則滾動子view

由上四種情況分別處理:
1,3情況 觸發父view的重新整理載入動畫
2,4情況 則不消費onTouch事件,並把結果返還給攔截器onInterceptTouchEvent處理把事件交給子view

上程式碼:

package com.huan.squirrel.pushcardlayout.pushcardlayout;


import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ListViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;


/**
 * @author:Squirrel桓
 * @time:2018/8/28
 */
public class PushCardLayout extends ViewGroup implements NestedScrollingParent,
        NestedScrollingChild {

    private int mTouchSlop;
    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
    private final NestedScrollingChildHelper mNestedScrollingChildHelper;

    //滾動內容區
    private View contentLayout;
    //底部佈局,頂部佈局
    private LinearLayout bottomLayout, topLayout;
    private View topLayoutView;
    private View bottomLayoutView;

    //資料載入監聽
    private PushCardDatalistener dataListener;
    //動畫載入監聽
    private PushCardAnimationListener animationListener;

    public void setDataListener(PushCardDatalistener dataListener) {
        this.dataListener = dataListener;
    }

    public void setAnimationListener(PushCardAnimationListener animationListener) {
        this.animationListener = animationListener;
    }

    public View getTopLayoutView() {
        return topLayoutView;
    }

    /**
     * 設定頂部view
     *
     * @param topLayoutView
     */
    public void setTopLayoutView(View topLayoutView) {
        topLayout.removeAllViews();
        topLayout.addView(topLayoutView);
        this.topLayoutView = topLayoutView;
    }

    public View getBottomLayoutView() {
        return bottomLayoutView;
    }

    /**
     * 設定底部view
     *
     * @param bottomLayoutView
     */
    public void setBottomLayoutView(View bottomLayoutView) {
        bottomLayout.removeAllViews();
        bottomLayout.addView(bottomLayoutView);
        this.bottomLayoutView = bottomLayoutView;
    }

    public PushCardLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
        setNestedScrollingEnabled(true);
        initView(context, attrs);
    }


    private int mMediumAnimationDuration;//動畫時長
    private final DecelerateInterpolator mDecelerateInterpolator;
    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
    private static final int[] LAYOUT_ATTRS = new int[]{
            android.R.attr.enabled
    };

    private void initView(Context context, AttributeSet attrs) {
        final DisplayMetrics metrics = getResources().getDisplayMetrics();//解析度
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//距離,表示滑動的時候,手的移動要大於這個距離才開始移動控制元件。

        mMediumAnimationDuration = getResources().getInteger(
                android.R.integer.config_mediumAnimTime);//動畫時長

        setWillNotDraw(false);//ViewGroup預設情況下,出於效能考慮,會被設定成WILL_NOT_DROW,這樣,ondraw就不會被執行了。
        //呼叫setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。就可以重寫ondraw()


        creatTopLayout(context);
        creatBottomLayout(context);
        /****** (START)  測試新增 可刪除   ***********/
        /*ImageView imageView = new ImageView(getContext());
        imageView.setImageResource(R.mipmap.ic_launcher_round);
        setTopLayoutView(imageView);
        ImageView imageView2 = new ImageView(getContext());
        imageView2.setImageResource(R.mipmap.ic_launcher_round);
        setBottomLayoutView(imageView2);*/
        /******  (END) 測試新增 可刪除   ***********/

        setChildrenDrawingOrderEnabled(true);//設定子view按照順序繪製
        mSpinnerOffsetEnd = bottomLayoutHeight / 2;
        mTotalDragDistance = mSpinnerOffsetEnd;

        final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
        setEnabled(a.getBoolean(0, true));
        a.recycle();
    }

    /**
     * 初始化頂部佈局
     * TODO 暫時用底部佈局高度作為預設高度,動態頂部,底部高度 ,用到時候在處理吧
     *
     * @param context
     */
    private int bottomLayoutHeight = (int)(100*getResources().getDisplayMetrics().density);

    public int getBottomLayoutHeight() {
        return bottomLayoutHeight;
    }

    private void creatBottomLayout(Context context) {
        bottomLayout = new LinearLayout(context);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, bottomLayoutHeight);
        layoutParams.gravity = Gravity.CENTER;
        bottomLayout.setLayoutParams(layoutParams);
        bottomLayout.setGravity(Gravity.CENTER);
        //bottomLayout.setGravity(Gravity.BOTTOM);
        addView(bottomLayout);
    }

    /**
     * 初始化底部佈局
     *
     * @param context
     */
    private int topLayoutHeight = (int)(100*getResources().getDisplayMetrics().density);

    private void creatTopLayout(Context context) {
        topLayout = new LinearLayout(context);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, topLayoutHeight);
        layoutParams.gravity = Gravity.CENTER;
        topLayout.setLayoutParams(layoutParams);
        topLayout.setGravity(Gravity.CENTER);
        addView(topLayout);
    }

    private int topLayoutOffsetTop = 0;//header距離頂部的距離
    private int bottomLayoutOffsetTop = 0;//footer距離頂部的距離

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0) {
            return;
        }
        if (contentLayout == null) {
            ensureTarget();
        }
        if (contentLayout == null) {
            return;
        }
        final View child = contentLayout;
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        final int childWidth = width - getPaddingLeft() - getPaddingRight();
        final int childHeight = height - getPaddingTop() - getPaddingBottom();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);

        final DisplayMetrics metrics = getResources().getDisplayMetrics();//解析度
        int topLayoutWidth = topLayout.getMeasuredWidth();
        topLayoutHeight = topLayout.getMeasuredHeight();
        topLayoutOffsetTop = -topLayoutHeight;
        topLayout.layout((width / 2 - topLayoutWidth / 2), -topLayoutHeight,
                (width / 2 + topLayoutWidth / 2), 0);

        int bottomLayoutWidth = bottomLayout.getMeasuredWidth();
        bottomLayoutHeight = bottomLayout.getMeasuredHeight();
        bottomLayoutOffsetTop = height;
        bottomLayout.layout((width / 2 - bottomLayoutWidth / 2), height,
                (width / 2 + bottomLayoutWidth / 2), height + bottomLayoutHeight);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (contentLayout == null) {
            ensureTarget();
        }
        if (contentLayout == null) {
            return;
        }
        contentLayout.measure(MeasureSpec.makeMeasureSpec(
                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
        topLayout.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec((int) topLayoutHeight, MeasureSpec.EXACTLY));

        bottomLayout.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec((int) bottomLayoutHeight, MeasureSpec.EXACTLY));
    }


    private void ensureTarget() {
        if (contentLayout == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(topLayout) && !child.equals(bottomLayout)) {
                    contentLayout = child;
                    break;
                }
            }
        }
    }


    @Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
        // if this is a List < L or another view that doesn't support nested
        // scrolling, ignore this request so that the vertical scroll event
        // isn't stolen
        if ((android.os.Build.VERSION.SDK_INT < 21 && contentLayout instanceof AbsListView)
                || (contentLayout != null && !ViewCompat.isNestedScrollingEnabled(contentLayout))) {
            // Nope.
        } else {
            super.requestDisallowInterceptTouchEvent(b);
        }
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ensureTarget();
        boolean handled = false;
        if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) {
            Log.i("CGQ", "不攔截子view滾動");
            mReturningToStart = false;
            if (isUped || isDowned) {
                finishSpinner(0);
            }
        }
        if (isEnabled() && !mReturningToStart && (!canChildScrollUp() || !canChildScrollDown())
                && !mRefreshing && !mNestedScrollInProgress) {
            handled = onTouchEvent(ev);
            Log.i("CGQ", "事件分發");
        }

        //Log.i("CGQ", "handled=" + handled);
        //父控制元件消費,子空間就不執行了。父控制元件不消費,再交給子空間處理
        return !handled ? super.onInterceptTouchEvent(ev) : handled;
    }


    private float mDownY;
    private boolean mIsBeingDraggedTop;//頂部拖拽狀態

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (!canRefresh) {
            return false;
        }

        int action = event.getAction();
        boolean handled = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 移動的起點
                mDownY = event.getY();
                mInitialDownY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(event.getY() - mDownY) < mTouchSlop) {
                    Log.i("CGQ", "未達到滾動最小值");
                    return false;
                }
                float offset_c = (event.getY() - mDownY);//當前滑動間距
                if (offset_c > 0) {//下滑手勢
                    if (isDowned) {//已經是下滑到底狀態
                        finishSpinner(0);
                    } else {//不是下滑到底狀態
                        if (canChildScrollUp()) {
                            Log.i("CGQ", "下滑");
                        } else {
                            Log.i("CGQ", "下滑並觸發");
                            handled = true;
                            float y = event.getY();
                            startDragging(y);
                            final float overscrollTop = offset_c * DRAG_RATE;
                            //下滑並觸發
                            moveSpinner(overscrollTop);
                        }
                    }
                } else {//上拉手勢
                    if (isUped) {//已經是上拉到頂狀態
                        finishSpinner(0);
                    } else {
                        if (canChildScrollDown()) {
                            Log.i("CGQ", "上滑");
                        } else {
                            Log.i("CGQ", "上滑並觸發");
                            handled = true;
                            float y = event.getY();
                            startDragging(y);
                            final float overscrollTop = offset_c * DRAG_RATE;
                            //下拉顯示頭部
                            moveSpinner(overscrollTop);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                finishSpinner(0);
                break;
            case MotionEvent.ACTION_CANCEL:
                finishSpinner(0);
                break;
        }
        return handled;
    }

    /**
     * @param offset 偏移量
     */
    private void scrollTopLayout(int offset) {
        if (contentLayout != null) {
            ViewCompat.offsetTopAndBottom(contentLayout, offset);//正數向下移動,負數向上移動
            //topLayout.bringToFront();
            if (isUping) {
                ViewCompat.offsetTopAndBottom(topLayout, offset);
            } else {
                ViewCompat.offsetTopAndBottom(bottomLayout, offset);
            }
            mContenViewOffsetTop = contentLayout.getTop();
        }

    }

    private float mInitialMotionY;
    private static final float DRAG_RATE = .5f;
    private float mInitialDownY;

    private void startDragging(float y) {
        final float yDiff = y - mInitialDownY;
        if (yDiff > mTouchSlop && !mIsBeingDraggedTop) {
            mInitialMotionY = mInitialDownY + mTouchSlop;
            mIsBeingDraggedTop = true;
        }
    }

    private float mTotalDragDistance = -1;
    int mSpinnerOffsetEnd;
    protected int mOriginalOffsetTop;
    int mContenViewOffsetTop;

    /**
     * @param overscrollTop
     * @param
     */
    private void moveSpinner(float overscrollTop) {
        boolean isTop = false;
        int targetY2 = 0;
        if (overscrollTop < 0) {
            isTop = true;
            overscrollTop = -overscrollTop;
            //更改狀態
            isDowning = true;
        } else {
            //更改狀態
            isUping = true;
        }
        Log.i("CGQ", "moveSpinner = " + overscrollTop);
        float originalDragPercent = overscrollTop / mTotalDragDistance;

        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
        float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
        float slingshotDist = mUsingCustomStart ? mSpinnerOffsetEnd - mOriginalOffsetTop
                : mSpinnerOffsetEnd;
        float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
                / slingshotDist);
        float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
                (tensionSlingshotPercent / 4), 2)) * 2f;
        float extraMove = (slingshotDist) * tensionPercent * 2;
        targetY2 = (int) ((slingshotDist * dragPercent) + extraMove);

        int h = !isTop ? (targetY2 - mContenViewOffsetTop) : (-targetY2 - mContenViewOffsetTop);

        if (animationListener != null) {
            animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, (float) Math.abs(contentLayout.getTop()) / bottomLayoutHeight);
        }
        Log.i("CGQ", "slingshotDist=" + slingshotDist + ",h=" + h);
        scrollTopLayout(h);
    }

    private boolean isUping = false;//正在上拉重新整理
    private boolean isDowning = false;//正在下拉
    private boolean isUped = false;//正在上拉到指定位置(預設拉到三分之二就觸發載入)
    private boolean isDowned = false;//正在下拉到指定位置(預設拉到三分之二就觸發載入)

    /**
     * 恢復
     */
    public void setCancel() {
        finishSpinner(0);
    }

    //是否可以上拉下拉滑動
    private boolean canRefresh = true;

    /**
     * 是否可以上拉下拉滑動
     *
     * @param canRefresh
     */
    public void setCanRefresh(boolean canRefresh) {
        this.canRefresh = canRefresh;
    }

    /**
     * 恢復動畫
     */
    private void finishSpinner(float overscrollTop) {
        if (contentLayout == null) {
            return;
        }
        //處理是否觸發重新整理或者載入更多
        if (Math.abs(contentLayout.getTop()) > bottomLayoutHeight / 3 * 2 && (!isUped && !isDowned)) {//拉到2/3以上則觸發
            //填充動畫(自動下拉到最大高度)
            float startValue = Math.abs(contentLayout.getTop())/(float)bottomLayoutHeight;
            ValueAnimator animator = ValueAnimator.ofFloat(startValue, 1);

            animator.setDuration((int)(200*(1-startValue)));
            animator.setInterpolator(mDecelerateInterpolator);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float scale = (float) animation.getAnimatedValue();
                    if(contentLayout.getTop()>0&& (topLayout.getTop() != topLayoutOffsetTop )){
                        ViewCompat.offsetTopAndBottom(contentLayout, (int) (( bottomLayoutHeight - contentLayout.getTop()) * scale));//正數向下移動,負數向上移動
                        ViewCompat.offsetTopAndBottom(topLayout, (int) ( (bottomLayoutHeight + (topLayoutOffsetTop - topLayout.getTop())) * scale));
                    }else if( (topLayout.getTop() != topLayoutOffsetTop )){
                        ViewCompat.offsetTopAndBottom(contentLayout, (int) ((- bottomLayoutHeight - contentLayout.getTop()) * scale));//正數向下移動,負數向上移動
                        ViewCompat.offsetTopAndBottom(bottomLayout, (int) ((-bottomLayoutHeight + (bottomLayoutOffsetTop - bottomLayout.getTop())) * scale));
                    }
                    mContenViewOffsetTop = contentLayout.getTop();
                    if (scale == 0) {//動畫開始
                        if (animationListener != null) {
                            animationListener.onStart(isUping ? topLayoutView : bottomLayoutView);
                        }
                    } else if (scale == 1) {//動畫結束
                        if (animationListener != null) {
                            animationListener.onEnd(isUping ? topLayoutView : bottomLayoutView);
                        }
                        if (isUping) {//上拉處理
                            isUping = false;
                            isUped = true;
                            if (dataListener != null) {
                                dataListener.onRefreshData();
                            }
                        }
                        if (isDowning) {//下拉處理
                            isDowning = false;
                            isDowned = true;
                            if (dataListener != null) {
                                dataListener.onLoadMoreData();
                            }
                        }
                    } else {//動畫進行中
                        if (animationListener != null) {
                            animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, scale);
                        }
                    }
                }
            });
            animator.start();
        } else {
            //回滾動畫
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.setDuration(200);
            animator.setInterpolator(mDecelerateInterpolator);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float scale = (float) animation.getAnimatedValue();
                    ViewCompat.offsetTopAndBottom(contentLayout, (int) (-contentLayout.getTop() * scale));//正數向下移動,負數向上移動
                    if (topLayout.getTop() != topLayoutOffsetTop) {
                        ViewCompat.offsetTopAndBottom(topLayout, (int) ((topLayoutOffsetTop - topLayout.getTop()) * scale));
                    }
                    if (bottomLayout.getTop() != bottomLayoutOffsetTop) {
                        ViewCompat.offsetTopAndBottom(bottomLayout, (int) ((bottomLayoutOffsetTop - bottomLayout.getTop()) * scale));
                    }
                    mContenViewOffsetTop = contentLayout.getTop();
                    if (scale == 0) {//動畫開始
                        if (animationListener != null) {
                            animationListener.onStart(isUping ? topLayoutView : bottomLayoutView);
                        }
                    } else if (scale == 1) {//動畫結束
                        isUped = false;
                        isDowned = false;
                        isUping = false;
                        isDowning = false;
                        if (animationListener != null) {
                            animationListener.onEnd(isUping ? topLayoutView : bottomLayoutView);
                        }
                    } else {//動畫進行中
                        if (animationListener != null) {
                            animationListener.onRuning(isUping ? topLayoutView : bottomLayoutView, isUping, scale);
                        }
                    }
                }
            });
            animator.start();
        }
    }

    // NestedScrollingParent
    private float mTotalUnconsumed;
    private boolean mNestedScrollInProgress;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];
    // Whether the client has set a custom starting position;
    boolean mUsingCustomStart;
    private boolean mReturningToStart;
    boolean mRefreshing = false;
    private OnChildScrollUpCallback mChildScrollUpCallback;

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return isEnabled() && !mReturningToStart && !mRefreshing
                && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        // Reset the counter of how much leftover scroll needs to be consumed.
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
        // Dispatch up to the nested parent
        startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
        mTotalUnconsumed = 0;
        mNestedScrollInProgress = true;
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        // If we are in the middle of consuming, a scroll, then we want to move the spinner back up
        // before allowing the list to scroll
        if (dy > 0 && mTotalUnconsumed > 0) {
            if (dy > mTotalUnconsumed) {
                consumed[1] = dy - (int) mTotalUnconsumed;
                mTotalUnconsumed = 0;
            } else {
                mTotalUnconsumed -= dy;
                consumed[1] = dy;
            }
            moveSpinner(mTotalUnconsumed);
        }

        // If a client layout is using a custom start position for the circle
        // view, they mean to hide it again before scrolling the child view
        // If we get back to mTotalUnconsumed == 0 and there is more to go, hide
        // the circle so it isn't exposed if its blocking content is moved
        if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
                && Math.abs(dy - consumed[1]) > 0) {
            //mCircleView.setVisibility(View.GONE);
        }

        // Now let our nested parent consume the leftovers
        final int[] parentConsumed = mParentScrollConsumed;
        if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
            consumed[0] += parentConsumed[0];
            consumed[1] += parentConsumed[1];
        }
    }


    @Override
    public int getNestedScrollAxes() {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }

    @Override
    public void onStopNestedScroll(View target) {
        mNestedScrollingParentHelper.onStopNestedScroll(target);
        mNestedScrollInProgress = false;
        // Finish the spinner for nested scrolling if we ever consumed any
        // unconsumed nested scroll
        if (mTotalUnconsumed > 0) {
            finishSpinner(mTotalUnconsumed);
            mTotalUnconsumed = 0;
        }
        // Dispatch up our nested parent
        stopNestedScroll();
    }


    @Override
    public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
                               final int dxUnconsumed, final int dyUnconsumed) {
        // Dispatch up to the nested parent first
        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                mParentOffsetInWindow);

        // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
        // sometimes between two nested scrolling views, we need a way to be able to know when any
        // nested scrolling parent has stopped handling events. We do that by using the
        // 'offset in window 'functionality to see if we have been moved from the event.
        // This is a decent indication of whether we should take over the event stream or not.
        final int dy = dyUnconsumed + mParentOffsetInWindow[1];
        if (dy < 0 && !canChildScrollUp()) {
            mTotalUnconsumed += Math.abs(dy);
            moveSpinner(mTotalUnconsumed);
        }
    }

    // NestedScrollingChild

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return mNestedScrollingChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return mNestedScrollingChildHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        mNestedScrollingChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return mNestedScrollingChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
        return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,
                                           int[] offsetInWindow) {
        return mNestedScrollingChildHelper.dispatchNestedPreScroll(
                dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX,
                                    float velocityY) {
        return dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY,
                                 boolean consumed) {
        return dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    //判斷是否可以下拉
    public boolean canChildScrollUp() {
        if (mChildScrollUpCallback != null) {
            return mChildScrollUpCallback.canChildScrollUp(this, contentLayout);
        }
        if (contentLayout instanceof ListView) {
            return ListViewCompat.canScrollList((ListView) contentLayout, -1);
        }
        return contentLayout.canScrollVertically(-1);
    }

    //判斷是否可以上拉
    public boolean canChildScrollDown() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (contentLayout instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) contentLayout;
                // AppLog.e(absListView.getFirstVisiblePosition()+"  :   "+absListView.getChildAt(absListView.getChildCount()-1).getBottom()+"  :   "+absListView.getPaddingBottom());
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return contentLayout.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(contentLayout, 1);
        }
    }

    public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) {
        mChildScrollUpCallback = callback;
    }

    /**
     * Classes that wish to override {@link PushCardLayout#canChildScrollUp()} method
     * behavior should implement this interface.
     */
    public interface OnChildScrollUpCallback {
        /**
         * Callback that will be called when {@link PushCardLayout#canChildScrollUp()} method
         * is called to allow the implementer to override its behavior.
         *
         * @param parent SwipeRefreshLayout that this callback is overriding.
         * @param child  The child view of SwipeRefreshLayout.
         * @return Whether it is possible for the child view of parent layout to scroll up.
         */
        boolean canChildScrollUp(@NonNull PushCardLayout parent, @Nullable View child);

        /**
         * Callback that will be called when {@link PushCardLayout#canChildScrollDown()} method
         * is called to allow the implementer to override its behavior.
         *
         * @param parent SwipeRefreshLayout that this callback is overriding.
         * @param child  The child view of SwipeRefreshLayout.
         * @return Whether it is possible for the child view of parent layout to scroll down.
         */
        boolean canChildScrollDown(@NonNull PushCardLayout parent, @Nullable View child);
    }

    /**
     * 資料監聽器
     */
    public interface PushCardDatalistener {
        /**
         * 上拉載入更多
         */
        void onLoadMoreData();

        /**
         * 下拉重新整理
         */
        void onRefreshData();
    }

    /**
     * 動畫監聽器
     */
    public interface PushCardAnimationListener {
        /**
         * 開始初始化操作
         */
        void onStart(View targetView);

        /**
         * 0-1屬性動畫,下拉百分比動畫
         *
         * @param targetView 目標view(頭部或者底部)
         * @param isUpper    //判斷頭部動畫還是底部動畫
         * @param value      動畫百分比
         */
        void onRuning(View targetView, boolean isUpper, float value);

        /**
         * 動畫結束
         */
        void onEnd(View targetView);
    }

}

一個類完事了。要啥動畫自己寫去吧!

使用部分:

1.layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.CustomerActivity">

    <com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout
        android:id="@+id/pcl_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never">
        <include layout="@layout/layout_main"/>
    </com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout>
</android.support.constraint.ConstraintLayout>

2.activity

package com.huan.squirrel.pushcardlayout.activity;

import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.huan.squirrel.pushcardlayout.R;
import com.huan.squirrel.pushcardlayout.pushcardlayout.PushCardLayout;

public class CustomerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_customer);
        initView();
    }

    private void initView() {
        //初始化
        PushCardLayout pcl_layout = findViewById(R.id.pcl_layout);

        /*************************   自定義頭部和底部佈局   ************************************************/
        TextView textView = new TextView(this);
        textView.setText("下拉重新整理資料");
        textView.setTextSize(20);
        textView.setBackgroundColor(Color.RED);

        TextView textView2 = new TextView(this);
        textView2.setText("上拉載入更多資料");
        textView2.setTextSize(20);
        textView2.setBackgroundColor(Color.YELLOW);

        //設定頂部佈局view
        pcl_layout.setTopLayoutView(textView);
        //設定底部佈局view
        pcl_layout.setBottomLayoutView(textView2);

        //禁用滑動 pcl_layout.setCanRefresh(false);

        /*************************   設定資料監聽器,可觸發網路請求  資料載入完成請手動恢復 pcl_layout.setCancel();  ********************************/
        pcl_layout.setDataListener(new PushCardLayout.PushCardDatalistener() {
            @Override
            public void onLoadMoreData() {
                Toast.makeText(CustomerActivity.this, "載入更多。。。", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onRefreshData() {
                Toast.makeText(CustomerActivity.this, "重新整理資料。。。", Toast.LENGTH_SHORT).show();
            }
        });

        /*************************   設定動畫監聽器,可自定義動畫   ************************************************/
        pcl_layout.setAnimationListener(new PushCardLayout.PushCardAnimationListener() {
            @Override
            public void onStart(View targetView) {
                Log.i("A", "Animation Start ...");

            }

            @Override
            public void onRuning(View targetView,boolean isUpper, final float value) {
                Log.i("A", "Animation onRuning:" + value);
                ((TextView) targetView).setRotation(value * 360);
                //isUpper 可判斷是頭部動畫還是底部動畫

            }

            @Override
            public void onEnd(View targetView) {
                Log.i("A", "Animation End ...");

            }
        });

    }
}

寫給自己用的,寫的不好還請大家指出。(許久不寫安卓了,要好好學習,多看看原始碼)