admin管理员组文章数量:1516870
Android SwipeRefreshAndLoadLayout,下拉刷新,上拉更多,使用SwipeRefreshLayout自带的Progress
最近使用Google的SwipeRefreshLayout控件,感觉下拉的那个Progress很炫酷,效果很不错,就是没有上拉更多的效果,不开心....随后便在网上四处找,看看有没有哪位大神加上了,直接用. 结果不理想,基本都是监听列表滑动,然后手动做的,感觉不太好,然后就自己捣鼓了一下,只在SwipeRefreshLayout的源码中,稍微改动了一下,效果还不错,喜欢的朋友可以看下,
以下是SwipeRefreshAndLoadLayout的代码:
package com.hzncc.kevin.swipelayout;import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.ContextCompat;
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.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.Toast;public class SwipeRefreshAndLoadLayout extends ViewGroup implements NestedScrollingParent,NestedScrollingChild {// Maps to ProgressBar.Large stylepublic static final int LARGE = MaterialProgressDrawable.LARGE;// Maps to ProgressBar default stylepublic static final int DEFAULT = MaterialProgressDrawable.DEFAULT;@VisibleForTestingstatic final int CIRCLE_DIAMETER = 40;@VisibleForTestingstatic final int CIRCLE_DIAMETER_LARGE = 56;private static final String LOG_TAG = SwipeRefreshAndLoadLayout.class.getSimpleName();private static final int MAX_ALPHA = 255;private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;private static final int INVALID_POINTER = -1;private static final float DRAG_RATE = .5f;// Max amount of circle that can be filled by progress during swipe gesture,// where 1.0 is a full circleprivate static final float MAX_PROGRESS_ANGLE = .8f;private static final int SCALE_DOWN_DURATION = 150;private static final int ALPHA_ANIMATION_DURATION = 300;private static final int ANIMATE_TO_TRIGGER_DURATION = 200;private static final int ANIMATE_TO_START_DURATION = 200;// Default background for the progress spinnerprivate static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;// Default offset in dips from the top of the view to where the progress spinner should stopprivate static final int DEFAULT_CIRCLE_TARGET = 64;private static final int[] LAYOUT_ATTRS = new int[]{android.R.attr.enabled};private final NestedScrollingParentHelper mNestedScrollingParentHelper;private final NestedScrollingChildHelper mNestedScrollingChildHelper;private final int[] mParentScrollConsumed = new int[2];private final int[] mParentOffsetInWindow = new int[2];private final DecelerateInterpolator mDecelerateInterpolator;protected int mFrom;protected int mOriginalOffsetTop;SwipeRefreshAndLoadLayout.OnRefreshListener mListener;boolean mRefreshing = false;int mCurrentTargetOffsetTop;// Whether this item is scaled up rather than clippedboolean mScale;CircleImageView mCircleView;private final Animation mAnimateToStartPosition = new Animation() {@Overridepublic void applyTransformation(float interpolatedTime, Transformation t) {moveToStart(interpolatedTime);}};float mStartingScale;int mSpinnerOffsetEnd;MaterialProgressDrawable mProgress;boolean mNotify;// Whether the client has set a custom starting position;boolean mUsingCustomStart;private final Animation mAnimateToCorrectPosition = new Animation() {@Overridepublic void applyTransformation(float interpolatedTime, Transformation t) {int targetTop = 0;int endTarget = 0;if (!mUsingCustomStart) {endTarget = mSpinnerOffsetEnd - Math.abs(mOriginalOffsetTop);} else {endTarget = mSpinnerOffsetEnd;}targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));int offset = targetTop - mCircleView.getTop();setTargetOffsetTopAndBottom(offset);mProgress.setArrowScale(1 - interpolatedTime);}};private View mTarget; // the target of the gestureprivate int mTouchSlop;private float mTotalDragDistance = -1;// If nested scrolling is enabled, the total amount that needed to be// consumed by this as the nested scrolling parent is used in place of the// overscroll determined by MOVE events in the onTouch handlerprivate float mTotalUnconsumed;private boolean mNestedScrollInProgress;private int mMediumAnimationDuration;private float mInitialMotionY;private float mInitialDownY;private boolean mIsBeingDragged;private int mActivePointerId = INVALID_POINTER;// Target is returning to its start offset because it was cancelled or a// refresh was triggered.private boolean mReturningToStart;private int mCircleViewIndex = -1;private Animation mScaleAnimation;private Animation mScaleDownAnimation;private Animation mAlphaStartAnimation;private Animation mAlphaMaxAnimation;private Animation mScaleDownToStartAnimation;private int mCircleDiameter;private SwipeRefreshAndLoadLayout.OnChildScrollUpCallback mChildScrollUpCallback;private boolean isLoadMore = false;private int currPage = 1;private int totalPages = -1;private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationRepeat(Animation animation) {}@SuppressLint("NewApi")@Overridepublic void onAnimationEnd(Animation animation) {if (mRefreshing) {// Make sure the progress view is fully visiblemProgress.setAlpha(MAX_ALPHA);mProgress.start();if (mNotify) {if (mListener != null) {if (isLoadMore) {if (totalPages > 0 && currPage >= totalPages) {Toast.makeText(getContext(), "没有更多了!", Toast.LENGTH_SHORT).show();setRefreshing(false);} else {currPage++;mListener.onLoadMore(currPage, totalPages);}} else {currPage = 1;mListener.onRefresh();}}}mCurrentTargetOffsetTop = mCircleView.getTop();} else {reset();}}};/*** Simple constructor to use when creating a SwipeRefreshLayout from code.** @param context*/public SwipeRefreshAndLoadLayout(Context context) {this(context, null);}/*** Constructor that is called when inflating SwipeRefreshLayout from XML.** @param context* @param attrs*/public SwipeRefreshAndLoadLayout(Context context, AttributeSet attrs) {super(context, attrs);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();mMediumAnimationDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime);setWillNotDraw(false);mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);final DisplayMetrics metrics = getResources().getDisplayMetrics();mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);createProgressView();ViewCompat.setChildrenDrawingOrderEnabled(this, true);// the absolute offset has to take into account that the circle starts at an offsetmSpinnerOffsetEnd = (int) (DEFAULT_CIRCLE_TARGET * metrics.density);mTotalDragDistance = mSpinnerOffsetEnd;mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);setNestedScrollingEnabled(true);mOriginalOffsetTop = mCurrentTargetOffsetTop = -mCircleDiameter;moveToStart(1.0f);final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);setEnabled(a.getBoolean(0, true));a.recycle();}public int getCurrPage() {return currPage;}public void setCurrPage(int currPage) {this.currPage = currPage;}public int getTotalPages() {return totalPages;}public void setTotalPages(int totalPages) {this.totalPages = totalPages;}void reset() {mCircleView.clearAnimation();mProgress.stop();mCircleView.setVisibility(View.GONE);setColorViewAlpha(MAX_ALPHA);// Return the circle to its start positionif (mScale) {setAnimationProgress(0 /* animation complete and view is hidden */);} else {setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop);}mCurrentTargetOffsetTop = mCircleView.getTop();}@Overridepublic void setEnabled(boolean enabled) {super.setEnabled(enabled);if (!enabled) {reset();}}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();reset();}@SuppressLint("NewApi")private void setColorViewAlpha(int targetAlpha) {mCircleView.getBackground().setAlpha(targetAlpha);mProgress.setAlpha(targetAlpha);}/*** 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.* <p>* <strong>Note:</strong> Calling this will reset the position of the refresh indicator to* <code>start</code>.* </p>** @param scale Set to true if there is no view at a higher z-order than where the progress* spinner is set to appear. Setting it to true will cause indicator to be scaled* up rather than clipped.* @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;mOriginalOffsetTop = start;mSpinnerOffsetEnd = end;mUsingCustomStart = true;reset();mRefreshing = false;}/*** @return The offset in pixels from the top of this view at which the progress spinner should* appear.*/public int getProgressViewStartOffset() {return mOriginalOffsetTop;}/*** @return 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 int getProgressViewEndOffset() {return mSpinnerOffsetEnd;}/*** 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. Setting it to true will cause indicator to be scaled* up rather than clipped.* @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) {mSpinnerOffsetEnd = end;mScale = scale;mCircleView.invalidate();}/*** One of DEFAULT, or LARGE.*/public void setSize(int size) {if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {return;}final DisplayMetrics metrics = getResources().getDisplayMetrics();if (size == MaterialProgressDrawable.LARGE) {mCircleDiameter = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);} else {mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);}// force the bounds of the progress circle inside the circle view to// update by setting it to null before updating its size and then// re-setting itmCircleView.setImageDrawable(null);mProgress.updateSizes(size);mCircleView.setImageDrawable(mProgress);}@Overrideprotected int getChildDrawingOrder(int childCount, int i) {if (mCircleViewIndex < 0) {return i;} else if (i == childCount - 1) {// Draw the selected child lastreturn mCircleViewIndex;} else if (i >= mCircleViewIndex) {// Move the children after the selected child earlier onereturn i + 1;} else {// Keep the children before the selected child the samereturn i;}}private void createProgressView() {mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT);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(SwipeRefreshAndLoadLayout.OnRefreshListener listener) {mListener = listener;}@SuppressLint("NewApi")private void startScaleUpAnimation(Animation.AnimationListener listener) {mCircleView.setVisibility(View.VISIBLE);if (android.os.Build.VERSION.SDK_INT >= 11) {// Pre API 11, alpha is used in place of scale up to show the// progress circle appearing.// Don't adjust the alpha during appearance otherwise.mProgress.setAlpha(MAX_ALPHA);}mScaleAnimation = new Animation() {@Overridepublic void applyTransformation(float interpolatedTime, Transformation t) {setAnimationProgress(interpolatedTime);}};mScaleAnimation.setDuration(mMediumAnimationDuration);if (listener != null) {mCircleView.setAnimationListener(listener);}mCircleView.clearAnimation();mCircleView.startAnimation(mScaleAnimation);}/*** Pre API 11, this does an alpha animation.** @param progress*/void setAnimationProgress(float progress) {mCircleView.setScaleX(progress);mCircleView.setScaleY(progress);}private void setRefreshing(boolean refreshing, final boolean notify) {if (mRefreshing != refreshing) {mNotify = notify;ensureTarget();mRefreshing = refreshing;if (mRefreshing) {animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);} else {startScaleDownAnimation(mRefreshListener);}}}void startScaleDownAnimation(Animation.AnimationListener listener) {mScaleDownAnimation = new Animation() {@Overridepublic 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) {Animation alpha = new Animation() {@Overridepublic 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)}*/@Deprecatedpublic 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(ContextCompat.getColor(getContext(), colorRes));}/*** Set the background color of the progress spinner disc.** @param color*/public void setProgressBackgroundColorSchemeColor(@ColorInt int color) {mCircleView.setBackgroundColor(color);mProgress.setBackgroundColor(color);}/*** 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*/public void setColorSchemeResources(@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]);}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) {ensureTarget();mProgress.setColorSchemeColors(colors);}/*** @return Whether the SwipeRefreshWidget is actively showing refresh* progress.*/public boolean isRefreshing() {return mRefreshing;}/*** 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.*/public void setRefreshing(boolean refreshing) {if (refreshing && mRefreshing != refreshing) {// scale and showmRefreshing = refreshing;int endTarget = 0;if (!mUsingCustomStart) {endTarget = mSpinnerOffsetEnd + mOriginalOffsetTop;} else {endTarget = mSpinnerOffsetEnd;}setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop);mNotify = false;startScaleUpAnimation(mRefreshListener);} else {setRefreshing(refreshing, false /* notify */);}}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;}}}}/*** Set the distance to trigger a sync in dips** @param distance*/public void setDistanceToTriggerSync(int distance) {mTotalDragDistance = distance;}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {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 childTop = getPaddingTop();final int childWidth = width - getPaddingLeft() - getPaddingRight();final int childHeight = height - getPaddingTop() - getPaddingBottom();child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);int circleWidth = mCircleView.getMeasuredWidth();int circleHeight = mCircleView.getMeasuredHeight();mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,(width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);}@Overridepublic 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));mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY));mCircleViewIndex = -1;// Get the index of the circleview.for (int index = 0; index < getChildCount(); index++) {if (getChildAt(index) == mCircleView) {mCircleViewIndex = index;break;}}}/*** Get the diameter of the progress circle that is displayed as part of the* swipe to refresh layout.** @return Diameter in pixels of the progress circle view.*/public int getProgressCircleDiameter() {return mCircleDiameter;}/*** @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() {boolean rel;if (mChildScrollUpCallback != null) {rel = mChildScrollUpCallback.canChildScrollUp(this, mTarget);} else {rel = ViewCompat.canScrollVertically(mTarget, -1);}isLoadMore = rel;return rel;}public boolean canChildScrollDown() {boolean rel;if (mChildScrollUpCallback != null) {rel = mChildScrollUpCallback.canChildScrollDown(this, mTarget);} else {rel = ViewCompat.canScrollVertically(mTarget, 1);}isLoadMore = !rel;return rel;}/*** Set a callback to override {@link SwipeRefreshAndLoadLayout#canChildScrollUp()} method. Non-null* callback will return the value provided by the callback and ignore all internal logic.** @param callback Callback that should be called when canChildScrollUp() is called.*/public void setOnChildScrollUpCallback(@Nullable SwipeRefreshAndLoadLayout.OnChildScrollUpCallback callback) {mChildScrollUpCallback = callback;}// NestedScrollingParent@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {ensureTarget();final int action = ev.getActionMasked();int pointerIndex;if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {mReturningToStart = false;}if (!isEnabled() || mReturningToStart || (canChildScrollUp() && canChildScrollDown())|| mRefreshing || mNestedScrollInProgress) {// Fail fast if we're not in a state where a swipe is possiblereturn false;}switch (action) {case MotionEvent.ACTION_DOWN:setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop());mActivePointerId = ev.getPointerId(0);mIsBeingDragged = false;pointerIndex = ev.findPointerIndex(mActivePointerId);if (pointerIndex < 0) {return false;}mInitialDownY = ev.getY(pointerIndex);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;}pointerIndex = ev.findPointerIndex(mActivePointerId);if (pointerIndex < 0) {return false;}final float y = ev.getY(pointerIndex);startDragging(y);break;case MotionEvent.ACTION_POINTER_UP:onSecondaryPointerUp(ev);break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:mIsBeingDragged = false;mActivePointerId = INVALID_POINTER;break;}return mIsBeingDragged;}@Overridepublic 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 stolenif ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {// Nope.} else {super.requestDisallowInterceptTouchEvent(b);}}@Overridepublic boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {return isEnabled() && !mReturningToStart && !mRefreshing&& (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;}@Overridepublic 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 parentstartNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);mTotalUnconsumed = 0;mNestedScrollInProgress = true;}@Overridepublic 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 scrollif (dy < 0 && mTotalUnconsumed > 0) {if (Math.abs(dy) > mTotalUnconsumed) {consumed[1] = Math.abs(dy) - (int) mTotalUnconsumed;mTotalUnconsumed = 0;} else {mTotalUnconsumed -= Math.abs(dy);consumed[1] = Math.abs(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 movedif (mUsingCustomStart && dy != 0 && mTotalUnconsumed == 0&& Math.abs(Math.abs(dy) - consumed[1]) > 0) {mCircleView.setVisibility(View.GONE);}// Now let our nested parent consume the leftoversfinal int[] parentConsumed = mParentScrollConsumed;if (dispatchNestedPreScroll(dx - consumed[0], Math.abs(dy) - consumed[1], parentConsumed, null)) {consumed[0] += parentConsumed[0];consumed[1] += parentConsumed[1];}}@Overridepublic int getNestedScrollAxes() {return mNestedScrollingParentHelper.getNestedScrollAxes();}// NestedScrollingChild@Overridepublic void onStopNestedScroll(View target) {mNestedScrollingParentHelper.onStopNestedScroll(target);mNestedScrollInProgress = false;// Finish the spinner for nested scrolling if we ever consumed any// unconsumed nested scrollif (mTotalUnconsumed > 0) {finishSpinner(mTotalUnconsumed);mTotalUnconsumed = 0;}// Dispatch up our nested parentstopNestedScroll();}@Overridepublic void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,final int dxUnconsumed, final int dyUnconsumed) {// Dispatch up to the nested parent firstdispatchNestedScroll(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);} else if (dy > 0 && !canChildScrollDown()) {mTotalUnconsumed += Math.abs(dy);moveSpinner(mTotalUnconsumed);}}@Overridepublic boolean isNestedScrollingEnabled() {return mNestedScrollingChildHelper.isNestedScrollingEnabled();}@Overridepublic void setNestedScrollingEnabled(boolean enabled) {mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);}@Overridepublic boolean startNestedScroll(int axes) {return mNestedScrollingChildHelper.startNestedScroll(axes);}@Overridepublic void stopNestedScroll() {mNestedScrollingChildHelper.stopNestedScroll();}@Overridepublic boolean hasNestedScrollingParent() {return mNestedScrollingChildHelper.hasNestedScrollingParent();}@Overridepublic boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,int dyUnconsumed, int[] offsetInWindow) {return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,dxUnconsumed, dyUnconsumed, offsetInWindow);}@Overridepublic boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);}@Overridepublic boolean onNestedPreFling(View target, float velocityX,float velocityY) {return dispatchNestedPreFling(velocityX, velocityY);}@Overridepublic boolean onNestedFling(View target, float velocityX, float velocityY,boolean consumed) {return dispatchNestedFling(velocityX, velocityY, consumed);}@Overridepublic boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);}@Overridepublic boolean dispatchNestedPreFling(float velocityX, float velocityY) {return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);}private boolean isAnimationRunning(Animation animation) {return animation != null && animation.hasStarted() && !animation.hasEnded();}@SuppressLint("NewApi")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 ? 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;int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);// where 1.0f is a full circleif (mCircleView.getVisibility() != View.VISIBLE) {mCircleView.setVisibility(View.VISIBLE);}if (!mScale) {mCircleView.setScaleX(1f);mCircleView.setScaleY(1f);}if (mScale) {setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));}if (overscrollTop < mTotalDragDistance) {if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA&& !isAnimationRunning(mAlphaStartAnimation)) {// Animate the alphastartProgressAlphaStartAnimation();}} else {if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {// Animate the alphastartProgressAlphaMaxAnimation();}}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);}private void finishSpinner(float overscrollTop) {if (overscrollTop > mTotalDragDistance) {setRefreshing(true, true /* notify */);} else {// cancel refreshmRefreshing = false;mProgress.setStartEndTrim(0f, 0f);Animation.AnimationListener listener = null;if (!mScale) {listener = new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {if (!mScale) {startScaleDownAnimation(null);}}@Overridepublic void onAnimationRepeat(Animation animation) {}};}animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);mProgress.showArrow(false);}}@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getActionMasked();int pointerIndex = -1;if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {mReturningToStart = false;}if (!isEnabled() || mReturningToStart || canChildScrollUp()|| mRefreshing || mNestedScrollInProgress) {// Fail fast if we're not in a state where a swipe is possiblereturn false;}switch (action) {case MotionEvent.ACTION_DOWN:mActivePointerId = ev.getPointerId(0);mIsBeingDragged = false;break;case MotionEvent.ACTION_MOVE: {pointerIndex = ev.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 = ev.getY(pointerIndex);startDragging(y);if (mIsBeingDragged) {final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;if (overscrollTop > 0) {moveSpinner(overscrollTop);} else {return false;}}break;}case MotionEvent.ACTION_POINTER_DOWN: {pointerIndex = ev.getActionIndex();if (pointerIndex < 0) {Log.e(LOG_TAG,"Got ACTION_POINTER_DOWN event but have an invalid action index.");return false;}mActivePointerId = ev.getPointerId(pointerIndex);break;}case MotionEvent.ACTION_POINTER_UP:onSecondaryPointerUp(ev);break;case MotionEvent.ACTION_UP: {pointerIndex = ev.findPointerIndex(mActivePointerId);if (pointerIndex < 0) {Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");return false;}if (mIsBeingDragged) {final float y = ev.getY(pointerIndex);final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;mIsBeingDragged = false;finishSpinner(overscrollTop);}mActivePointerId = INVALID_POINTER;return false;}case MotionEvent.ACTION_CANCEL:return false;}return true;}@SuppressLint("NewApi")private void startDragging(float y) {final float yDiff = y - mInitialDownY;if (yDiff > mTouchSlop && !mIsBeingDragged) {mInitialMotionY = mInitialDownY + mTouchSlop;mIsBeingDragged = true;mProgress.setAlpha(STARTING_PROGRESS_ALPHA);}}private void animateOffsetToCorrectPosition(int from, Animation.AnimationListener listener) {mFrom = from;mAnimateToCorrectPosition.reset();mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);if (listener != null) {mCircleView.setAnimationListener(listener);}mCircleView.clearAnimation();mCircleView.startAnimation(mAnimateToCorrectPosition);}private void animateOffsetToStartPosition(int from, Animation.AnimationListener listener) {if (mScale) {// Scale the item back downstartScaleDownReturnToStartAnimation(from, listener);} else {mFrom = from;mAnimateToStartPosition.reset();mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);if (listener != null) {mCircleView.setAnimationListener(listener);}mCircleView.clearAnimation();mCircleView.startAnimation(mAnimateToStartPosition);}}void moveToStart(float interpolatedTime) {int targetTop = 0;targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));int offset = targetTop - mCircleView.getTop();setTargetOffsetTopAndBottom(offset);}@SuppressLint("NewApi")private void startScaleDownReturnToStartAnimation(int from,Animation.AnimationListener listener) {mFrom = from;mStartingScale = mCircleView.getScaleX();mScaleDownToStartAnimation = new Animation() {@Overridepublic void applyTransformation(float interpolatedTime, Transformation t) {float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime));setAnimationProgress(targetScale);moveToStart(interpolatedTime);}};mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);if (listener != null) {mCircleView.setAnimationListener(listener);}mCircleView.clearAnimation();mCircleView.startAnimation(mScaleDownToStartAnimation);}void setTargetOffsetTopAndBottom(int offset) {mCircleView.bringToFront();ViewCompat.offsetTopAndBottom(mCircleView, offset);mCurrentTargetOffsetTop = mCircleView.getTop();}private void onSecondaryPointerUp(MotionEvent ev) {final int pointerIndex = ev.getActionIndex();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);}}/*** Classes that wish to be notified when the swipe gesture correctly* triggers a refresh should implement this interface.*/public interface OnRefreshListener {/*** Called when a swipe gesture triggers a refresh.*/void onRefresh();void onLoadMore(int currPage, int totalPages);}/*** Classes that wish to override {@link SwipeRefreshAndLoadLayout#canChildScrollUp()} method* behavior should implement this interface.*/public interface OnChildScrollUpCallback {/*** Callback that will be called when {@link SwipeRefreshAndLoadLayout#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(SwipeRefreshAndLoadLayout parent, @Nullable View child);boolean canChildScrollDown(SwipeRefreshAndLoadLayout parent, @Nullable View child);}
}
注意:这个类的大部分代码都是直接从SwipeRefreshLayout中拷贝过来的,所以需要另外两个类的,这两个类是v4包中的控件,但是是private的,所以用不了,
我自己拷贝出来了,分别是MaterialProgressDrawable和CircleImageView;
这里是MaterialProgressDrawable.java
/** Copyright (C) 2014 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** .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.hzncc.kevin.swipelayout;import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;/*** Fancy progress indicator for Material theme.*/
class MaterialProgressDrawable extends Drawable implements Animatable {private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();private static final float FULL_ROTATION = 1080.0f;@Retention(RetentionPolicy.SOURCE)@IntDef({LARGE, DEFAULT})public @interface ProgressDrawableSize {}// Maps to ProgressBar.Large stylestatic final int LARGE = 0;// Maps to ProgressBar default stylestatic final int DEFAULT = 1;// Maps to ProgressBar default styleprivate static final int CIRCLE_DIAMETER = 40;private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_widthprivate static final float STROKE_WIDTH = 2.5f;// Maps to ProgressBar.Large styleprivate static final int CIRCLE_DIAMETER_LARGE = 56;private static final float CENTER_RADIUS_LARGE = 12.5f;private static final float STROKE_WIDTH_LARGE = 3f;private static final int[] COLORS = new int[] {Color.BLACK};/*** The value in the linear interpolator for animating the drawable at which* the color transition should start*/private static final float COLOR_START_DELAY_OFFSET = 0.75f;private static final float END_TRIM_START_DELAY_OFFSET = 0.5f;private static final float START_TRIM_DURATION_OFFSET = 0.5f;/** The duration of a single progress spin in milliseconds. */private static final int ANIMATION_DURATION = 1332;/** The number of points in the progress "star". */private static final float NUM_POINTS = 5f;/** The list of animators operating on this drawable. */private final ArrayList<Animation> mAnimators = new ArrayList<Animation>();/** The indicator ring, used to manage animation state. */private final Ring mRing;/** Canvas rotation in degrees. */private float mRotation;/** Layout info for the arrowhead in dp */private static final int ARROW_WIDTH = 10;private static final int ARROW_HEIGHT = 5;private static final float ARROW_OFFSET_ANGLE = 5;/** Layout info for the arrowhead for the large spinner in dp */private static final int ARROW_WIDTH_LARGE = 12;private static final int ARROW_HEIGHT_LARGE = 6;private static final float MAX_PROGRESS_ARC = .8f;private Resources mResources;private View mParent;private Animation mAnimation;float mRotationCount;private double mWidth;private double mHeight;boolean mFinishing;MaterialProgressDrawable(Context context, View parent) {mParent = parent;mResources = context.getResources();mRing = new Ring(mCallback);mRing.setColors(COLORS);updateSizes(DEFAULT);setupAnimators();}private void setSizeParameters(double progressCircleWidth, double progressCircleHeight,double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {final Ring ring = mRing;final DisplayMetrics metrics = mResources.getDisplayMetrics();final float screenDensity = metrics.density;mWidth = progressCircleWidth * screenDensity;mHeight = progressCircleHeight * screenDensity;ring.setStrokeWidth((float) strokeWidth * screenDensity);ring.setCenterRadius(centerRadius * screenDensity);ring.setColorIndex(0);ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);ring.setInsets((int) mWidth, (int) mHeight);}/*** Set the overall size for the progress spinner. This updates the radius* and stroke width of the ring.** @param size One of {@link MaterialProgressDrawable.LARGE} or* {@link MaterialProgressDrawable.DEFAULT}*/public void updateSizes(@ProgressDrawableSize int size) {if (size == LARGE) {setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE,STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE);} else {setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH,ARROW_WIDTH, ARROW_HEIGHT);}}/*** @param show Set to true to display the arrowhead on the progress spinner.*/public void showArrow(boolean show) {mRing.setShowArrow(show);}/*** @param scale Set the scale of the arrowhead for the spinner.*/public void setArrowScale(float scale) {mRing.setArrowScale(scale);}/*** Set the start and end trim for the progress spinner arc.** @param startAngle start angle* @param endAngle end angle*/public void setStartEndTrim(float startAngle, float endAngle) {mRing.setStartTrim(startAngle);mRing.setEndTrim(endAngle);}/*** Set the amount of rotation to apply to the progress spinner.** @param rotation Rotation is from [0..1]*/public void setProgressRotation(float rotation) {mRing.setRotation(rotation);}/*** Update the background color of the circle image view.*/public void setBackgroundColor(int color) {mRing.setBackgroundColor(color);}/*** Set the colors 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 colors*/public void setColorSchemeColors(int... colors) {mRing.setColors(colors);mRing.setColorIndex(0);}@Overridepublic int getIntrinsicHeight() {return (int) mHeight;}@Overridepublic int getIntrinsicWidth() {return (int) mWidth;}@Overridepublic void draw(Canvas c) {final Rect bounds = getBounds();final int saveCount = c.save();c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());mRing.draw(c, bounds);c.restoreToCount(saveCount);}@Overridepublic void setAlpha(int alpha) {mRing.setAlpha(alpha);}@Overridepublic int getAlpha() {return mRing.getAlpha();}@Overridepublic void setColorFilter(ColorFilter colorFilter) {mRing.setColorFilter(colorFilter);}@SuppressWarnings("unused")void setRotation(float rotation) {mRotation = rotation;invalidateSelf();}@SuppressWarnings("unused")private float getRotation() {return mRotation;}@Overridepublic int getOpacity() {return PixelFormat.TRANSLUCENT;}@Overridepublic boolean isRunning() {final ArrayList<Animation> animators = mAnimators;final int N = animators.size();for (int i = 0; i < N; i++) {final Animation animator = animators.get(i);if (animator.hasStarted() && !animator.hasEnded()) {return true;}}return false;}@Overridepublic void start() {mAnimation.reset();mRing.storeOriginals();// Already showing some part of the ringif (mRing.getEndTrim() != mRing.getStartTrim()) {mFinishing = true;mAnimation.setDuration(ANIMATION_DURATION / 2);mParent.startAnimation(mAnimation);} else {mRing.setColorIndex(0);mRing.resetOriginals();mAnimation.setDuration(ANIMATION_DURATION);mParent.startAnimation(mAnimation);}}@Overridepublic void stop() {mParent.clearAnimation();setRotation(0);mRing.setShowArrow(false);mRing.setColorIndex(0);mRing.resetOriginals();}float getMinProgressArc(Ring ring) {return (float) Math.toRadians(ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));}// Adapted from ArgbEvaluator.javaprivate int evaluateColorChange(float fraction, int startValue, int endValue) {int startInt = (Integer) startValue;int startA = (startInt >> 24) & 0xff;int startR = (startInt >> 16) & 0xff;int startG = (startInt >> 8) & 0xff;int startB = startInt & 0xff;int endInt = (Integer) endValue;int endA = (endInt >> 24) & 0xff;int endR = (endInt >> 16) & 0xff;int endG = (endInt >> 8) & 0xff;int endB = endInt & 0xff;return (int) ((startA + (int) (fraction * (endA - startA))) << 24)| (int) ((startR + (int) (fraction * (endR - startR))) << 16)| (int) ((startG + (int) (fraction * (endG - startG))) << 8)| (int) ((startB + (int) (fraction * (endB - startB))));}/*** Update the ring color if this is within the last 25% of the animation.* The new ring color will be a translation from the starting ring color to* the next color.*/void updateRingColor(float interpolatedTime, Ring ring) {if (interpolatedTime > COLOR_START_DELAY_OFFSET) {// scale the interpolatedTime so that the full// transformation from 0 - 1 takes place in the// remaining timering.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)/ (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(),ring.getNextColor()));}}void applyFinishTranslation(float interpolatedTime, Ring ring) {// shrink back down and complete a full rotation before// starting other circles// Rotation goes between [0..1].updateRingColor(interpolatedTime, ring);float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)+ 1f);final float minProgressArc = getMinProgressArc(ring);final float startTrim = ring.getStartingStartTrim()+ (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim())* interpolatedTime;ring.setStartTrim(startTrim);ring.setEndTrim(ring.getStartingEndTrim());final float rotation = ring.getStartingRotation()+ ((targetRotation - ring.getStartingRotation()) * interpolatedTime);ring.setRotation(rotation);}private void setupAnimators() {final Ring ring = mRing;final Animation animation = new Animation() {@Overridepublic void applyTransformation(float interpolatedTime, Transformation t) {if (mFinishing) {applyFinishTranslation(interpolatedTime, ring);} else {// The minProgressArc is calculated from 0 to create an// angle that matches the stroke width.final float minProgressArc = getMinProgressArc(ring);final float startingEndTrim = ring.getStartingEndTrim();final float startingTrim = ring.getStartingStartTrim();final float startingRotation = ring.getStartingRotation();updateRingColor(interpolatedTime, ring);// Moving the start trim only occurs in the first 50% of a// single ring animationif (interpolatedTime <= START_TRIM_DURATION_OFFSET) {// scale the interpolatedTime so that the full// transformation from 0 - 1 takes place in the// remaining timefinal float scaledTime = (interpolatedTime)/ (1.0f - START_TRIM_DURATION_OFFSET);final float startTrim = startingTrim+ ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));ring.setStartTrim(startTrim);}// Moving the end trim starts after 50% of a single ring// animation completesif (interpolatedTime > END_TRIM_START_DELAY_OFFSET) {// scale the interpolatedTime so that the full// transformation from 0 - 1 takes place in the// remaining timefinal float minArc = MAX_PROGRESS_ARC - minProgressArc;float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET)/ (1.0f - START_TRIM_DURATION_OFFSET);final float endTrim = startingEndTrim+ (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));ring.setEndTrim(endTrim);}final float rotation = startingRotation + (0.25f * interpolatedTime);ring.setRotation(rotation);float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)+ (FULL_ROTATION * (mRotationCount / NUM_POINTS));setRotation(groupRotation);}}};animation.setRepeatCount(Animation.INFINITE);animation.setRepeatMode(Animation.RESTART);animation.setInterpolator(LINEAR_INTERPOLATOR);animation.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {mRotationCount = 0;}@Overridepublic void onAnimationEnd(Animation animation) {// do nothing}@Overridepublic void onAnimationRepeat(Animation animation) {ring.storeOriginals();ring.goToNextColor();ring.setStartTrim(ring.getEndTrim());if (mFinishing) {// finished closing the last ring from the swipe gesture; go// into progress modemFinishing = false;animation.setDuration(ANIMATION_DURATION);ring.setShowArrow(false);} else {mRotationCount = (mRotationCount + 1) % (NUM_POINTS);}}});mAnimation = animation;}private final Callback mCallback = new Callback() {@Overridepublic void invalidateDrawable(Drawable d) {invalidateSelf();}@Overridepublic void scheduleDrawable(Drawable d, Runnable what, long when) {scheduleSelf(what, when);}@Overridepublic void unscheduleDrawable(Drawable d, Runnable what) {unscheduleSelf(what);}};private static class Ring {private final RectF mTempBounds = new RectF();private final Paint mPaint = new Paint();private final Paint mArrowPaint = new Paint();private final Callback mCallback;private float mStartTrim = 0.0f;private float mEndTrim = 0.0f;private float mRotation = 0.0f;private float mStrokeWidth = 5.0f;private float mStrokeInset = 2.5f;private int[] mColors;// mColorIndex represents the offset into the available mColors that the// progress circle should currently display. As the progress circle is// animating, the mColorIndex moves by one to the next available color.private int mColorIndex;private float mStartingStartTrim;private float mStartingEndTrim;private float mStartingRotation;private boolean mShowArrow;private Path mArrow;private float mArrowScale;private double mRingCenterRadius;private int mArrowWidth;private int mArrowHeight;private int mAlpha;private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);private int mBackgroundColor;private int mCurrentColor;Ring(Callback callback) {mCallback = callback;mPaint.setStrokeCap(Paint.Cap.SQUARE);mPaint.setAntiAlias(true);mPaint.setStyle(Style.STROKE);mArrowPaint.setStyle(Style.FILL);mArrowPaint.setAntiAlias(true);}public void setBackgroundColor(int color) {mBackgroundColor = color;}/*** Set the dimensions of the arrowhead.** @param width Width of the hypotenuse of the arrow head* @param height Height of the arrow point*/public void setArrowDimensions(float width, float height) {mArrowWidth = (int) width;mArrowHeight = (int) height;}/*** Draw the progress spinner*/public void draw(Canvas c, Rect bounds) {final RectF arcBounds = mTempBounds;arcBounds.set(bounds);arcBounds.inset(mStrokeInset, mStrokeInset);final float startAngle = (mStartTrim + mRotation) * 360;final float endAngle = (mEndTrim + mRotation) * 360;float sweepAngle = endAngle - startAngle;mPaint.setColor(mCurrentColor);c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);drawTriangle(c, startAngle, sweepAngle, bounds);if (mAlpha < 255) {mCirclePaint.setColor(mBackgroundColor);mCirclePaint.setAlpha(255 - mAlpha);c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,mCirclePaint);}}private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {if (mShowArrow) {if (mArrow == null) {mArrow = new Path();mArrow.setFillType(Path.FillType.EVEN_ODD);} else {mArrow.reset();}// Adjust the position of the triangle so that it is inset as// much as the arc, but also centered on the arc.float inset = (int) mStrokeInset / 2 * mArrowScale;float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());// Update the path each time. This works around an issue in SKIA// where concatenating a rotation matrix to a scale matrix// ignored a starting negative rotation. This appears to have// been fixed as of API 21.mArrow.moveTo(0, 0);mArrow.lineTo(mArrowWidth * mArrowScale, 0);mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight* mArrowScale));mArrow.offset(x - inset, y);mArrow.close();// draw a trianglemArrowPaint.setColor(mCurrentColor);c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),bounds.exactCenterY());c.drawPath(mArrow, mArrowPaint);}}/*** Set the colors the progress spinner alternates between.** @param colors Array of integers describing the colors. Must be non-<code>null</code>.*/public void setColors(@NonNull int[] colors) {mColors = colors;// if colors are reset, make sure to reset the color index as wellsetColorIndex(0);}/*** Set the absolute color of the progress spinner. This is should only* be used when animating between current and next color when the* spinner is rotating.** @param color int describing the color.*/public void setColor(int color) {mCurrentColor = color;}/*** @param index Index into the color array of the color to display in* the progress spinner.*/public void setColorIndex(int index) {mColorIndex = index;mCurrentColor = mColors[mColorIndex];}/*** @return int describing the next color the progress spinner should use when drawing.*/public int getNextColor() {return mColors[getNextColorIndex()];}private int getNextColorIndex() {return (mColorIndex + 1) % (mColors.length);}/*** Proceed to the next available ring color. This will automatically* wrap back to the beginning of colors.*/public void goToNextColor() {setColorIndex(getNextColorIndex());}public void setColorFilter(ColorFilter filter) {mPaint.setColorFilter(filter);invalidateSelf();}/*** @param alpha Set the alpha of the progress spinner and associated arrowhead.*/public void setAlpha(int alpha) {mAlpha = alpha;}/*** @return Current alpha of the progress spinner and arrowhead.*/public int getAlpha() {return mAlpha;}/*** @param strokeWidth Set the stroke width of the progress spinner in pixels.*/public void setStrokeWidth(float strokeWidth) {mStrokeWidth = strokeWidth;mPaint.setStrokeWidth(strokeWidth);invalidateSelf();}@SuppressWarnings("unused")public float getStrokeWidth() {return mStrokeWidth;}@SuppressWarnings("unused")public void setStartTrim(float startTrim) {mStartTrim = startTrim;invalidateSelf();}@SuppressWarnings("unused")public float getStartTrim() {return mStartTrim;}public float getStartingStartTrim() {return mStartingStartTrim;}public float getStartingEndTrim() {return mStartingEndTrim;}public int getStartingColor() {return mColors[mColorIndex];}@SuppressWarnings("unused")public void setEndTrim(float endTrim) {mEndTrim = endTrim;invalidateSelf();}@SuppressWarnings("unused")public float getEndTrim() {return mEndTrim;}@SuppressWarnings("unused")public void setRotation(float rotation) {mRotation = rotation;invalidateSelf();}@SuppressWarnings("unused")public float getRotation() {return mRotation;}public void setInsets(int width, int height) {final float minEdge = (float) Math.min(width, height);float insets;if (mRingCenterRadius <= 0 || minEdge < 0) {insets = (float) Math.ceil(mStrokeWidth / 2.0f);} else {insets = (float) (minEdge / 2.0f - mRingCenterRadius);}mStrokeInset = insets;}@SuppressWarnings("unused")public float getInsets() {return mStrokeInset;}/*** @param centerRadius Inner radius in px of the circle the progress* spinner arc traces.*/public void setCenterRadius(double centerRadius) {mRingCenterRadius = centerRadius;}public double getCenterRadius() {return mRingCenterRadius;}/*** @param show Set to true to show the arrow head on the progress spinner.*/public void setShowArrow(boolean show) {if (mShowArrow != show) {mShowArrow = show;invalidateSelf();}}/*** @param scale Set the scale of the arrowhead for the spinner.*/public void setArrowScale(float scale) {if (scale != mArrowScale) {mArrowScale = scale;invalidateSelf();}}/*** @return The amount the progress spinner is currently rotated, between [0..1].*/public float getStartingRotation() {return mStartingRotation;}/*** If the start / end trim are offset to begin with, store them so that* animation starts from that offset.*/public void storeOriginals() {mStartingStartTrim = mStartTrim;mStartingEndTrim = mEndTrim;mStartingRotation = mRotation;}/*** Reset the progress spinner to default rotation, start and end angles.*/public void resetOriginals() {mStartingStartTrim = 0;mStartingEndTrim = 0;mStartingRotation = 0;setStartTrim(0);setEndTrim(0);setRotation(0);}private void invalidateSelf() {mCallback.invalidateDrawable(null);}}
}
CircleImageView.java,这个类继承的ImageView,在AndroidStudio里面会报红,不用理它
/** Copyright (C) 2014 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** .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.hzncc.kevin.swipelayout;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;/*** Private class created to work around issues with AnimationListeners being* called before the animation is actually complete and support shadows on older* platforms.*/
class CircleImageView extends ImageView {private static final int KEY_SHADOW_COLOR = 0x1E000000;private static final int FILL_SHADOW_COLOR = 0x3D000000;// PXprivate static final float X_OFFSET = 0f;private static final float Y_OFFSET = 1.75f;private static final float SHADOW_RADIUS = 3.5f;private static final int SHADOW_ELEVATION = 4;private Animation.AnimationListener mListener;int mShadowRadius;CircleImageView(Context context, int color) {super(context);final float density = getContext().getResources().getDisplayMetrics().density;final int shadowYOffset = (int) (density * Y_OFFSET);final int shadowXOffset = (int) (density * X_OFFSET);mShadowRadius = (int) (density * SHADOW_RADIUS);ShapeDrawable circle;if (elevationSupported()) {circle = new ShapeDrawable(new OvalShape());ViewCompat.setElevation(this, SHADOW_ELEVATION * density);} else {OvalShape oval = new OvalShadow(mShadowRadius);circle = new ShapeDrawable(oval);setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint());circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,KEY_SHADOW_COLOR);final int padding = mShadowRadius;// set padding so the inner image sits correctly within the shadow.setPadding(padding, padding, padding, padding);}circle.getPaint().setColor(color);ViewCompat.setBackground(this, circle);}private boolean elevationSupported() {return android.os.Build.VERSION.SDK_INT >= 21;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!elevationSupported()) {setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight()+ mShadowRadius * 2);}}public void setAnimationListener(Animation.AnimationListener listener) {mListener = listener;}@Overridepublic void onAnimationStart() {super.onAnimationStart();if (mListener != null) {mListener.onAnimationStart(getAnimation());}}@Overridepublic void onAnimationEnd() {super.onAnimationEnd();if (mListener != null) {mListener.onAnimationEnd(getAnimation());}}/*** Update the background color of the circle image view.** @param colorRes Id of a color resource.*/public void setBackgroundColorRes(int colorRes) {setBackgroundColor(ContextCompat.getColor(getContext(), colorRes));}@Overridepublic void setBackgroundColor(int color) {if (getBackground() instanceof ShapeDrawable) {((ShapeDrawable) getBackground()).getPaint().setColor(color);}}private class OvalShadow extends OvalShape {private RadialGradient mRadialGradient;private Paint mShadowPaint;OvalShadow(int shadowRadius) {super();mShadowPaint = new Paint();mShadowRadius = shadowRadius;updateRadialGradient((int) rect().width());}@Overrideprotected void onResize(float width, float height) {super.onResize(width, height);updateRadialGradient((int) width);}@Overridepublic void draw(Canvas canvas, Paint paint) {final int viewWidth = CircleImageView.this.getWidth();final int viewHeight = CircleImageView.this.getHeight();canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);}private void updateRadialGradient(int diameter) {mRadialGradient = new RadialGradient(diameter / 2, diameter / 2,mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT },null, Shader.TileMode.CLAMP);mShadowPaint.setShader(mRadialGradient);}}
}
另外这个控件还有一个缺陷,使用的时候必须嵌套一个NestedScrollView,原因没有弄清楚,希望有大神能解答下
<com.hzncc.kevin.swipelayout.SwipeRefreshAndLoadLayoutandroid:id="@+id/swipe_Layout"android:layout_width="match_parent"android:layout_height="wrap_content"><android.support.v4.widget.NestedScrollViewandroid:layout_width="match_parent"android:layout_height="wrap_content"><ListViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="wrap_content" /></android.support.v4.widget.NestedScrollView>
</com.hzncc.kevin.swipelayout.SwipeRefreshAndLoadLayout>
本文标签:
版权声明:本文标题:Android SwipeRefreshAndLoadLayout,下拉刷新,上拉更多,使用SwipeRefreshLayout自带的Progress 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.betaflare.com/biancheng/1703024358a598845.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。


发表评论