SwipeRefreshLayout源码分析

来源:互联网 发布:淘宝有ego女装官网吗? 编辑:程序博客网 时间:2024/06/15 10:35
public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingParent,        NestedScrollingChild {    // 这两个变量决定下拉刷新的刷新图标的大小,一个标准图,一个大图    public static final int LARGE = MaterialProgressDrawable.LARGE;    public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;   ...    // 这个view 就是包裹的子view ,  通过这个view来判断是否达到下拉刷新的条件,这个view默认也是要可以滑动才行    private View mTarget; // the target of the gesture    private OnRefreshListener mListener;    private boolean mRefreshing = false;    private int mTouchSlop;    private float mTotalDragDistance = -1;   ...    //这个CircleImageView 就是下拉刷新做动画的控件    private CircleImageView mCircleView;    private int mCircleViewIndex = -1;    protected int mFrom;    private float mStartingScale;    protected int mOriginalOffsetTop;    //这个drawable是CircleImageView里面的图    private MaterialProgressDrawable mProgress;    private Animation mScaleAnimation;    private Animation mScaleDownAnimation;    private Animation mAlphaStartAnimation;    private Animation mAlphaMaxAnimation;    private Animation mScaleDownToStartAnimation;    private float mSpinnerFinalOffset;    private boolean mNotify;    private int mCircleWidth;    private int mCircleHeight;    // Whether the client has set a custom starting position;    private boolean mUsingCustomStart;    //非常重要的监听器,当动画结束的时候会回调刷新,这个onRefresh里面提供给客户端重载数据,然后再       setRefreshing(false),代表数据更新完毕    private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {        @Override        public void onAnimationStart(Animation animation) {        }        @Override        public void onAnimationRepeat(Animation animation) {        }        @Override        public void onAnimationEnd(Animation animation) {            if (mRefreshing) {                // Make sure the progress view is fully visible                mProgress.setAlpha(MAX_ALPHA);                mProgress.start();                if (mNotify) {                    if (mListener != null) {                        mListener.onRefresh();                    }                }                mCurrentTargetOffsetTop = mCircleView.getTop();            } else {                reset();            }        }    };    /**     * The refresh indicator starting and resting position is always positioned     * near the top of the refreshing content. This position is a consistent     * location, but can be adjusted in either direction based on whether or not     * there is a toolbar or actionbar present.     *     * @param scale Set to true if there is no view at a higher z-order than     *            where the progress spinner is set to appear.     * @param start The offset in pixels from the top of this view at which the     *            progress spinner should appear.     * @param end The offset in pixels from the top of this view at which the     *            progress spinner should come to rest after a successful swipe     *            gesture.     */    public void setProgressViewOffset(boolean scale, int start, int end) {        mScale = scale;        mCircleView.setVisibility(View.GONE);        mOriginalOffsetTop = mCurrentTargetOffsetTop = start;        mSpinnerFinalOffset = end;        mUsingCustomStart = true;        mCircleView.invalidate();    }    /**     * The refresh indicator resting position is always positioned near the top     * of the refreshing content. This position is a consistent location, but     * can be adjusted in either direction based on whether or not there is a     * toolbar or actionbar present.     *     * @param scale Set to true if there is no view at a higher z-order than     *            where the progress spinner is set to appear.     * @param end The offset in pixels from the top of this view at which the     *            progress spinner should come to rest after a successful swipe     *            gesture.     */    public void setProgressViewEndTarget(boolean scale, int end) {        mSpinnerFinalOffset = end;        mScale = scale;        mCircleView.invalidate();    }    private void createProgressView() {        mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2);        mProgress = new MaterialProgressDrawable(getContext(), this);        mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);        mCircleView.setImageDrawable(mProgress);        mCircleView.setVisibility(View.GONE);        addView(mCircleView);    }    /**     * Set the listener to be notified when a refresh is triggered via the swipe     * gesture.     */    public void setOnRefreshListener(OnRefreshListener listener) {        mListener = listener;    }    /**     * Notify the widget that refresh state has changed. Do not call this when     * refresh is triggered by a swipe gesture.     *     * @param refreshing Whether or not the view should show refresh progress.     */     //一般提供给客户端调用传false,结束下拉刷新    public void setRefreshing(boolean refreshing) {        if (refreshing && mRefreshing != refreshing) {            // scale and show            mRefreshing = refreshing;            int endTarget = 0;            if (!mUsingCustomStart) {                endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);            } else {                endTarget = (int) mSpinnerFinalOffset;            }            setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,                    true /* requires update */);            mNotify = false;            startScaleUpAnimation(mRefreshListener);        } else {            setRefreshing(refreshing, false /* notify */);        }    }    private void setRefreshing(boolean refreshing, final boolean notify) {        if (mRefreshing != refreshing) {            mNotify = notify;            ensureTarget();            mRefreshing = refreshing;            if (mRefreshing) {                animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);            } else {                startScaleDownAnimation(mRefreshListener);            }        }    }    private void startScaleDownAnimation(Animation.AnimationListener listener) {        mScaleDownAnimation = new Animation() {            @Override            public void applyTransformation(float interpolatedTime, Transformation t) {                setAnimationProgress(1 - interpolatedTime);            }        };        mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);        mCircleView.setAnimationListener(listener);        mCircleView.clearAnimation();        mCircleView.startAnimation(mScaleDownAnimation);    }    private void startProgressAlphaStartAnimation() {        mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);    }    private void startProgressAlphaMaxAnimation() {        mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);    }    private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) {        // Pre API 11, alpha is used in place of scale. Don't also use it to        // show the trigger point.        if (mScale && isAlphaUsedForScale()) {            return null;        }        Animation alpha = new Animation() {            @Override            public void applyTransformation(float interpolatedTime, Transformation t) {                mProgress                        .setAlpha((int) (startingAlpha+ ((endingAlpha - startingAlpha)                                * interpolatedTime)));            }        };        alpha.setDuration(ALPHA_ANIMATION_DURATION);        // Clear out the previous animation listeners.        mCircleView.setAnimationListener(null);        mCircleView.clearAnimation();        mCircleView.startAnimation(alpha);        return alpha;    }    /**     * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)}     */    @Deprecated    public void setProgressBackgroundColor(int colorRes) {        setProgressBackgroundColorSchemeResource(colorRes);    }    /**     * Set the background color of the progress spinner disc.     *     * @param colorRes Resource id of the color.     */    public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) {        setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes));    }    /**     * Set the background color of the progress spinner disc.     *     * @param color     */    public void setProgressBackgroundColorSchemeColor(@ColorInt int color) {        mCircleView.setBackgroundColor(color);        mProgress.setBackgroundColor(color);    }    /**     * @deprecated Use {@link #setColorSchemeResources(int...)}     */    @Deprecated    public void setColorScheme(@ColorInt int... colors) {        setColorSchemeResources(colors);    }    /**     * Set the color resources used in the progress animation from color resources.     * The first color will also be the color of the bar that grows in response     * to a user swipe gesture.     *     * @param colorResIds     */     //从解析可以得知,通过这个方法参数可以传递一系列的颜色资源,保存在colorRes[]里面    public void setColorSchemeResources(@ColorRes int... colorResIds) {        final Resources res = getResources();        int[] colorRes = new int[colorResIds.length];        for (int i = 0; i < colorResIds.length; i++) {            colorRes[i] = res.getColor(colorResIds[i]);        }        //通过下面这个方法设置颜色        setColorSchemeColors(colorRes);    }    /**     * Set the colors used in the progress animation. The first     * color will also be the color of the bar that grows in response to a user     * swipe gesture.     *     * @param colors     */    public void setColorSchemeColors(@ColorInt int... colors) {        //设置目标target,也就是子view        ensureTarget();        //这个颜色资源数组最后设置到mProgress上        mProgress.setColorSchemeColors(colors);    }    /**     * @return Whether the SwipeRefreshWidget is actively showing refresh     *         progress.     */     //这个方法判断是否正在刷新    public boolean isRefreshing() {        return mRefreshing;    }    //比较重要的方法,遍历所有child,如果发现child不是mCircleView(下拉做动画的控件),就设置mTarget     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(mCircleView)) {                    mTarget = child;                    break;                }            }        }    }    /**     * @return Whether it is possible for the child view of this layout to     *         scroll up. Override this if the child view is a custom view.     */    public 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 ViewCompat.canScrollVertically(mTarget, -1);        }    }    @Override    //通过这个方法,看什么时候需要打断touch时间,自己消耗这个touch,这个控件是下拉刷新,那么按道理是滑动到顶部的时候,下拉要打断操作,其余默认交给子view    public boolean onInterceptTouchEvent(MotionEvent ev) {        ensureTarget();        final int action = MotionEventCompat.getActionMasked(ev);        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {            mReturningToStart = false;        }        //主要通过这几个方法和变量确定        //1.isEnabled 代表SwipeRefreshLayout是否启用        //2.canChildScrollUp()表明是否还能继续向上划        //3.mRefreshing 代表当前状态,mRefreshing = false ,代表没有在刷新        //4.mNestedScrollInProgress代表滚动状态        //5.mReturningToStart暂时看不到有设置为true的情况,有待查看        if (!isEnabled() || mReturningToStart || canChildScrollUp()                || mRefreshing || mNestedScrollInProgress) {            // Fail fast if we're not in a state where a swipe is possible            return false;        }        switch (action) {            case MotionEvent.ACTION_DOWN:                setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);                //先在ACTION_DOWN时,设置mIsBeingDragged ,代表还未算是拖拽状态                mIsBeingDragged = false;                final float initialDownY = getMotionEventY(ev, mActivePointerId);                if (initialDownY == -1) {                    return false;                }                mInitialDownY = initialDownY;                break;            case MotionEvent.ACTION_MOVE:                if (mActivePointerId == INVALID_POINTER) {                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");                    return false;                }                final float y = getMotionEventY(ev, mActivePointerId);                if (y == -1) {                    return false;                }                final float yDiff = y - mInitialDownY;                //判断距离,大于mTouchSlop ,拖拽成立,mIsBeingDragged = true,代表打断touch事件                //传递到当前的onTouchEvent()方法中                if (yDiff > mTouchSlop && !mIsBeingDragged) {                    mInitialMotionY = mInitialDownY + mTouchSlop;                    mIsBeingDragged = true;                    mProgress.setAlpha(STARTING_PROGRESS_ALPHA);                }                break;            case MotionEventCompat.ACTION_POINTER_UP:                onSecondaryPointerUp(ev);                break;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                mIsBeingDragged = false;                mActivePointerId = INVALID_POINTER;                break;        }        return mIsBeingDragged;    }    private void moveSpinner(float overscrollTop) {        mProgress.showArrow(true);        float originalDragPercent = overscrollTop / mTotalDragDistance;        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));        float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;        float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;        float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop                : mSpinnerFinalOffset;        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;        int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);        // where 1.0f is a full circle        if (mCircleView.getVisibility() != View.VISIBLE) {            mCircleView.setVisibility(View.VISIBLE);        }        if (!mScale) {            ViewCompat.setScaleX(mCircleView, 1f);            ViewCompat.setScaleY(mCircleView, 1f);        }        if (mScale) {            setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));        }        if (overscrollTop < mTotalDragDistance) {            if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA                    && !isAnimationRunning(mAlphaStartAnimation)) {                // Animate the alpha                startProgressAlphaStartAnimation();            }        } else {            if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {                // Animate the alpha                startProgressAlphaMaxAnimation();            }        }        float strokeStart = adjustedPercent * .8f;        mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));        mProgress.setArrowScale(Math.min(1f, adjustedPercent));        float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;        mProgress.setProgressRotation(rotation);        setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);    }    //若果当前距离超过mTotalDragDistance,代表刷新状态成立,开始刷新    //若果距离不够,,做了一个动画回滚回去    private void finishSpinner(float overscrollTop) {        if (overscrollTop > mTotalDragDistance) {            setRefreshing(true, true /* notify */);        } else {            // cancel refresh            mRefreshing = false;            mProgress.setStartEndTrim(0f, 0f);            Animation.AnimationListener listener = null;            if (!mScale) {                listener = new Animation.AnimationListener() {                    @Override                    public void onAnimationStart(Animation animation) {                    }                    @Override                    public void onAnimationEnd(Animation animation) {                        if (!mScale) {                            startScaleDownAnimation(null);                        }                    }                    @Override                    public void onAnimationRepeat(Animation animation) {                    }                };            }            animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);            mProgress.showArrow(false);        }    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        final int action = MotionEventCompat.getActionMasked(ev);        int pointerIndex = -1;        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {            mReturningToStart = false;        }        if (!isEnabled() || mReturningToStart || canChildScrollUp() || mNestedScrollInProgress) {            // Fail fast if we're not in a state where a swipe is possible            return false;        }        switch (action) {            case MotionEvent.ACTION_DOWN:                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);                mIsBeingDragged = false;                break;            case MotionEvent.ACTION_MOVE: {                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);                if (pointerIndex < 0) {                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");                    return false;                }                final float y = MotionEventCompat.getY(ev, pointerIndex);                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;                if (mIsBeingDragged) {                    if (overscrollTop > 0) {                        //根据当前拖动的距离,调整下拉刷新控件的位置                        moveSpinner(overscrollTop);                    } else {                        return false;                    }                }                break;            }            case MotionEvent.ACTION_UP: {                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);                if (pointerIndex < 0) {                    Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");                    return false;                }                final float y = MotionEventCompat.getY(ev, pointerIndex);                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;                mIsBeingDragged = false;                //根据拖拽的距离判断是否回滚还是刷新子view                finishSpinner(overscrollTop);                mActivePointerId = INVALID_POINTER;                return false;            }            case MotionEvent.ACTION_CANCEL:                return false;        }        return true;    }    /**     * Classes that wish to be notified when the swipe gesture correctly     * triggers a refresh should implement this interface.     */    public interface OnRefreshListener {        public void onRefresh();    }}
0 0