Android(安卓)下拉刷新上拉加载
作为一名后台人员,突然让搞安卓也是醉的不行不行的了,多余的话略过,代码走起。
首先添加依赖,在build.gradle中加入retrofit2和recycleView的依赖,要用到这个。
compile 'com.android.support:recyclerview-v7:26.1.0' compile 'com.squareup.retrofit2:retrofit:2.0.1' compile 'com.squareup.retrofit2:converter-gson:2.0.1' compile 'com.github.bumptech.glide:glide:3.7.0'
先看一下项目结构吧,如下图所示
布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
item.xml
<?xml version="1.0" encoding="utf-8"?>
在res下新建menu文件夹,menu_main.xml
在values下新建attrs.xml和dimens.xml。
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
dimens.xml
16dp 16dp 16dp
color.xml中增加
#80c3db
styles中添加
到此布局文件完成,接下来就是activity文件了。
Api.java 里面的pageCount是每页条数,pageIndex是页数
import bean.DataInfo;import retrofit2.Call;import retrofit2.http.GET;import retrofit2.http.Path;/** * Created by Administrator on 2016/6/25. */public interface Api { @GET("{pageCount}/{pageIndex}") Call getData(@Path("pageCount") int pageCount, @Path("pageIndex") int pageIndex);}
DataInfo是json数据的格式化
json格式如下{ "error": false, "results": [ { "_id": "5b50107f421aa917a31c0565", "createdAt": "2018-07-19T12:15:59.226Z", "desc": "2018-07-19", "publishedAt": "2018-07-19T00:00:00.0Z", "source": "web", "type": "\u798f\u5229", "url": "https://ww1.sinaimg.cn/large/0065oQSqly1ftf1snjrjuj30se10r1kx.jpg", "used": true, "who": "lijinshanmx" }, { "_id": "5b4eaae4421aa93aa99bee16", "createdAt": "2018-07-18T11:14:55.648Z", "desc": "2018-07-18", "publishedAt": "2018-07-18T00:00:00.0Z", "source": "web", "type": "\u798f\u5229", "url": "https://ww1.sinaimg.cn/large/0065oQSqly1ftdtot8zd3j30ju0pt137.jpg", "used": true, "who": "lijinshanmx" }, { "_id": "5b481d01421aa90bba87b9ae", "createdAt": "2018-07-13T11:31:13.266Z", "desc": "2018-07-13", "publishedAt": "2018-07-13T00:00:00.0Z", "source": "web", "type": "\u798f\u5229", "url": "http://ww1.sinaimg.cn/large/0073sXn7ly1ft82s05kpaj30j50pjq9v.jpg", "used": true, "who": "lijinshanmx" }, { "_id": "5b456f5d421aa92fc4eebe48", "createdAt": "2018-07-11T10:45:49.246Z", "desc": "2018-07-11", "publishedAt": "2018-07-11T00:00:00.0Z", "source": "web", "type": "\u798f\u5229", "url": "https://ww1.sinaimg.cn/large/0065oQSqly1ft5q7ys128j30sg10gnk5.jpg", "used": true, "who": "lijinshanmx" }, { "_id": "5b441f06421aa92fccb520a2", "createdAt": "2018-07-10T10:50:46.379Z", "desc": "2018-07-10", "publishedAt": "2018-07-10T00:00:00.0Z", "source": "web", "type": "\u798f\u5229", "url": "https://ww1.sinaimg.cn/large/0065oQSqgy1ft4kqrmb9bj30sg10fdzq.jpg", "used": true, "who": "lijinshanmx" } ]}----------------------------------------------------------------------------------------import java.util.ArrayList;/** * Created by Administrator on 2016/6/25. */public class DataInfo { public String error; public ArrayList results; public class Info{ public String _id; public String url; }}
CircleImageView.java
import android.content.Context;import android.content.res.Resources;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.view.ViewCompat;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. * * @hide */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;private int mShadowRadius;public CircleImageView(Context context, int color, final float radius) {super(context);final float density = getContext().getResources().getDisplayMetrics().density;final int diameter = (int) (radius * density * 2);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, diameter);circle = new ShapeDrawable(oval);ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE,circle.getPaint());circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset,shadowYOffset, KEY_SHADOW_COLOR);final int padding = (int) mShadowRadius;// set padding so the inner image sits correctly within the shadow.setPadding(padding, padding, padding, padding);}circle.getPaint().setColor(color);setBackgroundDrawable(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. */public void setBackgroundColor(int colorRes) {if (getBackground() instanceof ShapeDrawable) {final Resources res = getResources();((ShapeDrawable) getBackground()).getPaint().setColor(res.getColor(colorRes));}}private class OvalShadow extends OvalShape {private RadialGradient mRadialGradient;private int mShadowRadius;private Paint mShadowPaint;private int mCircleDiameter;public OvalShadow(int shadowRadius, int circleDiameter) {super();mShadowPaint = new Paint();mShadowRadius = shadowRadius;mCircleDiameter = circleDiameter;mRadialGradient = new RadialGradient(mCircleDiameter / 2,mCircleDiameter / 2, mShadowRadius, new int[] {FILL_SHADOW_COLOR, Color.TRANSPARENT }, null,Shader.TileMode.CLAMP);mShadowPaint.setShader(mRadialGradient);}@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,(mCircleDiameter / 2 + mShadowRadius), mShadowPaint);canvas.drawCircle(viewWidth / 2, viewHeight / 2,(mCircleDiameter / 2), paint);}}}
MaterialProgressDrawable.java
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.util.DisplayMetrics;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;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. * * @hide */class MaterialProgressDrawable extends Drawable implements Animatable { private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); @Retention(RetentionPolicy.CLASS) @IntDef({LARGE, DEFAULT}) public @interface ProgressDrawableSize {} // Maps to ProgressBar.Large style static final int LARGE = 0; // Maps to ProgressBar default style static final int DEFAULT = 1; // Maps to ProgressBar default style private static final int CIRCLE_DIAMETER = 40; private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width private static final float STROKE_WIDTH = 2.5f; // Maps to ProgressBar.Large style private 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 final int[] COLORS = new int[] { Color.BLACK }; /** The duration of a single progress spin in milliseconds. */ private static final int ANIMATION_DURATION = 1000 * 80 / 60; /** 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 mAnimators = new ArrayList(); /** 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; private float mRotationCount; private double mWidth; private double mHeight; private Animation mFinishAnimation; public 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 com.orangegangsters.github.swiperefreshlayout.MaterialProgressDrawable.LARGE} or * {@link com.orangegangsters.github.swiperefreshlayout.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); } @Override public int getIntrinsicHeight() { return (int) mHeight; } @Override public int getIntrinsicWidth() { return (int) mWidth; } @Override public 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); } @Override public void setAlpha(int alpha) { mRing.setAlpha(alpha); } public int getAlpha() { return mRing.getAlpha(); } @Override public void setColorFilter(ColorFilter colorFilter) { mRing.setColorFilter(colorFilter); } @SuppressWarnings("unused") void setRotation(float rotation) { mRotation = rotation; invalidateSelf(); } @SuppressWarnings("unused") private float getRotation() { return mRotation; } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public boolean isRunning() { final ArrayList 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; } @Override public void start() { mAnimation.reset(); mRing.storeOriginals(); // Already showing some part of the ring if (mRing.getEndTrim() != mRing.getStartTrim()) { mParent.startAnimation(mFinishAnimation); } else { mRing.setColorIndex(0); mRing.resetOriginals(); mParent.startAnimation(mAnimation); } } @Override public void stop() { mParent.clearAnimation(); setRotation(0); mRing.setShowArrow(false); mRing.setColorIndex(0); mRing.resetOriginals(); } private void setupAnimators() { final Ring ring = mRing; final Animation finishRingAnimation = new Animation() { public void applyTransformation(float interpolatedTime, Transformation t) { // shrink back down and complete a full rotation before starting other circles // Rotation goes between [0..1]. float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) + 1f); final float startTrim = ring.getStartingStartTrim() + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime; ring.setStartTrim(startTrim); final float rotation = ring.getStartingRotation() + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); ring.setRotation(rotation); ring.setArrowScale(1 - interpolatedTime); } }; finishRingAnimation.setInterpolator(EASE_INTERPOLATOR); finishRingAnimation.setDuration(ANIMATION_DURATION/2); finishRingAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { ring.goToNextColor(); ring.storeOriginals(); ring.setShowArrow(false); mParent.startAnimation(mAnimation); } @Override public void onAnimationRepeat(Animation animation) { } }); final Animation animation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { // The minProgressArc is calculated from 0 to create an angle that // matches the stroke width. final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); final float startingEndTrim = ring.getStartingEndTrim(); final float startingTrim = ring.getStartingStartTrim(); final float startingRotation = ring.getStartingRotation(); // Offset the minProgressArc to where the endTrim is located. final float minArc = MAX_PROGRESS_ARC - minProgressArc; final float endTrim = startingEndTrim + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); ring.setEndTrim(endTrim); final float startTrim = startingTrim + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR .getInterpolation(interpolatedTime)); ring.setStartTrim(startTrim); final float rotation = startingRotation + (0.25f * interpolatedTime); ring.setRotation(rotation); float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime) + (720.0f * (mRotationCount / NUM_POINTS)); setRotation(groupRotation); } }; animation.setRepeatCount(Animation.INFINITE); animation.setRepeatMode(Animation.RESTART); animation.setInterpolator(LINEAR_INTERPOLATOR); animation.setDuration(ANIMATION_DURATION); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mRotationCount = 0; } @Override public void onAnimationEnd(Animation animation) { // do nothing } @Override public void onAnimationRepeat(Animation animation) { ring.storeOriginals(); ring.goToNextColor(); ring.setStartTrim(ring.getEndTrim()); mRotationCount = (mRotationCount + 1) % (NUM_POINTS); } }); mFinishAnimation = finishRingAnimation; mAnimation = animation; } private final Callback mCallback = new Callback() { @Override public void invalidateDrawable(Drawable d) { invalidateSelf(); } @Override public void scheduleDrawable(Drawable d, Runnable what, long when) { scheduleSelf(what, when); } @Override public 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(); private int mBackgroundColor; public 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(mColors[mColorIndex]); 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 triangle mArrowPaint.setColor(mColors[mColorIndex]); 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-null
. */ public void setColors(@NonNull int[] colors) { mColors = colors; // if colors are reset, make sure to reset the color index as well setColorIndex(0); } /** * @param index Index into the color array of the color to display in * the progress spinner. */ public void setColorIndex(int index) { mColorIndex = index; } /** * Proceed to the next available ring color. This will automatically * wrap back to the beginning of colors. */ public void goToNextColor() { mColorIndex = (mColorIndex + 1) % (mColors.length); } 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; } @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); } } /** * Squishes the interpolation curve into the second half of the animation. */ private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { @Override public float getInterpolation(float input) { return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); } } /** * Squishes the interpolation curve into the first half of the animation. */ private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { @Override public float getInterpolation(float input) { return super.getInterpolation(Math.min(1, input * 2.0f)); } }}
SwipyRefreshLayout.java
import android.annotation.SuppressLint;import android.content.Context;import android.content.res.Resources;import android.content.res.TypedArray;import android.support.v4.view.MotionEventCompat;import android.support.v4.view.ViewCompat;import android.util.AttributeSet;import android.util.DisplayMetrics;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.Animation.AnimationListener;import android.view.animation.DecelerateInterpolator;import android.view.animation.Transformation;import android.widget.AbsListView;import com.example.administrator.retrofit.R;/** * The SwipeRefreshLayout should be used whenever the user can refresh the * contents of a view via a vertical swipe gesture. The activity that * instantiates this view should add an OnRefreshListener to be notified * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout * will notify the listener each and every time the gesture is completed again; * the listener is responsible for correctly determining when to actually * initiate a refresh of its content. If the listener determines there should * not be a refresh, it must call setRefreshing(false) to cancel any visual * indication of a refresh. If an activity wishes to show just the progress * animation, it should call setRefreshing(true). To disable the gesture and * progress animation, call setEnabled(false) on the view. * * This layout should be made the parent of the view that will be refreshed as a * result of the gesture and can only support one direct child. This view will * also be made the target of the gesture and will be forced to match both the * width and the height supplied in this layout. The SwipeRefreshLayout does not * provide accessibility events; instead, a menu item must be provided to allow * refresh of the content wherever this gesture is used. *
*//** * 下拉刷新、加载更多、分页索引 * @author xutao * */public class SwipyRefreshLayout extends ViewGroup {/** 是不是下拉 **/public boolean isTop;/** 第一页 **/public int firstIndex = 0;/** 页数索引 **/public int index = firstIndex; // Maps to ProgressBar.Large style public static final int LARGE = MaterialProgressDrawable.LARGE; // Maps to ProgressBar default style public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; private static final String LOG_TAG = SwipyRefreshLayout.class.getSimpleName(); private static final int MAX_ALPHA = 255; private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); private static final int CIRCLE_DIAMETER = 40; private static final int CIRCLE_DIAMETER_LARGE = 56; 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 circle private 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 spinner private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; // Default offset in dips from the top of the view to where the progress spinner should stop private static final int DEFAULT_CIRCLE_TARGET = 64; private View mTarget; // the target of the gesture private SwipyRefreshLayoutDirection mDirection; private boolean mBothDirection; private OnRefreshListener mListener; private boolean mRefreshing = false; private int mTouchSlop; private float mTotalDragDistance = -1; private int mMediumAnimationDuration; private int mCurrentTargetOffsetTop; // Whether or not the starting offset has been determined. private boolean mOriginalOffsetCalculated = false; private float mInitialMotionY; private boolean mIsBeingDragged; private int mActivePointerId = INVALID_POINTER; // Whether this item is scaled up rather than clipped private boolean mScale; // Target is returning to its start offset because it was cancelled or a // refresh was triggered. private boolean mReturningToStart; private final DecelerateInterpolator mDecelerateInterpolator; private static final int[] LAYOUT_ATTRS = new int[]{ android.R.attr.enabled }; private CircleImageView mCircleView; private int mCircleViewIndex = -1; protected int mFrom; private float mStartingScale; protected int mOriginalOffsetTop; 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; private AnimationListener mRefreshListener = new 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) { if (isTop) { index = firstIndex; mListener.onRefresh(index);}else {index ++;mListener.onLoad(index);} } } } else { mProgress.stop(); mCircleView.setVisibility(View.GONE); setColorViewAlpha(MAX_ALPHA); // Return the circle to its start position if (mScale) { setAnimationProgress(0 /* animation complete and view is hidden */); } else { setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, true /* requires update */); } } mCurrentTargetOffsetTop = mCircleView.getTop(); } }; 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. * * @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(); }*/ /** * 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) { mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); } else { mCircleHeight = mCircleWidth = (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 it mCircleView.setImageDrawable(null); mProgress.updateSizes(size); mCircleView.setImageDrawable(mProgress); } /** * Simple constructor to use when creating a SwipeRefreshLayout from code. * * @param context */ public SwipyRefreshLayout(Context context) { this(context, null); } /** * Constructor that is called when inflating SwipeRefreshLayout from XML. * * @param context * @param attrs */ public SwipyRefreshLayout(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 TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); setEnabled(a.getBoolean(0, true)); a.recycle(); final TypedArray a2 = context.obtainStyledAttributes(attrs, R.styleable.SwipyRefreshLayout); SwipyRefreshLayoutDirection direction = SwipyRefreshLayoutDirection.getFromInt(a2.getInt(R.styleable.SwipyRefreshLayout_direction, 0)); if (direction != SwipyRefreshLayoutDirection.BOTH) { mDirection = direction; mBothDirection = false; } else { mDirection = SwipyRefreshLayoutDirection.TOP; mBothDirection = true; } a2.recycle(); final DisplayMetrics metrics = getResources().getDisplayMetrics(); mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); createProgressView(); ViewCompat.setChildrenDrawingOrderEnabled(this, true); // the absolute offset has to take into account that the circle starts at an offset mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; mTotalDragDistance = mSpinnerFinalOffset; //设置刷新动画颜色 setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light); } protected int getChildDrawingOrder(int childCount, int i) { if (mCircleViewIndex < 0) { return i; } else if (i == childCount - 1) { // Draw the selected child last return mCircleViewIndex; } else if (i >= mCircleViewIndex) { // Move the children after the selected child earlier one return i + 1; } else { // Keep the children before the selected child the same return i; } } 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; } /** * Pre API 11, alpha is used to make the progress circle appear instead of scale. */ private boolean isAlphaUsedForScale() { return android.os.Build.VERSION.SDK_INT < 11; } /** * 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 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 startScaleUpAnimation(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() { @Override public 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 */ private void setAnimationProgress(float progress) { if (isAlphaUsedForScale()) { setColorViewAlpha((int) (progress * MAX_ALPHA)); } else { ViewCompat.setScaleX(mCircleView, progress); ViewCompat.setScaleY(mCircleView, 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); } } } private void startScaleDownAnimation(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); } @SuppressLint("NewApi") private void startProgressAlphaStartAnimation() { mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); } @SuppressLint("NewApi") 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; } /** * Set the background color of the progress spinner disc. * * @param colorRes Resource id of the color. */ public void setProgressBackgroundColor(int colorRes) { mCircleView.setBackgroundColor(colorRes); mProgress.setBackgroundColor(getResources().getColor(colorRes)); } /** * @deprecated Use {@link #setColorSchemeResources(int...)} */ @Deprecated public void setColorScheme(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 */ public void setColorSchemeResources(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(int... colors) { ensureTarget(); mProgress.setColorSchemeColors(colors); } /** * @return Whether the SwipeRefreshWidget is actively showing refresh * progress. */ public boolean isRefreshing() { return mRefreshing; } 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; } @Override protected 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); } @Override public 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(mCircleWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY)); if (!mUsingCustomStart && !mOriginalOffsetCalculated) { mOriginalOffsetCalculated = true; switch (mDirection) { case BOTTOM: mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight(); break; case TOP: default: mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); break; } } mCircleViewIndex = -1; // Get the index of the circleview. for (int index = 0; index < getChildCount(); index++) { if (getChildAt(index) == mCircleView) { mCircleViewIndex = index; 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 mTarget.getScrollY() > 0; } } else { return ViewCompat.canScrollVertically(mTarget, -1); } }// public boolean canChildScrollUp() {// if (android.os.Build.VERSION.SDK_INT < 14) {// if (mTarget instanceof AbsListView) {// final AbsListView absListView = (AbsListView) mTarget;// if (absListView.getLastVisiblePosition() + 1 == absListView.getCount()) {// int lastIndex = absListView.getLastVisiblePosition() - absListView.getFirstVisiblePosition();//// boolean res = absListView.getChildAt(lastIndex).getBottom() == absListView.getPaddingBottom();//// return res;// }// return true;// } else {// return mTarget.getScrollY() > 0;// }// } else {// return ViewCompat.canScrollVertically(mTarget, 1);// }// } public boolean canChildScrollDown() { if (android.os.Build.VERSION.SDK_INT < 14) { if (mTarget instanceof AbsListView) { final AbsListView absListView = (AbsListView) mTarget; try { if (absListView.getCount() > 0) { if (absListView.getLastVisiblePosition() + 1 == absListView.getCount()) { int lastIndex = absListView.getLastVisiblePosition() - absListView.getFirstVisiblePosition(); return absListView.getChildAt(lastIndex).getBottom() == absListView.getPaddingBottom(); } } } catch (Exception e) { e.printStackTrace(); } return true; } else { return true; } } else { return ViewCompat.canScrollVertically(mTarget, 1); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { ensureTarget(); final int action = MotionEventCompat.getActionMasked(ev); if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { mReturningToStart = false; } switch (mDirection) { case BOTTOM: if (!isEnabled() || mReturningToStart || (!mBothDirection && canChildScrollDown()) || mRefreshing) { // Fail fast if we're not in a state where a swipe is possible return false; } break; case TOP: default: if (!isEnabled() || mReturningToStart || (!mBothDirection && canChildScrollUp()) || mRefreshing) { // Fail fast if we're not in a state where a swipe is possible return false; } break; } switch (action) { case MotionEvent.ACTION_DOWN: setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mIsBeingDragged = false; final float initialMotionY = getMotionEventY(ev, mActivePointerId); if (initialMotionY == -1) { return false; } mInitialMotionY = initialMotionY; case MotionEvent.ACTION_MOVE: if (mActivePointerId == INVALID_POINTER) { return false; } final float y = getMotionEventY(ev, mActivePointerId); if (y == -1) { return false; } if (mBothDirection) { if (y > mInitialMotionY) { setRawDirection(SwipyRefreshLayoutDirection.TOP); } else if (y < mInitialMotionY) { setRawDirection(SwipyRefreshLayoutDirection.BOTTOM); } if ((mDirection == SwipyRefreshLayoutDirection.BOTTOM && canChildScrollDown()) || (mDirection == SwipyRefreshLayoutDirection.TOP && canChildScrollUp())) { return false; } } float yDiff; switch (mDirection) { case BOTTOM: yDiff = mInitialMotionY - y; break; case TOP: default: yDiff = y - mInitialMotionY; break; } if (yDiff > mTouchSlop && !mIsBeingDragged) { 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 float getMotionEventY(MotionEvent ev, int activePointerId) { final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); if (index < 0) { return -1; } return MotionEventCompat.getY(ev, index); } @Override public void requestDisallowInterceptTouchEvent(boolean b) { // Nope. } private boolean isAnimationRunning(Animation animation) { return animation != null && animation.hasStarted() && !animation.hasEnded(); } @SuppressLint("NewApi") @Override public boolean onTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { mReturningToStart = false; } switch (mDirection) { case BOTTOM: if (!isEnabled() || mReturningToStart || canChildScrollDown() || mRefreshing) { // Fail fast if we're not in a state where a swipe is possible return false; } break; case TOP: default: if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) { // Fail fast if we're not in a state where a swipe is possible return false; } break; } switch (action) { case MotionEvent.ACTION_DOWN: mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mIsBeingDragged = false; break; case MotionEvent.ACTION_MOVE: { final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); if (pointerIndex < 0) { return false; } final float y = MotionEventCompat.getY(ev, pointerIndex); float overscrollTop; switch (mDirection) { case BOTTOM: overscrollTop = (mInitialMotionY - y) * DRAG_RATE; break; case TOP: default: overscrollTop = (y - mInitialMotionY) * DRAG_RATE; break; } if (mIsBeingDragged) { mProgress.showArrow(true); float originalDragPercent = overscrollTop / mTotalDragDistance; if (originalDragPercent < 0) { return false; } 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); int targetY; if (mDirection == SwipyRefreshLayoutDirection.TOP) { targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove); } else { 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 (overscrollTop < mTotalDragDistance) { if (mScale) { setAnimationProgress(overscrollTop / mTotalDragDistance); } if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA && !isAnimationRunning(mAlphaStartAnimation)) { // Animate the alpha startProgressAlphaStartAnimation(); } float strokeStart = (float) (adjustedPercent * .8f); mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); mProgress.setArrowScale(Math.min(1f, adjustedPercent)); } else { if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) { // Animate the alpha startProgressAlphaMaxAnimation(); } } float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; mProgress.setProgressRotation(rotation); setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); } break; } case MotionEventCompat.ACTION_POINTER_DOWN: { final int index = MotionEventCompat.getActionIndex(ev); mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { if (mActivePointerId == INVALID_POINTER) { if (action == MotionEvent.ACTION_UP) { } return false; } final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float y = MotionEventCompat.getY(ev, pointerIndex); float overscrollTop; switch (mDirection) { case BOTTOM: overscrollTop = (mInitialMotionY - y) * DRAG_RATE; isTop = false; break; case TOP: default: overscrollTop = (y - mInitialMotionY) * DRAG_RATE; isTop = true; break; } mIsBeingDragged = false; if (overscrollTop > mTotalDragDistance) { setRefreshing(true, true /* notify */); } else { // cancel refresh mRefreshing = false; mProgress.setStartEndTrim(0f, 0f); AnimationListener listener = null; if (!mScale) { listener = new 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); } mActivePointerId = INVALID_POINTER; return false; } } return true; } private void animateOffsetToCorrectPosition(int from, 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, AnimationListener listener) { if (mScale) { // Scale the item back down startScaleDownReturnToStartAnimation(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); } } private final Animation mAnimateToCorrectPosition = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { int targetTop = 0; int endTarget = 0; if (!mUsingCustomStart) { switch (mDirection) { case BOTTOM: endTarget = getMeasuredHeight() - (int) (mSpinnerFinalOffset); break; case TOP: default: endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop)); break; } } else { endTarget = (int) mSpinnerFinalOffset; } targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); int offset = targetTop - mCircleView.getTop(); setTargetOffsetTopAndBottom(offset, false /* requires update */); } }; private void moveToStart(float interpolatedTime) { int targetTop = 0; targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); int offset = targetTop - mCircleView.getTop(); setTargetOffsetTopAndBottom(offset, false /* requires update */); } private final Animation mAnimateToStartPosition = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { moveToStart(interpolatedTime); } }; @SuppressLint("NewApi") private void startScaleDownReturnToStartAnimation(int from, AnimationListener listener) { mFrom = from; if (isAlphaUsedForScale()) { mStartingScale = mProgress.getAlpha(); } else { mStartingScale = ViewCompat.getScaleX(mCircleView); } mScaleDownToStartAnimation = new Animation() { @Override public 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); } private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { mCircleView.bringToFront(); mCircleView.offsetTopAndBottom(offset); mCurrentTargetOffsetTop = mCircleView.getTop(); if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { invalidate(); } } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, 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 = MotionEventCompat.getPointerId(ev, newPointerIndex); } } /** * Classes that wish to be notified when the swipe gesture correctly * triggers a refresh should implement this interface. */ public interface OnRefreshListener { public void onRefresh(int index); public void onLoad(int index); } public SwipyRefreshLayoutDirection getDirection() { return mBothDirection ? SwipyRefreshLayoutDirection.BOTH : mDirection; } public void setDirection(SwipyRefreshLayoutDirection direction) { if (direction == SwipyRefreshLayoutDirection.BOTH) { mBothDirection = true; } else { mBothDirection = false; mDirection = direction; } switch (mDirection) { case BOTTOM: mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight(); break; case TOP: default: mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); break; } } // only TOP or Bottom private void setRawDirection(SwipyRefreshLayoutDirection direction) { if (mDirection == direction) { return; } mDirection = direction; switch (mDirection) { case BOTTOM: mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight(); break; case TOP: default: mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); break; } } /** * @return 获得从第一页开始索引 */public int getFirstIndex() {return firstIndex;}/** * 设置从第几页开始(默认值为0) * @param firstIndex 第几页 */public void setFirstIndex(int firstIndex) {this.firstIndex = firstIndex;}/** * @return 获得当前索引 */public int getIndex() {return index;}
SwipyRefreshLayoutDirection.java
/** * * @author xutao * */public enum SwipyRefreshLayoutDirection {TOP(0), // 只有下拉刷新BOTTOM(1), // 只有加载更多BOTH(2);// 全都有private int mValue;SwipyRefreshLayoutDirection(int value) {this.mValue = value;}public static SwipyRefreshLayoutDirection getFromInt(int value) {for (SwipyRefreshLayoutDirection direction : SwipyRefreshLayoutDirection.values()) {if (direction.mValue == value) {return direction;}}return BOTH;}}
MyAdapter.java
import android.content.Context;import android.support.v7.widget.RecyclerView;import android.support.v7.widget.RecyclerView.ViewHolder;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;import com.bumptech.glide.Glide;import java.util.ArrayList;import bean.DataInfo;/** * Created by Administrator on 2016/6/25. */public class MyAdapter extends RecyclerView.Adapter implements View.OnClickListener { private Context context; private ArrayList list; public MyAdapter(Context context, ArrayList list) { this.context = context; this.list = list; } private OnRecyclerViewItemClickListener mOnItemClickListener = null; @Override public void onClick(View v) { if (mOnItemClickListener != null) { //注意这里使用getTag方法获取数据 mOnItemClickListener.onItemClick(v, (DataInfo.Info) v.getTag()); } } public interface OnRecyclerViewItemClickListener { void onItemClick(View view , DataInfo.Info data); } public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) { this.mOnItemClickListener = listener; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item, null); ViewHodler vh=new ViewHodler(view); LinearLayout.LayoutParams lp = new LinearLayout. LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); view.setLayoutParams(lp); view.setOnClickListener(this); return vh; } @Override public void onBindViewHolder(ViewHolder holder, int position) { ViewHodler mHodler = (ViewHodler) holder; mHodler.textview.setText("this is item "+position ); Glide.with(context) .load(list.get(position).url) .centerCrop() .placeholder(R.color.app_primary_color) .crossFade() .into(mHodler.imageView); Log.i("aaaa", list.get(position)._id); mHodler.itemView.setTag(list.get(position)); } @Override public int getItemCount() { return list.size(); } class ViewHodler extends RecyclerView.ViewHolder{ private TextView textview; private ImageView imageView; public ViewHodler(View itemView) { super(itemView); textview= (TextView) itemView.findViewById(R.id.textview); imageView= (ImageView) itemView.findViewById(R.id.image);// itemView.setOnClickListener(new View.OnClickListener() {// @Override// public void onClick(View v) {// Toast.makeText(context,"click"+getPosition(),Toast.LENGTH_SHORT).show();// }// }); } }}
MainActivity.java
import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.Log;import android.view.View;import android.widget.Toast;import com.example.demo.pull.SwipyRefreshLayout;import java.util.ArrayList;import api.Api;import bean.DataInfo;import retrofit2.Call;import retrofit2.Callback;import retrofit2.Response;import retrofit2.Retrofit;import retrofit2.converter.gson.GsonConverterFactory;public class MainActivity extends AppCompatActivity implements MyAdapter.OnRecyclerViewItemClickListener,SwipyRefreshLayout.OnRefreshListener{ private ArrayList arrayList; private RecyclerView recyclerView; private MyAdapter adapter; private SwipyRefreshLayout refreshLayout; private LinearLayoutManager linearLayoutManager; private int pages=1; private final int TOP_REFRESH = 1; private final int BOTTOM_REFRESH = 2; //数据接口 public static final String URL = "http://gank.io/api/data/福利/"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView= (RecyclerView) findViewById(R.id.recyerview); refreshLayout= (SwipyRefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setOnRefreshListener(this); arrayList=new ArrayList(); initData(1); linearLayoutManager=new LinearLayoutManager(this); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(linearLayoutManager); adapter=new MyAdapter(this,arrayList); adapter.setOnItemClickListener(this); recyclerView.setAdapter(adapter); } private void initData(int pages) { //使用retrofit配置api Retrofit retrofit=new Retrofit.Builder() .baseUrl(URL) .addConverterFactory(GsonConverterFactory.create()) .build(); Api api = retrofit.create(Api.class); Call call=api.getData(10,pages); call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { arrayList.addAll(response.body().results); adapter.notifyDataSetChanged(); Log.i("aaaa", arrayList.size() + ""); refreshLayout.setRefreshing(false); } @Override public void onFailure(Call call, Throwable t) { refreshLayout.setRefreshing(false); } }); } @Override public void onItemClick(View view, DataInfo.Info data) { Toast.makeText(MainActivity.this, "click item " + data, Toast.LENGTH_SHORT).show(); } @Override public void onRefresh(int index) { dataOption(TOP_REFRESH); Toast.makeText(this,"已经是最新数据",Toast.LENGTH_SHORT).show(); } @Override public void onLoad(int index) { dataOption(BOTTOM_REFRESH); Toast.makeText(this,"加载完成",Toast.LENGTH_SHORT).show(); } private void dataOption(int option){ switch (option) { case TOP_REFRESH: //下拉刷新 arrayList.clear(); initData(1); break; case BOTTOM_REFRESH: //上拉加载更多 pages++; initData(pages); break; } // adapter.notifyDataSetChanged(); }}
最后不要忘记在清单文件中加上权限哦
代码完成。
源码传送门
更多相关文章
- 微信公众号 修改 应用签名 不生效
- Android(安卓)基于RecyclerView上下拉刷新(让你轻松自定义头部和
- Android(安卓)SwipeRefreshLayout+RecyclerView下拉刷新与上拉加
- Android中ListView下拉刷新的实现代码
- 自定义控件和试图(部分原生api)下拉菜单
- 推荐大家一个好用的实现分页加载和下拉刷新Android工具包
- Android使用枚举单例实现Toast快速刷新(自定义吐司)
- Android(安卓)类ListView下拉刷新控件实现 .
- Android(安卓)RecyclerView删除多个选中的item 及 局部刷新