org.alice.jack_blog.widget.refreshContainer.CanRefreshLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.alice.jack_blog.widget.refreshContainer.CanRefreshLayout.java

Source

package org.alice.jack_blog.widget.refreshContainer;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.Scroller;

import org.alice.jack_blog.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Copyright 2016 canyinghao
 * 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.
 */
public class CanRefreshLayout extends FrameLayout {

    //  
    private static final int DEFAULT_DURATION = 300;

    //  
    private static final int DEFAULT_AUTO_DURATION = 100;
    //    
    private static final float DEFAULT_FRICTION = 0.5f;
    //   ????  CLASSIC
    private static final int DEFAULT_SMOOTH_LENGTH = 50;
    //   ??? CLASSIC
    private static final int DEFAULT_SMOOTH_DURATION = 3;

    //  ?
    private static byte NO_SCROLL = 0;
    private static byte NO_SCROLL_UP = 1;
    private static byte NO_SCROLL_DOWN = 2;

    // 
    public static final byte CLASSIC = 0;
    public static final byte UPPER = 1;
    public static final byte LOWER = 2;
    public static final byte MID = 3;

    //  
    protected View mHeaderView;
    //  
    protected View mFooterView;
    //    
    protected View mContentView;

    //      mIsCoo=true
    protected View mScrollView;

    protected ViewPager mViewPager;

    // 0 mScrollViewNestedScrollingChild  1 viewpager
    protected boolean mIsViewPager;

    protected AppBarLayout mAppBar;

    //    0??
    protected int mMaxHeaderHeight;

    //    0??
    protected int mMaxFooterHeight;

    //   
    protected OnStartUpListener onStartUpListener;

    //   
    protected OnStartDownListener onStartDownListener;
    //    
    protected int mHeaderHeight;
    //    
    protected int mFooterHeight;

    private boolean isSetHeaderHeight;
    private boolean isSetFooterHeight;
    //    ?
    private boolean isHeaderRefreshing;
    private boolean isFooterRefreshing;

    //    
    private float mFriction = DEFAULT_FRICTION;
    //    ??
    private boolean mRefreshEnabled = true;
    //    ??
    private boolean mLoadMoreEnabled = true;
    //   ?
    protected OnRefreshListener mOnRefreshListener;
    //    ?
    protected OnLoadMoreListener mOnLoadMoreListener;
    //  
    private int mHeadStyle = CLASSIC;
    //    
    private int mFootStyle = CLASSIC;

    //    
    private int mDuration = DEFAULT_DURATION;
    //    ???  CLASSIC
    private int mSmoothLength = DEFAULT_SMOOTH_LENGTH;
    //    ?? CLASSIC
    private int mSmoothDuration = DEFAULT_SMOOTH_DURATION;

    //  ??view?
    private int isUpOrDown = NO_SCROLL;
    //  y?
    float directionX;
    //   x?
    float directionY;
    //   ??
    private int mHeadOffY;
    //    ??
    private int mFootOffY;
    //    ??
    private int mContentOffY;
    //  ??
    private float lastY;
    //  ??
    private int currentOffSetY;
    //  ?
    private int offsetSum;
    //    ?
    private int scrollSum;
    //  
    private int tempY;
    //   
    private int mRefreshBackgroundResource;
    //    
    private int mLoadMoreBackgroundResource;
    //  ?CoordinatorLayout
    private boolean mIsCoo;
    //  ?  mIsCoo=true
    private boolean isDependentOpen = true;

    private Scroller mScroller = new Scroller(getContext());

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

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

