com.gu.swiperefresh.SwipeRefreshPlush.java Source code

Java tutorial

Introduction

Here is the source code for com.gu.swiperefresh.SwipeRefreshPlush.java

Source

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gu.swiperefresh;

import android.content.Context;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MotionEventCompat;
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.ScrollerCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;

import com.gu.swiperefresh.Utils.Log;

import static android.support.v4.widget.ViewDragHelper.INVALID_POINTER;

/**
 * Created by gu on 2016/11/13.
 *  SwipeRefreshLayout 
 * ??view ? AbsListView ?NestedScrollingChild
 * ?view  NestedScrollingChild ?view  AbsListView?sdk21 ???view requestDisallowInterceptTouchEvent 
 * targetview ???? ??
 */

public class SwipeRefreshPlush extends ViewGroup implements NestedScrollingParent, NestedScrollingChild {
    private static final float DRAG_RATE = .5f;
    private final String TAG = "SwipeRefreshPlush";
    private final NestedScrollingChildHelper mNestedScrollingChildHelper;
    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];
    int circleViewIndex = -1;
    private int REFRESH_MODE = 1;
    private OnRefreshListener mListener;
    private View mRefreshView;
    private View mLoadMoreView;
    private View mTarget;
    //?
    private float mUpTotalUnconsumed;
    //?
    private float mDownTotalUnconsumed;
    private float mInitialMotionY;
    private float mInitialDownY;
    private boolean mIsBeingDragUp;
    private boolean mIsBeingDragDown;
    private int mActivePointerId = INVALID_POINTER;
    private int mTouchSlop;
    private boolean mNestedScrollInProgress;
    // Target is returning to its start offset because it was cancelled or a
    // refresh was triggered.
    private boolean mReturningToStart;
    private ScrollerCompat mScroller;
    private VelocityTracker mVelocityTracker;
    private int mMaximumVelocity;
    //fling?
    private int mMinimumVelocity;

    private IRefreshViewController mRefreshController;
    private ILoadViewController mLoadViewController;

    private View mNoMoreView = null;
    //onInterceptTouchEventonTouch move
    private float mLastY;

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

    public SwipeRefreshPlush(Context context, AttributeSet attrs) {
        super(context, attrs);
        mLoadViewController = new LoadViewController(context, this);
        mRefreshController = new RefreshViewController(context, this);
        //   mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        mScroller = ScrollerCompat.create(getContext());
        createProgressView();
        ViewCompat.setChildrenDrawingOrderEnabled(this, true);
    }

    /**
     * ?
     *
     * @param onRefreshListener
     */
    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
        this.mListener = onRefreshListener;
        mLoadViewController.setRefreshListener(mListener);
        mRefreshController.setRefreshListener(mListener);
    }

    /**
     * ?
     * <p>
     * public static interface SwipeRefreshMode{
     * int MODE_BOTH=1;
     * int MODE_REFRESH_ONLY=2;
     * int MODE_LOADMODE=3;
     * int MODE_NONE=4;
     * }
     *
     * @param mode ?
     */
    public void setScrollMode(int mode) {
        this.REFRESH_MODE = mode;
    }

    /**
     * refresh progress color
     *
     * @param colorResIds
     */
    public void setRefreshColorResources(@ColorRes int... colorResIds) {
        final Context context = getContext();
        int[] colorRes = new int[colorResIds.length];
        for (int i = 0; i < colorResIds.length; i++) {
            colorRes[i] = ContextCompat.getColor(context, colorResIds[i]);
        }
        setRefreshColors(colorRes);
    }

    /**
     * progress 
     *
     * @param colors
     */
    public void setRefreshColors(@ColorInt int... colors) {
        ensureTarget();
        if (mRefreshController instanceof RefreshViewController) {
            ((RefreshViewController) mRefreshController).setProgressColors(colors);
        }
    }

    /**
     * loadmore 
     *
     * @param colorResIds
     */
    public void setLoadMoreColorResources(@ColorRes int... colorResIds) {
        final Context context = getContext();
        int[] colorRes = new int[colorResIds.length];
        for (int i = 0; i < colorResIds.length; i++) {
            colorRes[i] = ContextCompat.getColor(context, colorResIds[i]);
        }
        setLoadMoreColors(colorRes);
    }

    public void setLoadMoreColors(@ColorInt int... colors) {
        ensureTarget();
        if (mLoadViewController instanceof LoadViewController) {
            ((LoadViewController) mLoadViewController).setProgressColors(colors);
        }
    }

    /**
     * ?refresh
     *
     * @param refresh
     */
    public void setRefresh(boolean refresh) {
        ensureTarget();
        mRefreshController.setRefreshing(refresh, false);
    }

    /**
     * ?loadmore
     *
     * @param show
     */
    public void setLoadMore(boolean show) {
        if (show) {
            showLoadMoreView(mLoadViewController.getMaxHeight());
        } else {
            // hideLoadMoreView();
            hideLoadMoreView(mLoadViewController.getCurrentHeight());
            mLoadViewController.reset();
        }
    }

    /**
     * ??view
     *
     * @param view
     * @param layoutParams
     */
    public void setNoMoreView(View view, LayoutParams layoutParams) {
        mNoMoreView = view;
        mNoMoreView.setLayoutParams(layoutParams);
        // mLoadMoreView.setVisibility(GONE);
    }

    /**
     * ??
     *
     * @param show true:false:
     */
    public void showNoMore(boolean show) {
        mLoadViewController.showNoMore(show);
        if (show && mNoMoreView != null) {
            mLoadMoreView.clearAnimation();
            detachViewFromParent(mLoadMoreView);
            mLoadMoreView = mNoMoreView;
            addView(mNoMoreView, mNoMoreView.getLayoutParams());
        } else if (!show) {
            detachViewFromParent(mLoadMoreView);
            mLoadMoreView = mLoadViewController.getDefaultView();
            addView(mLoadMoreView);
        }
    }

    public ILoadViewController getLoadViewController() {
        return mLoadViewController;
    }

    public IRefreshViewController getRefreshController() {
        return mRefreshController;
    }

    @Override
    public void setEnabled(boolean enable) {
        super.setEnabled(enable);
        if (!enable) {
            reset();
        }
    }

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

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        Log.d(TAG, "onLayout: " + getChildCount());
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0)
            return;
        if (mTarget == null)
            ensureTarget();
        if (mTarget == null)
            return;
        final View child = mTarget;
        final int childLeft = getPaddingLeft();
        final int childRight = getPaddingRight();
        final int childTop = getPaddingTop();
        final int childBottom = getPaddingBottom();
        final int childWidth = width - childLeft - childRight;
        //   final int childHeight=child.getMeasuredHeight();
        final int childHeight = height - childTop - childBottom;
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
        final int circleWidth = mRefreshView.getMeasuredWidth();
        final int circleHeight = mRefreshView.getMeasuredHeight();
        final int loadViewWidth = mLoadMoreView.getMeasuredWidth();
        final int loadViewHeight = mLoadMoreView.getMeasuredHeight();
        mRefreshView.layout((width / 2 - circleWidth / 2), mRefreshController.getCurrentTargetOffsetTop(),
                (width / 2 + circleWidth / 2), mRefreshController.getCurrentTargetOffsetTop() + circleHeight);
        LayoutParams layoutParams = mLoadMoreView.getLayoutParams();
        if (layoutParams instanceof MarginLayoutParams) {
            final MarginLayoutParams lp = (MarginLayoutParams) mLoadMoreView.getLayoutParams();
            mLoadMoreView.layout(width / 2 - loadViewWidth / 2, height - childBottom + lp.topMargin,
                    width / 2 + loadViewWidth / 2, height + loadViewHeight + childBottom + lp.bottomMargin);
        } else {
            mLoadMoreView.layout(width / 2 - loadViewWidth / 2, height - childBottom, width / 2 + loadViewWidth / 2,
                    height + loadViewHeight + childBottom);
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            if (!canChildScrollDown() && canLoadMore()) {
                //showLoadMoreView(mLoadMoreView.getHeight());
                mLoadViewController.finishPullRefresh(mLoadViewController.getMaxHeight());
                mScroller.abortAnimation();
            }
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        if (circleViewIndex < 0) {
            return i;
        } else if (i == childCount - 1) {
            // Draw the selected child last
            return circleViewIndex;
        } else if (i >= circleViewIndex) {
            // Move the children after the selected child earlier one
            return i + 1;
        } else {
            // Keep the children before the selected child the same
            return i;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mTarget == null)
            ensureTarget();
        if (mTarget == null)
            return;
        mTarget.measure(
                MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                        MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                        MeasureSpec.EXACTLY));
        mRefreshView.measure(
                MeasureSpec.makeMeasureSpec(mRefreshController.getRefreshViewSize().getWidth(),
                        MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mRefreshController.getRefreshViewSize().getHeight(),
                        MeasureSpec.EXACTLY));
        measureChild(mLoadMoreView);
        //measureChild(mNoMoreView);
        circleViewIndex = -1;
        // Get the index of the circleview.
        for (int index = 0; index < getChildCount(); index++) {
            if (getChildAt(index) == mRefreshView) {
                circleViewIndex = index;
                break;
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = MotionEventCompat.getActionMasked(event);
        int pointerIndex;
        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
            mReturningToStart = false;
        }

        if (!isEnabled() || mReturningToStart || mRefreshController.isRefresh() || mNestedScrollInProgress) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            mIsBeingDragUp = false;
            mIsBeingDragDown = false;
            mActivePointerId = event.getPointerId(0);
            pointerIndex = event.findPointerIndex(mActivePointerId);
            if (pointerIndex < 0) {
                return false;
            }
            mInitialDownY = event.getY(pointerIndex);
            mLastY = mInitialDownY;
            return false;
        case MotionEvent.ACTION_CANCEL:
            return false;
        case MotionEvent.ACTION_MOVE: {
            pointerIndex = event.findPointerIndex(mActivePointerId);
            if (pointerIndex < 0) {
                // Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                return false;
            }

            final float y = event.getY(pointerIndex);
            startDragging(y);

            if (mIsBeingDragUp) {
                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                if (overscrollTop > 0) {
                    mRefreshController.showPullRefresh(overscrollTop);
                }

            } else if (mIsBeingDragDown) {
                int dy = (int) (y - mLastY);
                Log.i(TAG, "lasty:" + mLastY);
                Log.i(TAG, "dy:" + dy);
                //
                if (dy >= 0.5) {
                    hideLoadMoreView(Math.abs(dy));
                } else if (dy < -0.5) {
                    showLoadMoreView(Math.abs(dy));
                }
            }
            mLastY = y;
            break;
        }
        case MotionEvent.ACTION_UP: {
            pointerIndex = event.findPointerIndex(mActivePointerId);
            if (pointerIndex < 0) {
                //  Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
                return false;
            }

            if (mIsBeingDragUp) {
                final float y = event.getY(pointerIndex);
                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                mIsBeingDragUp = false;
                if (overscrollTop > 0)
                    mRefreshController.finishPullRefresh(overscrollTop);
            }
            if (mIsBeingDragDown) {
                final float y = event.getY(pointerIndex);
                final float overscrollBottom = (y - mInitialMotionY);
                mIsBeingDragDown = false;
                if (overscrollBottom < 0)
                    mLoadViewController.finishPullRefresh(Math.abs(overscrollBottom));
            }
            mActivePointerId = INVALID_POINTER;
            return false;
        }
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ensureTarget();

        final int action = MotionEventCompat.getActionMasked(ev);
        int pointerIndex;

        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
            mReturningToStart = false;
        }
        if (!isEnabled() || (!canLoadMore() && !canRefresh()) || mReturningToStart || mRefreshController.isRefresh()
                || mNestedScrollInProgress) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            mRefreshController.setTargetOffsetTopAndBottom(
                    mRefreshController.getCurrentTargetOffsetTop() - mRefreshView.getTop(), true);
            mActivePointerId = ev.getPointerId(0);
            mIsBeingDragUp = false;
            mIsBeingDragDown = false;

            pointerIndex = ev.findPointerIndex(mActivePointerId);
            if (pointerIndex < 0) {
                return false;
            }
            mInitialDownY = ev.getY(pointerIndex);
            initOrResetVelocityTracker();
            mVelocityTracker.addMovement(ev);
            break;

        case MotionEvent.ACTION_MOVE:
            if (mActivePointerId == INVALID_POINTER) {
                return false;
            }
            mVelocityTracker.addMovement(ev);
            pointerIndex = ev.findPointerIndex(mActivePointerId);
            if (pointerIndex < 0) {
                return false;
            }
            final float y = ev.getY(pointerIndex);
            startDragging(y);
            break;

        case MotionEventCompat.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            break;

        case MotionEvent.ACTION_UP:
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            float initialVelocity = velocityTracker.getYVelocity(mActivePointerId);
            Log.d(TAG, "fling:" + initialVelocity);
            if (Math.abs(initialVelocity) > mMinimumVelocity) {
                flingWithNestedDispatch(0, -initialVelocity);
            }
            releaseVelocityTracker();
            break;
        case MotionEvent.ACTION_CANCEL:
            mIsBeingDragUp = false;
            mActivePointerId = INVALID_POINTER;
            break;
        }

        return mIsBeingDragUp || mIsBeingDragDown;
    }

    @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 && mTarget instanceof AbsListView)
                || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
            // Nope.
        } else {
            super.requestDisallowInterceptTouchEvent(b);
        }
    }

    void reset() {
        mRefreshController.reset();
        mLoadViewController.reset();
    }

    private void createProgressView() {
        this.mRefreshView = mRefreshController.create();
        this.mLoadMoreView = mLoadViewController.create();
        addView(mLoadMoreView, mLoadMoreView.getLayoutParams());
        addView(mRefreshView);
    }

    private void initOrResetVelocityTracker() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        } else {
            mVelocityTracker.clear();
        }
    }

    private void releaseVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private void measureChild(View view) {
        if (view == null)
            return;
        LayoutParams lp = view.getLayoutParams();
        int width, height;
        width = getMeasureSpec(lp.width, getWidth());
        height = getMeasureSpec(lp.height, mLoadViewController.getMaxHeight());
        view.measure(width, height);
    }

    private int getMeasureSpec(int size, int parentSize) {
        int result;
        if (size == LayoutParams.MATCH_PARENT)
            result = MeasureSpec.makeMeasureSpec(parentSize, MeasureSpec.EXACTLY);
        else if (size == LayoutParams.WRAP_CONTENT)
            result = MeasureSpec.makeMeasureSpec(parentSize, MeasureSpec.AT_MOST);
        else
            result = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        return result;
    }

    private void startDragging(float y) {
        final float yDiff = y - mInitialDownY;
        if (yDiff > mTouchSlop && !mIsBeingDragUp) {
            if (!canChildScrollUp()) {
                mInitialMotionY = mInitialDownY + mTouchSlop;
                mIsBeingDragUp = true;
                mRefreshController.startProgress();
            } else if (mLoadViewController.getCurrentHeight() > 0) {
                hideLoadMoreView((int) yDiff);
            }
        } else if (yDiff < -mTouchSlop && !mIsBeingDragDown && !canChildScrollDown() && canLoadMore()) {
            Log.d(TAG, yDiff + ":" + mTouchSlop);
            mInitialMotionY = mInitialDownY + mTouchSlop;
            mLastY = mInitialDownY;
            mIsBeingDragDown = true;
        }
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = ev.getPointerId(pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mActivePointerId = ev.getPointerId(newPointerIndex);
            //mVelocityTracker.addMovement(ev);
        }
    }

    private boolean flingWithNestedDispatch(float velocityX, float velocityY) {
        final boolean canFling = Math.abs(velocityY) > mMinimumVelocity;
        if (!dispatchNestedPreFling(velocityX, velocityY)) {
            dispatchNestedFling(velocityX, velocityY, canFling);
            if (canFling) {
                return fling(velocityY);
            }
        }
        return false;
    }

    private boolean fling(float velocityY) {
        if (velocityY <= 0) {
            if (mLoadViewController.getCurrentHeight() > 0) {
                hideLoadMoreView(mLoadViewController.getCurrentHeight());
            }
            mScroller.abortAnimation();
            return false;
        }
        //mPullState = STATE_FLING;
        mScroller.abortAnimation();
        mScroller.computeScrollOffset();
        if (canChildScrollUp() && canLoadMore()) {
            mScroller.fling(0, mScroller.getCurrY(), 0, (int) velocityY, 0, 0, Integer.MIN_VALUE,
                    Integer.MAX_VALUE);
        }
        ViewCompat.postInvalidateOnAnimation(this);
        return false;
    }

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

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

    @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 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);
    }

    /**
     * @param child
     * @param target
     * @param nestedScrollAxes 
     * @return
     */
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return REFRESH_MODE != SwipeRefreshMode.MODE_NONE && !mRefreshController.isRefresh()
                && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;

    }

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

    @Override
    public void onStopNestedScroll(View target) {
        mNestedScrollInProgress = false;
        mNestedScrollingParentHelper.onStopNestedScroll(target);
        if (mUpTotalUnconsumed > 0) {
            mRefreshController.finishPullRefresh(mUpTotalUnconsumed);
            mUpTotalUnconsumed = 0;
        }
        if (mDownTotalUnconsumed > 0) {
            mLoadViewController.finishPullRefresh(mDownTotalUnconsumed);
            mDownTotalUnconsumed = 0;
        }
        stopNestedScroll();
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow);
        final int dy = dyUnconsumed + mParentOffsetInWindow[1];
        if (mRefreshController.isRefresh())
            return;
        if (dy < 0 && !canChildScrollUp() && canRefresh()) {
            mUpTotalUnconsumed += Math.abs(dy);
            //moveSpinner(mUpTotalUnconsumed);
            mRefreshController.showPullRefresh(mUpTotalUnconsumed);
        } else if (dy > 0 && !canChildScrollDown() && canLoadMore()) {
            mDownTotalUnconsumed += dy;
            showLoadMoreView(dy);
        }
    }

    /**
     * parent 
     *
     * @param target
     * @param dx
     * @param dy       y??>0?
     * @param consumed parent
     */
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        if (REFRESH_MODE != SwipeRefreshMode.MODE_NONE) {
            if (dy > 0 && mUpTotalUnconsumed > 0) {
                if (dy > mUpTotalUnconsumed) {
                    consumed[1] = dy - (int) mUpTotalUnconsumed;
                    mUpTotalUnconsumed = 0;
                } else {
                    mUpTotalUnconsumed -= dy;
                    consumed[1] = dy;
                }
                mRefreshController.showPullRefresh(mUpTotalUnconsumed);
            } else if (dy < -1 && mLoadViewController.getCurrentHeight() > 0) {
                if (dy + mDownTotalUnconsumed < 0) {
                    consumed[1] = dy + (int) mDownTotalUnconsumed;
                    mDownTotalUnconsumed = 0;
                } else {
                    mDownTotalUnconsumed += dy;
                    consumed[1] = dy;
                }
                hideLoadMoreView(Math.abs(dy));
            }
        }
        // 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 boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        if (!consumed) {
            flingWithNestedDispatch(velocityX, velocityY);
            return true;
        }
        return dispatchNestedFling(velocityX, velocityY, consumed);
    }

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

    }

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

    /********************
     * parent end
     ************************************/

    /*******************************************************************/
    /**
     * targrt view ??
     *
     * @return target view ??
     */
    private boolean canChildScrollUp() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0
                        || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
            }
        } else {
            return mTarget.canScrollVertically(-1);
        }
    }

    /**
     * target view ??
     *
     * @return
     */
    private boolean canChildScrollDown() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                int count = absListView.getChildCount();
                //return absListView.canScrollList(-1);
                int position = absListView.getLastVisiblePosition();
                return (count > position + 1)
                        || absListView.getChildAt(position).getBottom() <= absListView.getPaddingBottom();
            } else {
                return ViewCompat.canScrollVertically(mTarget, 1);
            }
        } else {
            return mTarget.canScrollVertically(1);
        }
    }

    private void showLoadMoreView(int height) {
        if (mLoadMoreView.getVisibility() != VISIBLE)
            mLoadMoreView.setVisibility(VISIBLE);
        scrollBy(0, mLoadViewController.move(height));
        //  mLoadMoreView.clearAnimation();
        //  mLoadViewController.finishPullRefresh();
    }

    private void hideLoadMoreView(int height) {
        if (mLoadViewController.getCurrentHeight() > 0) {
            int currentHeight = mLoadViewController.getCurrentHeight();
            if (height > currentHeight) {
                height = currentHeight;
            }
            scrollBy(0, mLoadViewController.move(-height));
        }
        if (mLoadViewController.getCurrentHeight() <= 0) {
            mLoadViewController.reset();
        }
    }

    //    /**
    //     * loadmore view
    //     *
    //     * @param view
    //     * @param layoutParams
    //     */
    //    public void setLoadMoreView(View view, LayoutParams layoutParams) {
    //        detachViewFromParent(mLoadMoreView);
    //        this.mLoadMoreView = view;
    //        addView(mLoadMoreView, layoutParams);
    //        mLoadViewController.changeDefaultView(mLoadMoreView);
    //    }

    private void ensureTarget() {
        // Don't bother getting the parent height if the parent hasn't been laid
        // out yet.
        if (mTarget == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(mRefreshView) && !child.equals(mLoadMoreView)) {
                    mTarget = child;
                    break;
                }
            }
        }
    }

    private boolean canRefresh() {
        if (REFRESH_MODE == SwipeRefreshMode.MODE_BOTH || REFRESH_MODE == SwipeRefreshMode.MODE_REFRESH_ONLY)
            return true;
        else
            return false;
    }

    private boolean canLoadMore() {
        if ((REFRESH_MODE == SwipeRefreshMode.MODE_BOTH || REFRESH_MODE == SwipeRefreshMode.MODE_LOADMODE)
                && canChildScrollUp()) {
            return true;
        } else
            return false;
    }

    public static interface SwipeRefreshMode {
        int MODE_BOTH = 1;//?
        int MODE_REFRESH_ONLY = 2;//
        int MODE_LOADMODE = 3;//
        int MODE_NONE = 4;//???
    }

    public interface OnRefreshListener {
        void onPullDownToRefresh();

        void onPullUpToRefresh();
    }
}