    public CanRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CanRefreshLayout, defStyleAttr, 0);

        try {
            final int N = a.getIndexCount();
            for (int i = 0; i < N; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.CanRefreshLayout_can_enabled_up) {
                    setRefreshEnabled(a.getBoolean(attr, true));

                } else if (attr == R.styleable.CanRefreshLayout_can_enabled_down) {
                    setLoadMoreEnabled(a.getBoolean(attr, true));

                } else if (attr == R.styleable.CanRefreshLayout_can_style_up) {
                    mHeadStyle = a.getInt(attr, CLASSIC);

                } else if (attr == R.styleable.CanRefreshLayout_can_style_down) {
                    mFootStyle = a.getInt(attr, CLASSIC);

                } else if (attr == R.styleable.CanRefreshLayout_can_friction) {

                    setFriction(a.getFloat(attr, DEFAULT_FRICTION));

                } else if (attr == R.styleable.CanRefreshLayout_can_duration) {

                    mDuration = a.getInt(attr, DEFAULT_DURATION);

                } else if (attr == R.styleable.CanRefreshLayout_can_smooth_duration) {

                    mSmoothDuration = a.getInt(attr, DEFAULT_SMOOTH_DURATION);

                } else if (attr == R.styleable.CanRefreshLayout_can_smooth_length) {

                    mSmoothLength = a.getInt(attr, DEFAULT_SMOOTH_LENGTH);

                } else if (attr == R.styleable.CanRefreshLayout_can_bg_up) {

                    mRefreshBackgroundResource = a.getResourceId(attr, android.R.color.transparent);

                } else if (attr == R.styleable.CanRefreshLayout_can_bg_down) {

                    mLoadMoreBackgroundResource = a.getResourceId(attr, android.R.color.transparent);

                } else if (attr == R.styleable.CanRefreshLayout_can_is_coo) {

                    mIsCoo = a.getBoolean(attr, false);

                }
            }

        } finally {
            a.recycle();
        }

    }

    private void setAppBarListener() {
        if (mAppBar != null) {

            mAppBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
                @Override
                public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {

                    int miniH = mAppBar.getMeasuredHeight() / 2;

                    if (verticalOffset == 0) {
                        isDependentOpen = true;
                    } else if (Math.abs(verticalOffset) >= miniH) {
                        isDependentOpen = false;
                    }
                }
            });
        }
    }

    /**
     * 
     *
     * @param mRefreshBackgroundResource int
     */
    public void setRefreshBackgroundResource(int mRefreshBackgroundResource) {
        this.mRefreshBackgroundResource = mRefreshBackgroundResource;
    }

    /**
     * 
     *
     * @param mLoadMoreBackgroundResource int
     */
    public void setLoadMoreBackgroundResource(int mLoadMoreBackgroundResource) {
        this.mLoadMoreBackgroundResource = mLoadMoreBackgroundResource;
    }

    /**
     * ??
     *
     * @param mHeaderHeight int
     */
    public void setHeaderHeight(int mHeaderHeight) {
        this.mHeaderHeight = mHeaderHeight;
        isSetHeaderHeight = true;
    }

    /**
     * ??
     *
     * @param mFooterHeight int
     */
    public void setFooterHeight(int mFooterHeight) {
        this.mFooterHeight = mFooterHeight;
        isSetFooterHeight = true;
    }

    /**
     * ??
     *
     * @param enable boolean
     */
    public void setRefreshEnabled(boolean enable) {
        this.mRefreshEnabled = enable;
    }

    /**
     * ??
     *
     * @param enable boolean
     */
    public void setLoadMoreEnabled(boolean enable) {
        this.mLoadMoreEnabled = enable;
    }

    /**
     * ?
     *
     * @param mOnRefreshListener OnRefreshListener
     */
    public void setOnRefreshListener(@NonNull OnRefreshListener mOnRefreshListener) {
        this.mOnRefreshListener = mOnRefreshListener;
    }

    /**
     * ?
     *
     * @param mOnLoadMoreListener OnLoadMoreListener
     */
    public void setOnLoadMoreListener(@NonNull OnLoadMoreListener mOnLoadMoreListener) {
        this.mOnLoadMoreListener = mOnLoadMoreListener;
    }

    /**
     * 
     *
     * @param mMaxHeaderHeight int
     */
    public void setMaxHeaderHeight(int mMaxHeaderHeight) {

        this.mMaxHeaderHeight = mMaxHeaderHeight;

    }

    /**
     * 
     *
     * @param mMaxFooterHeight int
     */
    public void setMaxFooterHeight(int mMaxFooterHeight) {
        this.mMaxFooterHeight = mMaxFooterHeight;
    }

    /**
     * ?
     *
     * @param onStartUpListener OnStartUpListener
     */
    public void setOnStartUpListener(OnStartUpListener onStartUpListener) {
        this.onStartUpListener = onStartUpListener;
    }

    /**
     * ?
     *
     * @param onStartDownListener OnStartDownListener
     */
    public void setOnStartDownListener(OnStartDownListener onStartDownListener) {
        this.onStartDownListener = onStartDownListener;
    }

    /**
     * 
     *
     * @param mFriction FloatRange
     */
    public void setFriction(@FloatRange(from = 0.0, to = 1.0) float mFriction) {

        this.mFriction = mFriction;

    }

    /**
     * 
     *
     * @param mDuration int
     */
    public void setDuration(int mDuration) {
        this.mDuration = mDuration;
    }

    /**
     * ???
     *
     * @param mSmoothLength int
     */
    public void setSmoothLength(int mSmoothLength) {
        this.mSmoothLength = mSmoothLength;
    }

    /**
     * ??
     *
     * @param mSmoothDuration int
     */
    public void setSmoothDuration(int mSmoothDuration) {
        this.mSmoothDuration = mSmoothDuration;
    }

    /**
     * 
     *
     * @param headStyle IntRange
     * @param footStyle IntRange
     */
    public void setStyle(@IntRange(from = 0, to = 3) int headStyle, @IntRange(from = 0, to = 3) int footStyle) {

        this.mHeadStyle = headStyle;
        this.mFootStyle = footStyle;

        if (mHeadStyle == LOWER || mHeadStyle == MID) {

            bringChildToFront(mContentView);

        }

        if (mFootStyle == LOWER || mFootStyle == MID) {

            bringChildToFront(mContentView);
        }

        if (mHeaderView != null && (mHeadStyle == CLASSIC || mHeadStyle == UPPER)) {

            bringChildToFront(mHeaderView);
        }

        if (mFooterView != null && (mFootStyle == CLASSIC || mFootStyle == UPPER)) {

            bringChildToFront(mFooterView);

        }

        int count = getChildCount();

        List<View> list = new ArrayList<>();

        for (int i = 0; i < count; i++) {

            View v = getChildAt(i);

            if (v != mHeaderView && v != mFooterView && v != mContentView) {

                list.add(v);
            }

        }

        for (View v : list) {

            bringChildToFront(v);

        }

    }

    /**
     * idview
     */
    @Override
    protected void onFinishInflate() {

        final int childCount = getChildCount();

        if (childCount > 0) {
            mHeaderView = findViewById(R.id.can_refresh_header);
            mContentView = findViewById(R.id.can_content_view);
            mFooterView = findViewById(R.id.can_refresh_footer);
            mScrollView = findViewById(R.id.can_scroll_view);
        }

        if (mContentView == null) {
            throw new IllegalStateException("mContentView is null");
        }

        if (mIsCoo) {
            if (mContentView instanceof CoordinatorLayout) {

                CoordinatorLayout coo = (CoordinatorLayout) mContentView;

                mAppBar = (AppBarLayout) coo.getChildAt(0);

                setAppBarListener();

            } else {
                throw new IllegalStateException("mContentView is not CoordinatorLayout");
            }

            if (mScrollView == null) {
                throw new IllegalStateException("mScrollView is null");
            }

            if (mScrollView instanceof ViewPager) {

                mViewPager = (ViewPager) mScrollView;
                mIsViewPager = true;

            } else if (mScrollView instanceof NestedScrollingChild) {

                mIsViewPager = false;

            } else {
                throw new IllegalStateException("mScrollView is not NestedScrollingChild or ViewPager");
            }
        }

        if (mHeaderView != null && !(mHeaderView instanceof CanRefresh)) {

            throw new IllegalStateException("mHeaderView  error");
        }
        if (mFooterView != null && !(mFooterView instanceof CanRefresh)) {

            throw new IllegalStateException("mFooterView error");
        }

        if (mHeaderView != null) {

            getHeaderInterface().setIsHeaderOrFooter(true);
        }

        if (mFooterView != null) {

            getFooterInterface().setIsHeaderOrFooter(false);
        }

        super.onFinishInflate();

        setStyle(mHeadStyle, mFootStyle);

    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

        childLayout();

    }

    /**
     * view?
     */
    private void childLayout() {

        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();

        if (mHeaderView != null) {
            MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
            final int left = paddingLeft + lp.leftMargin;
            final int top = paddingTop + lp.topMargin - mHeaderHeight + mHeadOffY;
            final int right = left + mHeaderView.getMeasuredWidth();
            final int bottom = top + mHeaderView.getMeasuredHeight();

            mHeaderView.layout(left, top, right, bottom);

        }

        if (mFooterView != null) {
            MarginLayoutParams lp = (MarginLayoutParams) mFooterView.getLayoutParams();
            final int left = paddingLeft + lp.leftMargin;
            final int top = getMeasuredHeight() + paddingTop + lp.topMargin - mFootOffY;
            final int right = left + mFooterView.getMeasuredWidth();
            final int bottom = top + mFooterView.getMeasuredHeight();
            mFooterView.layout(left, top, right, bottom);

        }
        if (mContentView != null) {

            MarginLayoutParams lp = (MarginLayoutParams) mContentView.getLayoutParams();
            final int left = paddingLeft + lp.leftMargin;
            final int top = paddingTop + lp.topMargin + mContentOffY;
            final int right = left + mContentView.getMeasuredWidth();
            final int bottom = top + mContentView.getMeasuredHeight();

            mContentView.layout(left, top, right, bottom);
        }

        int count = getChildCount();

        for (int i = 0; i < count; i++) {

            View v = getChildAt(i);

            if (v != mHeaderView && v != mFooterView && v != mContentView) {

                MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
                int left = paddingLeft + lp.leftMargin;
                int top = paddingTop + lp.topMargin + mContentOffY;

                int right = left + v.getMeasuredWidth();
                int bottom = top + v.getMeasuredHeight();

                v.layout(left, top, right, bottom);
                System.out.println("==========top" + top);
            }

        }

    }

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

        if (mHeaderView != null) {
            measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
            MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();

            if (!isSetHeaderHeight) {
                mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            }

        }

        if (mFooterView != null) {
            measureChildWithMargins(mFooterView, widthMeasureSpec, 0, heightMeasureSpec, 0);
            MarginLayoutParams lp = (MarginLayoutParams) mFooterView.getLayoutParams();

            if (!isSetFooterHeight) {
                mFooterHeight = mFooterView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            }

        }
        if (mContentView != null) {

            measureChildWithMargins(mContentView, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }

        int count = getChildCount();

        for (int i = 0; i < count; i++) {

            View v = getChildAt(i);

            if (v != mHeaderView && v != mFooterView && v != mContentView) {

                measureChildWithMargins(v, widthMeasureSpec, 0, heightMeasureSpec, 0);

            }

        }

    }

    /**
     * ?
     *
     * @return boolean
     */
    private boolean canRefresh() {

        return !isHeaderRefreshing && mRefreshEnabled && mHeaderView != null && !canChildScrollUp();
    }

    /**
     * ?
     *
     * @return boolean
     */
    private boolean canLoadMore() {

        return !isFooterRefreshing && mLoadMoreEnabled && mFooterView != null && !canChildScrollDown();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {

        return super.dispatchTouchEvent(e);

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {

        switch (e.getAction()) {

        case MotionEvent.ACTION_DOWN:

            directionY = e.getY();
            directionX = e.getX();

            break;

        case MotionEvent.ACTION_MOVE:

            if (directionY <= 0 || directionX <= 0) {

                break;
            }

            float eventY = e.getY();
            float eventX = e.getX();

            float offY = eventY - directionY;
            float offX = eventX - directionX;

            directionY = eventY;
            directionX = eventX;

            boolean moved = Math.abs(offY) > Math.abs(offX);

            if (offY > 0 && moved && canRefresh()) {
                isUpOrDown = NO_SCROLL_UP;
            } else if (offY < 0 && moved && canLoadMore()) {

                isUpOrDown = NO_SCROLL_DOWN;
            } else {
                isUpOrDown = NO_SCROLL;
            }

            if (isUpOrDown == NO_SCROLL_DOWN || isUpOrDown == NO_SCROLL_UP) {

                return true;
            }

            break;

        }

        return super.onInterceptTouchEvent(e);

    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {

        //     ??view
        if (!canChildScrollDown() && !canChildScrollUp()) {

            if (isUpOrDown == NO_SCROLL_UP) {
                if (canRefresh()) {

                    return touch(e, true);

                }
            } else if (isUpOrDown == NO_SCROLL_DOWN) {
                if (canLoadMore()) {

                    return touch(e, false);
                }

            } else {

                switch (e.getAction()) {

                case MotionEvent.ACTION_DOWN:

                    directionY = e.getY();
                    directionX = e.getX();

                    break;

                case MotionEvent.ACTION_MOVE:
                    if (directionY <= 0 || directionX <= 0) {

                        break;
                    }

                    float eventY = e.getY();
                    float eventX = e.getX();

                    float offY = eventY - directionY;
                    float offX = eventX - directionX;

                    directionY = eventY;
                    directionX = eventX;

                    boolean moved = Math.abs(offY) > Math.abs(offX);

                    if (offY > 0 && moved && canRefresh()) {
                        isUpOrDown = NO_SCROLL_UP;
                    } else if (offY < 0 && moved && canLoadMore()) {

                        isUpOrDown = NO_SCROLL_DOWN;
                    } else {
                        isUpOrDown = NO_SCROLL;
                    }

                    break;

                }

                return true;

            }

        } else {

            if (canRefresh()) {
                return touch(e, true);

            } else if (canLoadMore()) {

                return touch(e, false);
            }
        }

        return super.onTouchEvent(e);
    }

    /**
     * ?
     *
     * @param e      MotionEvent
     * @param isHead boolean
     * @return boolean
     */
    private boolean touch(MotionEvent e, boolean isHead) {

        switch (e.getAction()) {

        case MotionEvent.ACTION_DOWN:

            lastY = e.getY();
            return true;

        case MotionEvent.ACTION_MOVE:

            if (lastY > 0) {
                currentOffSetY = (int) (e.getY() - lastY);
                offsetSum += currentOffSetY;
            }
            lastY = e.getY();

            boolean isCanMove;
            if (isHead) {
                isCanMove = offsetSum > 0;
            } else {
                isCanMove = offsetSum < 0;
            }

            if (isCanMove) {

                float ratio = getRatio();

                if (ratio < 0) {
                    ratio = 0;
                }

                int scrollNum = -((int) (currentOffSetY * ratio));

                scrollSum += scrollNum;

                if (isHead) {

                    if (mMaxHeaderHeight > 0 && Math.abs(scrollSum) > mMaxHeaderHeight) {

                        scrollSum = scrollSum > 0 ? mMaxHeaderHeight : -mMaxHeaderHeight;

                        scrollNum = 0;
                    }

                } else {

                    if (mMaxFooterHeight > 0 && Math.abs(scrollSum) > mMaxFooterHeight) {

                        scrollSum = scrollSum > 0 ? mMaxFooterHeight : -mMaxFooterHeight;

                        scrollNum = 0;
                    }
                }

                if (isHead) {
                    setBackgroundResource(mRefreshBackgroundResource);

                    if (onStartUpListener != null && Math.abs(scrollSum) > 0) {

                        onStartUpListener.onUp();
                    }

                    smoothMove(true, true, scrollNum, scrollSum);

                    if (Math.abs(scrollSum) > mHeaderHeight) {

                        getHeaderInterface().onPrepare();
                    }

                    getHeaderInterface().onPositionChange(Math.abs(scrollSum) / (float) mHeaderHeight);
                } else {
                    setBackgroundResource(mLoadMoreBackgroundResource);

                    if (onStartDownListener != null && Math.abs(scrollSum) > 0) {

                        onStartDownListener.onDown();
                    }

                    smoothMove(false, true, scrollNum, scrollSum);

                    if (Math.abs(scrollSum) > mFooterHeight) {

                        getFooterInterface().onPrepare();
                    }

                    getFooterInterface().onPositionChange(Math.abs(scrollSum) / (float) mFooterHeight);

                }

            }

            return true;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:

            if (isHead) {
                if (Math.abs(scrollSum) > mHeaderHeight) {

                    smoothMove(true, false, mHeadStyle == CLASSIC ? -mHeaderHeight : mHeaderHeight, mHeaderHeight);
                    getHeaderInterface().onRelease();
                    refreshing();
                } else {

                    smoothMove(true, false, 0, 0);

                    if (onStartUpListener != null) {
                        onStartUpListener.onReset();
                    }
                }

            } else {

                if (Math.abs(scrollSum) > mFooterHeight) {

                    smoothMove(false, false,
                            mFootStyle == CLASSIC
                                    ? mContentView.getMeasuredHeight() - getMeasuredHeight() + mFooterHeight
                                    : mFooterHeight,
                            mFooterHeight);

                    getFooterInterface().onRelease();
                    loadingMore();
                } else {

                    smoothMove(false, false,
                            mFootStyle == CLASSIC ? mContentView.getMeasuredHeight() - getMeasuredHeight() : 0, 0);

                    if (onStartDownListener != null) {
                        onStartDownListener.onReset();
                    }
                }

            }

            resetParameter();

            break;

        }

        return super.onTouchEvent(e);

    }

    /**
     * ??
     *
     * @return float
     */
    private float getRatio() {

        return 1 - (Math.abs(offsetSum) / (float) getMeasuredHeight()) - 0.3f * mFriction;

    }

    /**
     * ??
     */
    private void resetParameter() {

        directionX = 0;
        directionY = 0;
        isUpOrDown = NO_SCROLL;
        lastY = 0;
        offsetSum = 0;
        scrollSum = 0;

    }

    /**
     * * 
     *
     * @param isHeader    boolean
     * @param isMove      boolean  ?
     * @param moveScrollY int
     * @param moveY       int
     */
    private void smoothMove(boolean isHeader, boolean isMove, int moveScrollY, int moveY) {

        moveY = Math.abs(moveY);
        if (isHeader) {

            if (mHeadStyle == CLASSIC) {

                if (isMove) {
                    smoothScrollBy(0, moveScrollY);
                } else {

                    smoothScrollTo(0, moveScrollY);
                }
            } else {

                layoutMove(true, isMove, moveScrollY, moveY);
            }

        } else {

            if (mFootStyle == CLASSIC) {

                if (isMove) {
                    smoothScrollBy(0, moveScrollY);
                } else {

                    smoothScrollTo(0, moveScrollY);
                }
            } else {

                layoutMove(false, isMove, moveScrollY, moveY);
            }

        }

    }

    /**
     * ?
     *
     * @param fx int
     * @param fy int
     */
    public void smoothScrollTo(int fx, int fy) {
        int dx = fx - mScroller.getFinalX();
        int dy = fy - mScroller.getFinalY();
        smoothScrollBy(dx, dy);
    }

    /**
     * ??
     *
     * @param dx int
     * @param dy int
     */
    public void smoothScrollBy(int dx, int dy) {

        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
        invalidate();

    }

    /**
     * ???
     *
     * @param isHeader    ?
     * @param moveScrollY ?
     * @param isMove      ?
     * @param moveY       ?
     */
    private void layoutMove(boolean isHeader, boolean isMove, int moveScrollY, int moveY) {

        if (isMove) {

            if (isHeader) {

                if (mHeadStyle == UPPER) {

                    mHeadOffY = moveY;

                } else if (mHeadStyle == LOWER) {

                    mHeadOffY = mHeaderHeight;
                    mContentOffY = moveY;

                } else if (mHeadStyle == MID) {

                    mHeadOffY = moveY / 2 + mHeaderHeight / 2;

                    mContentOffY = moveY;

                }

            } else {

                if (mFootStyle == UPPER) {

                    mFootOffY = moveY;

                } else if (mFootStyle == LOWER) {

                    mFootOffY = mFooterHeight;
                    mContentOffY = -moveY;

                } else if (mFootStyle == MID) {

                    mFootOffY = moveY / 2 + mFooterHeight / 2;

                    mContentOffY = -moveY;

                }

            }

        } else {

            layoutMoveSmooth(isHeader, moveScrollY, moveY);

        }

        requestLayout();
    }

    private void layoutMoveSmooth(boolean isHeader, int moveScrollY, int moveY) {

        if (moveScrollY > 0) {

            tempY = moveScrollY;
            layoutSmoothMove(isHeader, moveScrollY, moveY);

        } else {
            tempY = Math.abs(scrollSum);
            layoutSmoothMove(isHeader, moveScrollY, moveY);

        }

    }

    private void layoutSmoothMove(final boolean isHeader, final int moveScrollY, final int moveY) {

        tempY -= mSmoothLength;

        if (tempY <= moveY) {

            layoutMove(isHeader, true, moveScrollY, moveY);
            return;
        }

        layoutMove(isHeader, true, moveScrollY, tempY);

        postDelayed(new Runnable() {
            @Override
            public void run() {
                layoutSmoothMove(isHeader, moveScrollY, moveY);
            }
        }, mSmoothDuration);

    }

    /**
     * ?
     */
    public void refreshComplete() {

        if (!isHeaderRefreshing) {
            return;
        }

        postDelayed(new Runnable() {
            @Override
            public void run() {
                smoothMove(true, false, mHeadStyle == CLASSIC ? 0 : mHeaderHeight, 0);
                isHeaderRefreshing = false;
                getHeaderInterface().onComplete();
                getHeaderInterface().onReset();
                if (onStartUpListener != null) {
                    onStartUpListener.onReset();
                }

            }
        }, mDuration);

    }

    /**
     * ?
     */
    public void loadMoreComplete() {
        if (!isFooterRefreshing) {
            return;
        }
        postDelayed(new Runnable() {
            @Override
            public void run() {
                smoothMove(false, false,
                        mFootStyle == CLASSIC ? mContentView.getMeasuredHeight() - getMeasuredHeight()
                                : mFooterHeight,
                        0);
                isFooterRefreshing = false;
                getFooterInterface().onComplete();
                getFooterInterface().onReset();
                if (onStartDownListener != null) {
                    onStartDownListener.onReset();
                }

            }
        }, mDuration);

    }

    /**
     * 
     */
    public void autoRefresh() {

        if (mHeaderView != null) {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    setBackgroundResource(mRefreshBackgroundResource);
                    smoothMove(true, false, -mHeaderHeight, -mHeaderHeight);
                    getHeaderInterface().onRelease();
                    refreshing();
                }
            }, DEFAULT_AUTO_DURATION);

        }

    }

    private void refreshing() {
        isHeaderRefreshing = true;
        if (mOnRefreshListener != null) {

            mOnRefreshListener.onRefresh();
        }

    }

    private void loadingMore() {
        isFooterRefreshing = true;
        if (mOnLoadMoreListener != null) {

            mOnLoadMoreListener.onLoadMore();
        }

    }

    @Override
    public void computeScroll() {

        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }

        super.computeScroll();
    }

    private CanRefresh getHeaderInterface() {
        return (CanRefresh) mHeaderView;
    }

    private CanRefresh getFooterInterface() {
        return (CanRefresh) mFooterView;
    }

    /**
     * ?
     *
     * @return boolean
     */
    protected boolean canChildScrollUp() {

        if (mIsCoo) {
            if (mIsViewPager) {
                int current = mViewPager.getCurrentItem();
                if (current < mViewPager.getChildCount()) {

                    PagerAdapter adapter = mViewPager.getAdapter();

                    if (adapter instanceof FragmentPagerAdapter) {

                        FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) adapter;

                        Fragment fragment = fragmentPagerAdapter.getItem(current);

                        if (fragment != null) {
                            mScrollView = fragment.getView();
                        }

                    } else {

                        mScrollView = mViewPager.getChildAt(current);
                    }

                }
            }

            if (mScrollView == null) {
                return false;
            }

            return !isDependentOpen || canScrollUp(mScrollView);

        }
        return canScrollUp(mContentView);
    }

    private boolean canScrollUp(View view) {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (view instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) view;
                return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0
                        || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(view, -1) || view.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(view, -1);
        }
    }

    /**
     * ?
     *
     * @return boolean
     */
    protected boolean canChildScrollDown() {

        if (mIsCoo) {

            if (mIsViewPager) {
                int current = mViewPager.getCurrentItem();
                if (current < mViewPager.getChildCount()) {

                    PagerAdapter adapter = mViewPager.getAdapter();

                    if (adapter instanceof FragmentPagerAdapter) {

                        FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) adapter;

                        Fragment fragment = fragmentPagerAdapter.getItem(current);

                        if (fragment != null) {
                            mScrollView = fragment.getView();
                        }

                    } else {

                        mScrollView = mViewPager.getChildAt(current);
                    }

                }
            }

            if (mScrollView == null) {
                return false;
            }
            return isDependentOpen || canScrollDown(mScrollView);

        }

        return canScrollDown(mContentView);

    }

    private boolean canScrollDown(View view) {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (view instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) view;
                return absListView.getChildCount() > 0
                        && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1
                                || absListView.getChildAt(absListView.getChildCount() - 1).getBottom() > absListView
                                        .getPaddingBottom());
            } else {
                return ViewCompat.canScrollVertically(view, 1) || view.getScrollY() < 0;
            }
        } else {
            return ViewCompat.canScrollVertically(view, 1);
        }
    }

    /**
     * ?
     */
    public interface OnLoadMoreListener {
        void onLoadMore();
    }

    /**
     * ?
     */
    public interface OnRefreshListener {
        void onRefresh();
    }

    /**
     * ?
     */
    public interface OnStartUpListener {

        void onUp();

        void onReset();

    }

    /**
     * ?
     */
    public interface OnStartDownListener {

        void onDown();

        void onReset();

    }

}