├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── scopes
│ └── scope_settings.xml
└── vcs.xml
├── README.md
├── SwipeRefreshLoadDemo.iml
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── zfeng
│ │ └── swiperefreshload
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── zfeng
│ │ └── swiperefreshload
│ │ ├── CircleImageView.java
│ │ ├── ListViewDemoActivity.java
│ │ ├── MaterialProgressDrawable.java
│ │ ├── RecyclerViewDemoActivity.java
│ │ └── SwipeRefreshLoadLayout.java
│ └── res
│ ├── layout
│ ├── activity_main.xml
│ └── activity_recycler.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ids.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── themes.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | TestSwipeRefreshLayout
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwipeRefreshLoadDemo
2 |
3 | 我写的新组件RecyclerViewPullRefresh #https://github.com/AndyZhaofeng/RecyclerViewPullRefresh#
4 |
5 |
6 | 改写的android.support.v4.widget.SwipeRefreshLayout类,使其可以上拉加载更多数据。其中对android.support.v7.widget.RecyclerView支持更好一些,当然也支持listview;
7 |
8 | 其中ListViewDemoActivity.java是ListView的例子,点击“菜单”选择“RecyclerViewDemo”进入RecyclerViewDemoActivity.java,RecyclerViewDemoActivity.java是RecyclerView的例子。
9 |
--------------------------------------------------------------------------------
/SwipeRefreshLoadDemo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | applicationId "com.yeahis.testswiperefreshlayout"
9 | minSdkVersion 15
10 | targetSdkVersion 21
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:21.0.3'
25 | compile 'com.android.support:recyclerview-v7:21.0.3'
26 | compile 'com.android.support:support-v4:21.0.3'
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\android\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/zfeng/swiperefreshload/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.zfeng.swiperefreshload;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zfeng/swiperefreshload/CircleImageView.java:
--------------------------------------------------------------------------------
1 | package com.zfeng.swiperefreshload;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.RadialGradient;
9 | import android.graphics.Shader;
10 | import android.graphics.drawable.ShapeDrawable;
11 | import android.graphics.drawable.shapes.OvalShape;
12 | import android.support.v4.view.ViewCompat;
13 | import android.view.animation.Animation;
14 | import android.widget.ImageView;
15 |
16 | /**
17 | * Created by zfeng on 2015-2-26.
18 | */
19 | public class CircleImageView extends ImageView {
20 |
21 | private static final int KEY_SHADOW_COLOR = 0x1E000000;
22 | private static final int FILL_SHADOW_COLOR = 0x3D000000;
23 | // PX
24 | private static final float X_OFFSET = 0f;
25 | private static final float Y_OFFSET = 1.75f;
26 | private static final float SHADOW_RADIUS = 3.5f;
27 | private static final int SHADOW_ELEVATION = 4;
28 |
29 | private Animation.AnimationListener mListener;
30 | private int mShadowRadius;
31 |
32 | public CircleImageView(Context context, int color, final float radius) {
33 | super(context);
34 | final float density = getContext().getResources().getDisplayMetrics().density;
35 | final int diameter = (int) (radius * density * 2);
36 | final int shadowYOffset = (int) (density * Y_OFFSET);
37 | final int shadowXOffset = (int) (density * X_OFFSET);
38 |
39 | mShadowRadius = (int) (density * SHADOW_RADIUS);
40 |
41 | ShapeDrawable circle;
42 | if (elevationSupported()) {
43 | circle = new ShapeDrawable(new OvalShape());
44 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
45 | } else {
46 | OvalShape oval = new OvalShadow(mShadowRadius, diameter);
47 | circle = new ShapeDrawable(oval);
48 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
49 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
50 | KEY_SHADOW_COLOR);
51 | final int padding = (int) mShadowRadius;
52 | // set padding so the inner image sits correctly within the shadow.
53 | setPadding(padding, padding, padding, padding);
54 | }
55 | circle.getPaint().setColor(color);
56 | setBackgroundDrawable(circle);
57 | }
58 |
59 | private boolean elevationSupported() {
60 | return android.os.Build.VERSION.SDK_INT >= 21;
61 | }
62 |
63 | @Override
64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
66 | if (!elevationSupported()) {
67 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight()
68 | + mShadowRadius*2);
69 | }
70 | }
71 |
72 | public void setAnimationListener(Animation.AnimationListener listener) {
73 | mListener = listener;
74 | }
75 |
76 | @Override
77 | public void onAnimationStart() {
78 | super.onAnimationStart();
79 | if (mListener != null) {
80 | mListener.onAnimationStart(getAnimation());
81 | }
82 | }
83 |
84 | @Override
85 | public void onAnimationEnd() {
86 | super.onAnimationEnd();
87 | if (mListener != null) {
88 | mListener.onAnimationEnd(getAnimation());
89 | }
90 | }
91 |
92 | /**
93 | * Update the background color of the circle image view.
94 | */
95 | public void setBackgroundColor(int colorRes) {
96 | if (getBackground() instanceof ShapeDrawable) {
97 | final Resources res = getResources();
98 | ((ShapeDrawable) getBackground()).getPaint().setColor(res.getColor(colorRes));
99 | }
100 | }
101 |
102 | private class OvalShadow extends OvalShape {
103 | private RadialGradient mRadialGradient;
104 | private int mShadowRadius;
105 | private Paint mShadowPaint;
106 | private int mCircleDiameter;
107 |
108 | public OvalShadow(int shadowRadius, int circleDiameter) {
109 | super();
110 | mShadowPaint = new Paint();
111 | mShadowRadius = shadowRadius;
112 | mCircleDiameter = circleDiameter;
113 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2,
114 | mShadowRadius, new int[] {
115 | FILL_SHADOW_COLOR, Color.TRANSPARENT
116 | }, null, Shader.TileMode.CLAMP);
117 | mShadowPaint.setShader(mRadialGradient);
118 | }
119 |
120 | @Override
121 | public void draw(Canvas canvas, Paint paint) {
122 | final int viewWidth = CircleImageView.this.getWidth();
123 | final int viewHeight = CircleImageView.this.getHeight();
124 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius),
125 | mShadowPaint);
126 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint);
127 | }
128 | }
129 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/zfeng/swiperefreshload/ListViewDemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.zfeng.swiperefreshload;
2 |
3 | import android.content.Intent;
4 | import android.os.Handler;
5 | import android.support.v4.widget.SwipeRefreshLayout;
6 | import android.support.v7.app.ActionBarActivity;
7 | import android.os.Bundle;
8 | import android.view.LayoutInflater;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.BaseAdapter;
14 | import android.widget.ListView;
15 | import android.widget.TextView;
16 |
17 | import java.util.ArrayList;
18 |
19 | public class ListViewDemoActivity extends ActionBarActivity
20 | {
21 | private SwipeRefreshLayout sRLayout;
22 | private SwipeRefreshLoadLayout swipeRefreshLayout;
23 | private ListView listView;
24 | private TestBaseAdapter baseAdapter;
25 | private String[] arrays;
26 | private ArrayList arrayList;
27 |
28 |
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState)
31 | {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_main);
34 | swipeRefreshLayout=(SwipeRefreshLoadLayout)findViewById(R.id.main_swipe);
35 | listView=(ListView)findViewById(R.id.main_list);
36 | arrays=getResources().getStringArray(R.array.test_demo);
37 | arrayList=new ArrayList();
38 |
39 | for(int i=0;i mAnimators = new ArrayList();
86 |
87 | /** The indicator ring, used to manage animation state. */
88 | private final Ring mRing;
89 |
90 | /** Canvas rotation in degrees. */
91 | private float mRotation;
92 |
93 | /** Layout info for the arrowhead in dp */
94 | private static final int ARROW_WIDTH = 10;
95 | private static final int ARROW_HEIGHT = 5;
96 | private static final float ARROW_OFFSET_ANGLE = 5;
97 |
98 | /** Layout info for the arrowhead for the large spinner in dp */
99 | private static final int ARROW_WIDTH_LARGE = 12;
100 | private static final int ARROW_HEIGHT_LARGE = 6;
101 | private static final float MAX_PROGRESS_ARC = .8f;
102 |
103 | private Resources mResources;
104 | private View mParent;
105 | private Animation mAnimation;
106 | private float mRotationCount;
107 | private double mWidth;
108 | private double mHeight;
109 | boolean mFinishing;
110 |
111 | public MaterialProgressDrawable(Context context, View parent) {
112 | mParent = parent;
113 | mResources = context.getResources();
114 |
115 | mRing = new Ring(mCallback);
116 | mRing.setColors(COLORS);
117 |
118 | updateSizes(DEFAULT);
119 | setupAnimators();
120 | }
121 |
122 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
123 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
124 | final Ring ring = mRing;
125 | final DisplayMetrics metrics = mResources.getDisplayMetrics();
126 | final float screenDensity = metrics.density;
127 |
128 | mWidth = progressCircleWidth * screenDensity;
129 | mHeight = progressCircleHeight * screenDensity;
130 | ring.setStrokeWidth((float) strokeWidth * screenDensity);
131 | ring.setCenterRadius(centerRadius * screenDensity);
132 | ring.setColorIndex(0);
133 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);
134 | ring.setInsets((int) mWidth, (int) mHeight);
135 | }
136 |
137 | /**
138 | * Set the overall size for the progress spinner. This updates the radius
139 | * and stroke width of the ring.
140 | *
141 | */
142 | public void updateSizes(@ProgressDrawableSize int size) {
143 | if (size == LARGE) {
144 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE,
145 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE);
146 | } else {
147 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH,
148 | ARROW_WIDTH, ARROW_HEIGHT);
149 | }
150 | }
151 |
152 | /**
153 | * @param show Set to true to display the arrowhead on the progress spinner.
154 | */
155 | public void showArrow(boolean show) {
156 | mRing.setShowArrow(show);
157 | }
158 |
159 | /**
160 | * @param scale Set the scale of the arrowhead for the spinner.
161 | */
162 | public void setArrowScale(float scale) {
163 | mRing.setArrowScale(scale);
164 | }
165 |
166 | /**
167 | * Set the start and end trim for the progress spinner arc.
168 | *
169 | * @param startAngle start angle
170 | * @param endAngle end angle
171 | */
172 | public void setStartEndTrim(float startAngle, float endAngle) {
173 | mRing.setStartTrim(startAngle);
174 | mRing.setEndTrim(endAngle);
175 | }
176 |
177 | /**
178 | * Set the amount of rotation to apply to the progress spinner.
179 | *
180 | * @param rotation Rotation is from [0..1]
181 | */
182 | public void setProgressRotation(float rotation) {
183 | mRing.setRotation(rotation);
184 | }
185 |
186 | /**
187 | * Update the background color of the circle image view.
188 | */
189 | public void setBackgroundColor(int color) {
190 | mRing.setBackgroundColor(color);
191 | }
192 |
193 | /**
194 | * Set the colors used in the progress animation from color resources.
195 | * The first color will also be the color of the bar that grows in response
196 | * to a user swipe gesture.
197 | *
198 | * @param colors
199 | */
200 | public void setColorSchemeColors(int... colors) {
201 | mRing.setColors(colors);
202 | mRing.setColorIndex(0);
203 | }
204 |
205 | @Override
206 | public int getIntrinsicHeight() {
207 | return (int) mHeight;
208 | }
209 |
210 | @Override
211 | public int getIntrinsicWidth() {
212 | return (int) mWidth;
213 | }
214 |
215 | @Override
216 | public void draw(Canvas c) {
217 | final Rect bounds = getBounds();
218 | final int saveCount = c.save();
219 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
220 | mRing.draw(c, bounds);
221 | c.restoreToCount(saveCount);
222 | }
223 |
224 | @Override
225 | public void setAlpha(int alpha) {
226 | mRing.setAlpha(alpha);
227 | }
228 |
229 | public int getAlpha() {
230 | return mRing.getAlpha();
231 | }
232 |
233 | @Override
234 | public void setColorFilter(ColorFilter colorFilter) {
235 | mRing.setColorFilter(colorFilter);
236 | }
237 |
238 | @SuppressWarnings("unused")
239 | void setRotation(float rotation) {
240 | mRotation = rotation;
241 | invalidateSelf();
242 | }
243 |
244 | @SuppressWarnings("unused")
245 | private float getRotation() {
246 | return mRotation;
247 | }
248 |
249 | @Override
250 | public int getOpacity() {
251 | return PixelFormat.TRANSLUCENT;
252 | }
253 |
254 | @Override
255 | public boolean isRunning() {
256 | final ArrayList animators = mAnimators;
257 | final int N = animators.size();
258 | for (int i = 0; i < N; i++) {
259 | final Animation animator = animators.get(i);
260 | if (animator.hasStarted() && !animator.hasEnded()) {
261 | return true;
262 | }
263 | }
264 | return false;
265 | }
266 |
267 | @Override
268 | public void start() {
269 | mAnimation.reset();
270 | mRing.storeOriginals();
271 | // Already showing some part of the ring
272 | if (mRing.getEndTrim() != mRing.getStartTrim()) {
273 | mFinishing = true;
274 | mAnimation.setDuration(ANIMATION_DURATION/2);
275 | mParent.startAnimation(mAnimation);
276 | } else {
277 | mRing.setColorIndex(0);
278 | mRing.resetOriginals();
279 | mAnimation.setDuration(ANIMATION_DURATION);
280 | mParent.startAnimation(mAnimation);
281 | }
282 | }
283 |
284 | @Override
285 | public void stop() {
286 | mParent.clearAnimation();
287 | setRotation(0);
288 | mRing.setShowArrow(false);
289 | mRing.setColorIndex(0);
290 | mRing.resetOriginals();
291 | }
292 |
293 | private void applyFinishTranslation(float interpolatedTime, Ring ring) {
294 | // shrink back down and complete a full rotation before
295 | // starting other circles
296 | // Rotation goes between [0..1].
297 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
298 | + 1f);
299 | final float startTrim = ring.getStartingStartTrim()
300 | + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime;
301 | ring.setStartTrim(startTrim);
302 | final float rotation = ring.getStartingRotation()
303 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
304 | ring.setRotation(rotation);
305 | }
306 |
307 | private void setupAnimators() {
308 | final Ring ring = mRing;
309 | final Animation animation = new Animation() {
310 | @Override
311 | public void applyTransformation(float interpolatedTime, Transformation t) {
312 | if (mFinishing) {
313 | applyFinishTranslation(interpolatedTime, ring);
314 | } else {
315 | // The minProgressArc is calculated from 0 to create an
316 | // angle that
317 | // matches the stroke width.
318 | final float minProgressArc = (float) Math.toRadians(
319 | ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
320 | final float startingEndTrim = ring.getStartingEndTrim();
321 | final float startingTrim = ring.getStartingStartTrim();
322 | final float startingRotation = ring.getStartingRotation();
323 |
324 | // Offset the minProgressArc to where the endTrim is
325 | // located.
326 | final float minArc = MAX_PROGRESS_ARC - minProgressArc;
327 | final float endTrim = startingEndTrim + (minArc
328 | * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
329 | ring.setEndTrim(endTrim);
330 |
331 | final float startTrim = startingTrim + (MAX_PROGRESS_ARC
332 | * END_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
333 | ring.setStartTrim(startTrim);
334 |
335 | final float rotation = startingRotation + (0.25f * interpolatedTime);
336 | ring.setRotation(rotation);
337 |
338 | float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
339 | + (720.0f * (mRotationCount / NUM_POINTS));
340 | setRotation(groupRotation);
341 | }
342 | }
343 | };
344 | animation.setRepeatCount(Animation.INFINITE);
345 | animation.setRepeatMode(Animation.RESTART);
346 | animation.setInterpolator(LINEAR_INTERPOLATOR);
347 | animation.setAnimationListener(new Animation.AnimationListener() {
348 |
349 | @Override
350 | public void onAnimationStart(Animation animation) {
351 | mRotationCount = 0;
352 | }
353 |
354 | @Override
355 | public void onAnimationEnd(Animation animation) {
356 | // do nothing
357 | }
358 |
359 | @Override
360 | public void onAnimationRepeat(Animation animation) {
361 | ring.storeOriginals();
362 | ring.goToNextColor();
363 | ring.setStartTrim(ring.getEndTrim());
364 | if (mFinishing) {
365 | // finished closing the last ring from the swipe gesture; go
366 | // into progress mode
367 | mFinishing = false;
368 | animation.setDuration(ANIMATION_DURATION);
369 | ring.setShowArrow(false);
370 | } else {
371 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
372 | }
373 | }
374 | });
375 | mAnimation = animation;
376 | }
377 |
378 | private final Callback mCallback = new Callback() {
379 | @Override
380 | public void invalidateDrawable(Drawable d) {
381 | invalidateSelf();
382 | }
383 |
384 | @Override
385 | public void scheduleDrawable(Drawable d, Runnable what, long when) {
386 | scheduleSelf(what, when);
387 | }
388 |
389 | @Override
390 | public void unscheduleDrawable(Drawable d, Runnable what) {
391 | unscheduleSelf(what);
392 | }
393 | };
394 |
395 | private static class Ring {
396 | private final RectF mTempBounds = new RectF();
397 | private final Paint mPaint = new Paint();
398 | private final Paint mArrowPaint = new Paint();
399 |
400 | private final Callback mCallback;
401 |
402 | private float mStartTrim = 0.0f;
403 | private float mEndTrim = 0.0f;
404 | private float mRotation = 0.0f;
405 | private float mStrokeWidth = 5.0f;
406 | private float mStrokeInset = 2.5f;
407 |
408 | private int[] mColors;
409 | // mColorIndex represents the offset into the available mColors that the
410 | // progress circle should currently display. As the progress circle is
411 | // animating, the mColorIndex moves by one to the next available color.
412 | private int mColorIndex;
413 | private float mStartingStartTrim;
414 | private float mStartingEndTrim;
415 | private float mStartingRotation;
416 | private boolean mShowArrow;
417 | private Path mArrow;
418 | private float mArrowScale;
419 | private double mRingCenterRadius;
420 | private int mArrowWidth;
421 | private int mArrowHeight;
422 | private int mAlpha;
423 | private final Paint mCirclePaint = new Paint();
424 | private int mBackgroundColor;
425 |
426 | public Ring(Callback callback) {
427 | mCallback = callback;
428 |
429 | mPaint.setStrokeCap(Paint.Cap.SQUARE);
430 | mPaint.setAntiAlias(true);
431 | mPaint.setStyle(Style.STROKE);
432 |
433 | mArrowPaint.setStyle(Paint.Style.FILL);
434 | mArrowPaint.setAntiAlias(true);
435 | }
436 |
437 | public void setBackgroundColor(int color) {
438 | mBackgroundColor = color;
439 | }
440 |
441 | /**
442 | * Set the dimensions of the arrowhead.
443 | *
444 | * @param width Width of the hypotenuse of the arrow head
445 | * @param height Height of the arrow point
446 | */
447 | public void setArrowDimensions(float width, float height) {
448 | mArrowWidth = (int) width;
449 | mArrowHeight = (int) height;
450 | }
451 |
452 | /**
453 | * Draw the progress spinner
454 | */
455 | public void draw(Canvas c, Rect bounds) {
456 | final RectF arcBounds = mTempBounds;
457 | arcBounds.set(bounds);
458 | arcBounds.inset(mStrokeInset, mStrokeInset);
459 |
460 | final float startAngle = (mStartTrim + mRotation) * 360;
461 | final float endAngle = (mEndTrim + mRotation) * 360;
462 | float sweepAngle = endAngle - startAngle;
463 |
464 | mPaint.setColor(mColors[mColorIndex]);
465 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
466 |
467 | drawTriangle(c, startAngle, sweepAngle, bounds);
468 |
469 | if (mAlpha < 255) {
470 | mCirclePaint.setColor(mBackgroundColor);
471 | mCirclePaint.setAlpha(255 - mAlpha);
472 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
473 | mCirclePaint);
474 | }
475 | }
476 |
477 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
478 | if (mShowArrow) {
479 | if (mArrow == null) {
480 | mArrow = new android.graphics.Path();
481 | mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD);
482 | } else {
483 | mArrow.reset();
484 | }
485 |
486 | // Adjust the position of the triangle so that it is inset as
487 | // much as the arc, but also centered on the arc.
488 | float inset = (int) mStrokeInset / 2 * mArrowScale;
489 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
490 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
491 |
492 | // Update the path each time. This works around an issue in SKIA
493 | // where concatenating a rotation matrix to a scale matrix
494 | // ignored a starting negative rotation. This appears to have
495 | // been fixed as of API 21.
496 | mArrow.moveTo(0, 0);
497 | mArrow.lineTo(mArrowWidth * mArrowScale, 0);
498 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
499 | * mArrowScale));
500 | mArrow.offset(x - inset, y);
501 | mArrow.close();
502 | // draw a triangle
503 | mArrowPaint.setColor(mColors[mColorIndex]);
504 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
505 | bounds.exactCenterY());
506 | c.drawPath(mArrow, mArrowPaint);
507 | }
508 | }
509 |
510 | /**
511 | * Set the colors the progress spinner alternates between.
512 | *
513 | * @param colors Array of integers describing the colors. Must be non-null
.
514 | */
515 | public void setColors(@NonNull int[] colors) {
516 | mColors = colors;
517 | // if colors are reset, make sure to reset the color index as well
518 | setColorIndex(0);
519 | }
520 |
521 | /**
522 | * @param index Index into the color array of the color to display in
523 | * the progress spinner.
524 | */
525 | public void setColorIndex(int index) {
526 | mColorIndex = index;
527 | }
528 |
529 | /**
530 | * Proceed to the next available ring color. This will automatically
531 | * wrap back to the beginning of colors.
532 | */
533 | public void goToNextColor() {
534 | mColorIndex = (mColorIndex + 1) % (mColors.length);
535 | }
536 |
537 | public void setColorFilter(ColorFilter filter) {
538 | mPaint.setColorFilter(filter);
539 | invalidateSelf();
540 | }
541 |
542 | /**
543 | * @param alpha Set the alpha of the progress spinner and associated arrowhead.
544 | */
545 | public void setAlpha(int alpha) {
546 | mAlpha = alpha;
547 | }
548 |
549 | /**
550 | * @return Current alpha of the progress spinner and arrowhead.
551 | */
552 | public int getAlpha() {
553 | return mAlpha;
554 | }
555 |
556 | /**
557 | * @param strokeWidth Set the stroke width of the progress spinner in pixels.
558 | */
559 | public void setStrokeWidth(float strokeWidth) {
560 | mStrokeWidth = strokeWidth;
561 | mPaint.setStrokeWidth(strokeWidth);
562 | invalidateSelf();
563 | }
564 |
565 | @SuppressWarnings("unused")
566 | public float getStrokeWidth() {
567 | return mStrokeWidth;
568 | }
569 |
570 | @SuppressWarnings("unused")
571 | public void setStartTrim(float startTrim) {
572 | mStartTrim = startTrim;
573 | invalidateSelf();
574 | }
575 |
576 | @SuppressWarnings("unused")
577 | public float getStartTrim() {
578 | return mStartTrim;
579 | }
580 |
581 | public float getStartingStartTrim() {
582 | return mStartingStartTrim;
583 | }
584 |
585 | public float getStartingEndTrim() {
586 | return mStartingEndTrim;
587 | }
588 |
589 | @SuppressWarnings("unused")
590 | public void setEndTrim(float endTrim) {
591 | mEndTrim = endTrim;
592 | invalidateSelf();
593 | }
594 |
595 | @SuppressWarnings("unused")
596 | public float getEndTrim() {
597 | return mEndTrim;
598 | }
599 |
600 | @SuppressWarnings("unused")
601 | public void setRotation(float rotation) {
602 | mRotation = rotation;
603 | invalidateSelf();
604 | }
605 |
606 | @SuppressWarnings("unused")
607 | public float getRotation() {
608 | return mRotation;
609 | }
610 |
611 | public void setInsets(int width, int height) {
612 | final float minEdge = (float) Math.min(width, height);
613 | float insets;
614 | if (mRingCenterRadius <= 0 || minEdge < 0) {
615 | insets = (float) Math.ceil(mStrokeWidth / 2.0f);
616 | } else {
617 | insets = (float) (minEdge / 2.0f - mRingCenterRadius);
618 | }
619 | mStrokeInset = insets;
620 | }
621 |
622 | @SuppressWarnings("unused")
623 | public float getInsets() {
624 | return mStrokeInset;
625 | }
626 |
627 | /**
628 | * @param centerRadius Inner radius in px of the circle the progress
629 | * spinner arc traces.
630 | */
631 | public void setCenterRadius(double centerRadius) {
632 | mRingCenterRadius = centerRadius;
633 | }
634 |
635 | public double getCenterRadius() {
636 | return mRingCenterRadius;
637 | }
638 |
639 | /**
640 | * @param show Set to true to show the arrow head on the progress spinner.
641 | */
642 | public void setShowArrow(boolean show) {
643 | if (mShowArrow != show) {
644 | mShowArrow = show;
645 | invalidateSelf();
646 | }
647 | }
648 |
649 | /**
650 | * @param scale Set the scale of the arrowhead for the spinner.
651 | */
652 | public void setArrowScale(float scale) {
653 | if (scale != mArrowScale) {
654 | mArrowScale = scale;
655 | invalidateSelf();
656 | }
657 | }
658 |
659 | /**
660 | * @return The amount the progress spinner is currently rotated, between [0..1].
661 | */
662 | public float getStartingRotation() {
663 | return mStartingRotation;
664 | }
665 |
666 | /**
667 | * If the start / end trim are offset to begin with, store them so that
668 | * animation starts from that offset.
669 | */
670 | public void storeOriginals() {
671 | mStartingStartTrim = mStartTrim;
672 | mStartingEndTrim = mEndTrim;
673 | mStartingRotation = mRotation;
674 | }
675 |
676 | /**
677 | * Reset the progress spinner to default rotation, start and end angles.
678 | */
679 | public void resetOriginals() {
680 | mStartingStartTrim = 0;
681 | mStartingEndTrim = 0;
682 | mStartingRotation = 0;
683 | setStartTrim(0);
684 | setEndTrim(0);
685 | setRotation(0);
686 | }
687 |
688 | private void invalidateSelf() {
689 | mCallback.invalidateDrawable(null);
690 | }
691 | }
692 |
693 | /**
694 | * Squishes the interpolation curve into the second half of the animation.
695 | */
696 | private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
697 | @Override
698 | public float getInterpolation(float input) {
699 | return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
700 | }
701 | }
702 |
703 | /**
704 | * Squishes the interpolation curve into the first half of the animation.
705 | */
706 | private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
707 | @Override
708 | public float getInterpolation(float input) {
709 | return super.getInterpolation(Math.min(1, input * 2.0f));
710 | }
711 | }
712 | }
713 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zfeng/swiperefreshload/RecyclerViewDemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.zfeng.swiperefreshload;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.TextView;
12 |
13 | import java.util.ArrayList;
14 |
15 | /**
16 | * Created by zfeng on 2015-2-27.
17 | */
18 | public class RecyclerViewDemoActivity extends ActionBarActivity
19 | {
20 | private SwipeRefreshLoadLayout swipeRefreshLayout;
21 | private RecyclerView recyclerView;
22 |
23 | private String[] arrays;
24 | private ArrayList arrayList;
25 | private DemoAdapter demoAdapter;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState)
29 | {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_recycler);
32 |
33 | swipeRefreshLayout=(SwipeRefreshLoadLayout)findViewById(R.id.recycler_swipe);
34 | recyclerView=(RecyclerView)findViewById(R.id.recycler_list);
35 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
36 |
37 | arrays=getResources().getStringArray(R.array.test_demo);
38 | arrayList=new ArrayList();
39 |
40 | for(int i=0;i<20;++i)
41 | {
42 | arrayList.add(i+"");
43 | }
44 | demoAdapter=new DemoAdapter();
45 | recyclerView.setAdapter(demoAdapter);
46 |
47 | swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLoadLayout.OnRefreshListener() {
48 | @Override
49 | public void onRefresh() {
50 | refreshContent();
51 | }
52 | });
53 | swipeRefreshLayout.setLoadMoreListener(new SwipeRefreshLoadLayout.LoadMoreListener()
54 | {
55 | @Override
56 | public void loadMore()
57 | {
58 | loadMoreData();
59 | }
60 | });
61 | }
62 |
63 | private void refreshContent()
64 | {
65 | new Handler().postDelayed(new Runnable()
66 | {
67 | @Override
68 | public void run()
69 | {
70 | if(!arrayList.isEmpty())
71 | {
72 | arrayList.clear();
73 | }
74 | for(int i=0;i
102 | {
103 | class DemoViewHolder extends RecyclerView.ViewHolder
104 | {
105 | private TextView textView;
106 |
107 | public DemoViewHolder(View itemView)
108 | {
109 | super(itemView);
110 | this.textView=(TextView)itemView;
111 | }
112 |
113 | public void addDetails(int position)
114 | {
115 | textView.setText(arrayList.get(position));
116 | }
117 | }
118 |
119 | @Override
120 | public DemoViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
121 | {
122 | View view= LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false);
123 | return new DemoViewHolder(view);
124 | }
125 |
126 | @Override
127 | public void onBindViewHolder(DemoViewHolder holder, int position)
128 | {
129 | holder.addDetails(position);
130 | }
131 |
132 | @Override
133 | public int getItemCount()
134 | {
135 | return arrayList.size();
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zfeng/swiperefreshload/SwipeRefreshLoadLayout.java:
--------------------------------------------------------------------------------
1 | package com.zfeng.swiperefreshload;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.support.v4.view.MotionEventCompat;
7 | import android.support.v4.view.ViewCompat;
8 | import android.support.v4.widget.SwipeRefreshLayout;
9 | import android.support.v7.widget.LinearLayoutManager;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.util.AttributeSet;
12 | import android.util.DisplayMetrics;
13 | import android.util.Log;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.view.ViewConfiguration;
17 | import android.view.ViewGroup;
18 | import android.view.animation.Animation;
19 | import android.view.animation.DecelerateInterpolator;
20 | import android.view.animation.Transformation;
21 | import android.widget.AbsListView;
22 |
23 | /**
24 | * Created by zfeng on 2015-2-26.
25 | */
26 | public class SwipeRefreshLoadLayout extends ViewGroup {
27 | // Maps to ProgressBar.Large style
28 | public static final int LARGE = MaterialProgressDrawable.LARGE;
29 | // Maps to ProgressBar default style
30 | public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
31 |
32 | private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
33 |
34 | private static final int MAX_ALPHA = 255;
35 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
36 |
37 | private static final int CIRCLE_DIAMETER = 40;
38 | private static final int CIRCLE_DIAMETER_LARGE = 56;
39 |
40 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
41 | private static final int INVALID_POINTER = -1;
42 | private static final float DRAG_RATE = .5f;
43 |
44 | // Max amount of circle that can be filled by progress during swipe gesture,
45 | // where 1.0 is a full circle
46 | private static final float MAX_PROGRESS_ANGLE = .8f;
47 |
48 | private static final int SCALE_DOWN_DURATION = 150;
49 |
50 | private static final int ALPHA_ANIMATION_DURATION = 300;
51 |
52 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
53 |
54 | private static final int ANIMATE_TO_START_DURATION = 200;
55 |
56 | // Default background for the progress spinner
57 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
58 | // Default offset in dips from the top of the view to where the progress spinner should stop
59 | private static final int DEFAULT_CIRCLE_TARGET = 64;
60 |
61 | private View mTarget; // the target of the gesture
62 | private OnRefreshListener mListener;
63 | private LoadMoreListener mLoadMoreListener;
64 | private boolean mCanLoadMore=true;
65 | private boolean mRefreshing = false;
66 | private int mTouchSlop;
67 | private float mTotalDragDistance = -1;
68 | private int mMediumAnimationDuration;
69 | private int mCurrentTargetOffsetTop;
70 | // Whether or not the starting offset has been determined.
71 | private boolean mOriginalOffsetCalculated = false;
72 |
73 | private float mInitialMotionY;
74 | private boolean mIsBeingDragged;
75 | private int mActivePointerId = INVALID_POINTER;
76 | // Whether this item is scaled up rather than clipped
77 | private boolean mScale;
78 |
79 | // Target is returning to its start offset because it was cancelled or a
80 | // refresh was triggered.
81 | private boolean mReturningToStart;
82 | private final DecelerateInterpolator mDecelerateInterpolator;
83 | private static final int[] LAYOUT_ATTRS = new int[]{
84 | android.R.attr.enabled
85 | };
86 |
87 | private CircleImageView mCircleView;
88 | private int mCircleViewIndex = -1;
89 |
90 | protected int mFrom;
91 |
92 | private float mStartingScale;
93 |
94 | protected int mOriginalOffsetTop;
95 |
96 | private MaterialProgressDrawable mProgress;
97 |
98 | private Animation mScaleAnimation;
99 |
100 | private Animation mScaleDownAnimation;
101 |
102 | private Animation mAlphaStartAnimation;
103 |
104 | private Animation mAlphaMaxAnimation;
105 |
106 | private Animation mScaleDownToStartAnimation;
107 |
108 | private float mSpinnerFinalOffset;
109 |
110 | private boolean mNotify;
111 |
112 | private int mCircleWidth;
113 |
114 | private int mCircleHeight;
115 |
116 | // Whether the client has set a custom starting position;
117 | private boolean mUsingCustomStart;
118 |
119 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
120 | @Override
121 | public void onAnimationStart(Animation animation) {
122 | }
123 |
124 | @Override
125 | public void onAnimationRepeat(Animation animation) {
126 | }
127 |
128 | @Override
129 | public void onAnimationEnd(Animation animation) {
130 | if (mRefreshing) {
131 | // Make sure the progress view is fully visible
132 | mProgress.setAlpha(MAX_ALPHA);
133 | mProgress.start();
134 | if (mNotify) {
135 | if (mListener != null) {
136 | mListener.onRefresh();
137 | }
138 | }
139 | } else {
140 | mProgress.stop();
141 | mCircleView.setVisibility(View.GONE);
142 | setColorViewAlpha(MAX_ALPHA);
143 | // Return the circle to its start position
144 | if (mScale) {
145 | setAnimationProgress(0 /* animation complete and view is hidden */);
146 | } else {
147 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop,
148 | true /* requires update */);
149 | }
150 | }
151 | mCurrentTargetOffsetTop = mCircleView.getTop();
152 | }
153 | };
154 |
155 | private void setColorViewAlpha(int targetAlpha) {
156 | mCircleView.getBackground().setAlpha(targetAlpha);
157 | mProgress.setAlpha(targetAlpha);
158 | }
159 |
160 | /**
161 | * The refresh indicator starting and resting position is always positioned
162 | * near the top of the refreshing content. This position is a consistent
163 | * location, but can be adjusted in either direction based on whether or not
164 | * there is a toolbar or actionbar present.
165 | *
166 | * @param scale Set to true if there is no view at a higher z-order than
167 | * where the progress spinner is set to appear.
168 | * @param start The offset in pixels from the top of this view at which the
169 | * progress spinner should appear.
170 | * @param end The offset in pixels from the top of this view at which the
171 | * progress spinner should come to rest after a successful swipe
172 | * gesture.
173 | */
174 | public void setProgressViewOffset(boolean scale, int start, int end) {
175 | mScale = scale;
176 | mCircleView.setVisibility(View.GONE);
177 | mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
178 | mSpinnerFinalOffset = end;
179 | mUsingCustomStart = true;
180 | mCircleView.invalidate();
181 | }
182 |
183 | /**
184 | * The refresh indicator resting position is always positioned near the top
185 | * of the refreshing content. This position is a consistent location, but
186 | * can be adjusted in either direction based on whether or not there is a
187 | * toolbar or actionbar present.
188 | *
189 | * @param scale Set to true if there is no view at a higher z-order than
190 | * where the progress spinner is set to appear.
191 | * @param end The offset in pixels from the top of this view at which the
192 | * progress spinner should come to rest after a successful swipe
193 | * gesture.
194 | */
195 | public void setProgressViewEndTarget(boolean scale, int end) {
196 | mSpinnerFinalOffset = end;
197 | mScale = scale;
198 | mCircleView.invalidate();
199 | }
200 |
201 | /**
202 | * One of DEFAULT, or LARGE.
203 | */
204 | public void setSize(int size) {
205 | if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
206 | return;
207 | }
208 | final DisplayMetrics metrics = getResources().getDisplayMetrics();
209 | if (size == MaterialProgressDrawable.LARGE) {
210 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
211 | } else {
212 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
213 | }
214 | // force the bounds of the progress circle inside the circle view to
215 | // update by setting it to null before updating its size and then
216 | // re-setting it
217 | mCircleView.setImageDrawable(null);
218 | mProgress.updateSizes(size);
219 | mCircleView.setImageDrawable(mProgress);
220 | }
221 |
222 | /**
223 | * Simple constructor to use when creating a SwipeRefreshLayout from code.
224 | *
225 | * @param context
226 | */
227 | public SwipeRefreshLoadLayout(Context context) {
228 | this(context, null);
229 | }
230 |
231 | /**
232 | * Constructor that is called when inflating SwipeRefreshLayout from XML.
233 | *
234 | * @param context
235 | * @param attrs
236 | */
237 | public SwipeRefreshLoadLayout(Context context, AttributeSet attrs) {
238 | super(context, attrs);
239 |
240 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
241 |
242 | mMediumAnimationDuration = getResources().getInteger(
243 | android.R.integer.config_mediumAnimTime);
244 |
245 | setWillNotDraw(false);
246 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
247 |
248 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
249 | setEnabled(a.getBoolean(0, true));
250 | a.recycle();
251 |
252 | final DisplayMetrics metrics = getResources().getDisplayMetrics();
253 | mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
254 | mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
255 |
256 | createProgressView();
257 | ViewCompat.setChildrenDrawingOrderEnabled(this, true);
258 | // the absolute offset has to take into account that the circle starts at an offset
259 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
260 | mTotalDragDistance = mSpinnerFinalOffset;
261 | }
262 |
263 | protected int getChildDrawingOrder(int childCount, int i) {
264 | if (mCircleViewIndex < 0) {
265 | return i;
266 | } else if (i == childCount - 1) {
267 | // Draw the selected child last
268 | return mCircleViewIndex;
269 | } else if (i >= mCircleViewIndex) {
270 | // Move the children after the selected child earlier one
271 | return i + 1;
272 | } else {
273 | // Keep the children before the selected child the same
274 | return i;
275 | }
276 | }
277 |
278 | private void createProgressView() {
279 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2);
280 | mProgress = new MaterialProgressDrawable(getContext(), this);
281 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
282 | mCircleView.setImageDrawable(mProgress);
283 | mCircleView.setVisibility(View.GONE);
284 | addView(mCircleView);
285 | }
286 |
287 | /**
288 | * Set the listener to be notified when a refresh is triggered via the swipe
289 | * gesture.
290 | */
291 | public void setOnRefreshListener(OnRefreshListener listener) {
292 | mListener = listener;
293 | }
294 | public void setLoadMoreListener(LoadMoreListener listener){
295 | mLoadMoreListener=listener;
296 | }
297 |
298 | /**
299 | * Pre API 11, alpha is used to make the progress circle appear instead of scale.
300 | */
301 | private boolean isAlphaUsedForScale() {
302 | return android.os.Build.VERSION.SDK_INT < 11;
303 | }
304 |
305 | /**
306 | * Notify the widget that refresh state has changed. Do not call this when
307 | * refresh is triggered by a swipe gesture.
308 | *
309 | * @param refreshing Whether or not the view should show refresh progress.
310 | */
311 | public void setRefreshing(boolean refreshing) {
312 | if (refreshing && mRefreshing != refreshing) {
313 | // scale and show
314 | mRefreshing = refreshing;
315 | int endTarget = 0;
316 | if (!mUsingCustomStart) {
317 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);
318 | } else {
319 | endTarget = (int) mSpinnerFinalOffset;
320 | }
321 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,
322 | true /* requires update */);
323 | mNotify = false;
324 | startScaleUpAnimation(mRefreshListener);
325 | } else {
326 | setRefreshing(refreshing, false /* notify */);
327 | }
328 | }
329 | public void setLoadMore(boolean load)
330 | {
331 | mCanLoadMore=!load;
332 | }
333 |
334 | private void startScaleUpAnimation(Animation.AnimationListener listener) {
335 | mCircleView.setVisibility(View.VISIBLE);
336 | if (android.os.Build.VERSION.SDK_INT >= 11) {
337 | // Pre API 11, alpha is used in place of scale up to show the
338 | // progress circle appearing.
339 | // Don't adjust the alpha during appearance otherwise.
340 | mProgress.setAlpha(MAX_ALPHA);
341 | }
342 | mScaleAnimation = new Animation() {
343 | @Override
344 | public void applyTransformation(float interpolatedTime, Transformation t) {
345 | setAnimationProgress(interpolatedTime);
346 | }
347 | };
348 | mScaleAnimation.setDuration(mMediumAnimationDuration);
349 | if (listener != null) {
350 | mCircleView.setAnimationListener(listener);
351 | }
352 | mCircleView.clearAnimation();
353 | mCircleView.startAnimation(mScaleAnimation);
354 | }
355 |
356 | /**
357 | * Pre API 11, this does an alpha animation.
358 | *
359 | * @param progress
360 | */
361 | private void setAnimationProgress(float progress) {
362 | if (isAlphaUsedForScale()) {
363 | setColorViewAlpha((int) (progress * MAX_ALPHA));
364 | } else {
365 | ViewCompat.setScaleX(mCircleView, progress);
366 | ViewCompat.setScaleY(mCircleView, progress);
367 | }
368 | }
369 |
370 | private void setRefreshing(boolean refreshing, final boolean notify) {
371 | if (mRefreshing != refreshing) {
372 | mNotify = notify;
373 | ensureTarget();
374 | mRefreshing = refreshing;
375 | if (mRefreshing) {
376 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
377 | } else {
378 | startScaleDownAnimation(mRefreshListener);
379 | }
380 | }
381 | }
382 |
383 | private void startScaleDownAnimation(Animation.AnimationListener listener) {
384 | mScaleDownAnimation = new Animation() {
385 | @Override
386 | public void applyTransformation(float interpolatedTime, Transformation t) {
387 | setAnimationProgress(1 - interpolatedTime);
388 | }
389 | };
390 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
391 | mCircleView.setAnimationListener(listener);
392 | mCircleView.clearAnimation();
393 | mCircleView.startAnimation(mScaleDownAnimation);
394 | }
395 |
396 | private void startProgressAlphaStartAnimation() {
397 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);
398 | }
399 |
400 | private void startProgressAlphaMaxAnimation() {
401 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);
402 | }
403 |
404 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) {
405 | // Pre API 11, alpha is used in place of scale. Don't also use it to
406 | // show the trigger point.
407 | if (mScale && isAlphaUsedForScale()) {
408 | return null;
409 | }
410 | Animation alpha = new Animation() {
411 | @Override
412 | public void applyTransformation(float interpolatedTime, Transformation t) {
413 | mProgress
414 | .setAlpha((int) (startingAlpha + ((endingAlpha - startingAlpha)
415 | * interpolatedTime)));
416 | }
417 | };
418 | alpha.setDuration(ALPHA_ANIMATION_DURATION);
419 | // Clear out the previous animation listeners.
420 | mCircleView.setAnimationListener(null);
421 | mCircleView.clearAnimation();
422 | mCircleView.startAnimation(alpha);
423 | return alpha;
424 | }
425 |
426 | /**
427 | * Set the background color of the progress spinner disc.
428 | *
429 | * @param colorRes Resource id of the color.
430 | */
431 | public void setProgressBackgroundColor(int colorRes) {
432 | mCircleView.setBackgroundColor(colorRes);
433 | mProgress.setBackgroundColor(getResources().getColor(colorRes));
434 | }
435 |
436 | /**
437 | * @deprecated Use {@link #setColorSchemeResources(int...)}
438 | */
439 | @Deprecated
440 | public void setColorScheme(int... colors) {
441 | setColorSchemeResources(colors);
442 | }
443 |
444 | /**
445 | * Set the color resources used in the progress animation from color resources.
446 | * The first color will also be the color of the bar that grows in response
447 | * to a user swipe gesture.
448 | *
449 | * @param colorResIds
450 | */
451 | public void setColorSchemeResources(int... colorResIds) {
452 | final Resources res = getResources();
453 | int[] colorRes = new int[colorResIds.length];
454 | for (int i = 0; i < colorResIds.length; i++) {
455 | colorRes[i] = res.getColor(colorResIds[i]);
456 | }
457 | setColorSchemeColors(colorRes);
458 | }
459 |
460 | /**
461 | * Set the colors used in the progress animation. The first
462 | * color will also be the color of the bar that grows in response to a user
463 | * swipe gesture.
464 | *
465 | * @param colors
466 | */
467 | public void setColorSchemeColors(int... colors) {
468 | ensureTarget();
469 | mProgress.setColorSchemeColors(colors);
470 | }
471 |
472 | /**
473 | * @return Whether the SwipeRefreshWidget is actively showing refresh
474 | * progress.
475 | */
476 | public boolean isRefreshing() {
477 | return mRefreshing;
478 | }
479 |
480 | private void ensureTarget() {
481 | // Don't bother getting the parent height if the parent hasn't been laid
482 | // out yet.
483 | if (mTarget == null) {
484 | for (int i = 0; i < getChildCount(); i++) {
485 | View child = getChildAt(i);
486 | if (!child.equals(mCircleView)) {
487 | mTarget = child;
488 | break;
489 | }
490 | }
491 | }
492 | }
493 |
494 | /**
495 | * Set the distance to trigger a sync in dips
496 | *
497 | * @param distance
498 | */
499 | public void setDistanceToTriggerSync(int distance) {
500 | mTotalDragDistance = distance;
501 | }
502 |
503 | @Override
504 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
505 | final int width = getMeasuredWidth();
506 | final int height = getMeasuredHeight();
507 | if (getChildCount() == 0) {
508 | return;
509 | }
510 | if (mTarget == null) {
511 | ensureTarget();
512 | }
513 | if (mTarget == null) {
514 | return;
515 | }
516 | final View child = mTarget;
517 | final int childLeft = getPaddingLeft();
518 | final int childTop = getPaddingTop();
519 | final int childWidth = width - getPaddingLeft() - getPaddingRight();
520 | final int childHeight = height - getPaddingTop() - getPaddingBottom();
521 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
522 | int circleWidth = mCircleView.getMeasuredWidth();
523 | int circleHeight = mCircleView.getMeasuredHeight();
524 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
525 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
526 | }
527 |
528 | @Override
529 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
530 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
531 | if (mTarget == null) {
532 | ensureTarget();
533 | }
534 | if (mTarget == null) {
535 | return;
536 | }
537 | mTarget.measure(MeasureSpec.makeMeasureSpec(
538 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
539 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
540 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
541 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
542 | MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
543 | if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
544 | mOriginalOffsetCalculated = true;
545 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
546 | }
547 | mCircleViewIndex = -1;
548 | // Get the index of the circleview.
549 | for (int index = 0; index < getChildCount(); index++) {
550 | if (getChildAt(index) == mCircleView) {
551 | mCircleViewIndex = index;
552 | break;
553 | }
554 | }
555 | }
556 |
557 | /**
558 | * @return Whether it is possible for the child view of this layout to
559 | * scroll up. Override this if the child view is a custom view.
560 | */
561 | public boolean canChildScrollUp() {
562 | if (android.os.Build.VERSION.SDK_INT < 14) {
563 | if (mTarget instanceof AbsListView) {
564 | final AbsListView absListView = (AbsListView) mTarget;
565 | return absListView.getChildCount() > 0
566 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
567 | .getTop() < absListView.getPaddingTop());
568 | } else {
569 | return mTarget.getScrollY() > 0;
570 | }
571 | } else {
572 | boolean bool = ViewCompat.canScrollVertically(mTarget, -1);
573 | return bool;
574 | }
575 | }
576 |
577 | @Override
578 | public boolean onInterceptTouchEvent(MotionEvent ev) {
579 | ensureTarget();
580 |
581 | final int action = MotionEventCompat.getActionMasked(ev);
582 |
583 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
584 | mReturningToStart = false;
585 | }
586 |
587 | if (!isEnabled() || mReturningToStart || mRefreshing) {
588 | // Fail fast if we're not in a state where a swipe is possible
589 | return false;
590 | }
591 |
592 | switch (action) {
593 | case MotionEvent.ACTION_DOWN:
594 | if (canChildScrollUp()) {
595 | } else {
596 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
597 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
598 | mIsBeingDragged = false;
599 | final float initialMotionY = getMotionEventY(ev, mActivePointerId);
600 | if (initialMotionY == -1) {
601 | return false;
602 | }
603 | mInitialMotionY = initialMotionY;
604 | }
605 |
606 | case MotionEvent.ACTION_MOVE:
607 | if (canChildScrollUp()) {
608 | } else {
609 | if (mActivePointerId == INVALID_POINTER) {
610 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
611 | return false;
612 | }
613 |
614 | final float y = getMotionEventY(ev, mActivePointerId);
615 | if (y == -1) {
616 | return false;
617 | }
618 | final float yDiff = y - mInitialMotionY;
619 | if (yDiff > mTouchSlop && !mIsBeingDragged) {
620 | mIsBeingDragged = true;
621 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
622 | }
623 | break;
624 | }
625 |
626 | case MotionEventCompat.ACTION_POINTER_UP:
627 | if (canChildScrollUp()) {
628 | } else {
629 | onSecondaryPointerUp(ev);
630 | break;
631 | }
632 |
633 | case MotionEvent.ACTION_UP:
634 | case MotionEvent.ACTION_CANCEL:
635 | if (canChildScrollUp())
636 | {
637 | if(mTarget instanceof AbsListView)
638 | {
639 | final AbsListView absListView = (AbsListView) mTarget;
640 |
641 | int lastPosition=absListView.getLastVisiblePosition();
642 | if(lastPosition+1==absListView.getCount())
643 | {
644 | if(mCanLoadMore)
645 | {
646 | mCanLoadMore=false;
647 | mLoadMoreListener.loadMore();
648 | }
649 | }
650 | }else if(mTarget instanceof RecyclerView)
651 | {
652 | final RecyclerView recyclerView=(RecyclerView)mTarget;
653 | LinearLayoutManager lLManager=(LinearLayoutManager)recyclerView.getLayoutManager();
654 | if((lLManager.findLastVisibleItemPosition()+1)==lLManager.getItemCount())
655 | {
656 | if(mCanLoadMore)
657 | {
658 | mCanLoadMore=false;
659 | mLoadMoreListener.loadMore();
660 | }
661 | }
662 | }
663 | } else {
664 | mIsBeingDragged = false;
665 | mActivePointerId = INVALID_POINTER;
666 | break;
667 | }
668 | }
669 |
670 | return mIsBeingDragged;
671 | }
672 |
673 | private float getMotionEventY(MotionEvent ev, int activePointerId) {
674 | final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
675 | if (index < 0) {
676 | return -1;
677 | }
678 | return MotionEventCompat.getY(ev, index);
679 | }
680 |
681 | @Override
682 | public void requestDisallowInterceptTouchEvent(boolean b) {
683 | // Nope.
684 | }
685 |
686 | private boolean isAnimationRunning(Animation animation) {
687 | return animation != null && animation.hasStarted() && !animation.hasEnded();
688 | }
689 |
690 | @Override
691 | public boolean onTouchEvent(MotionEvent ev) {
692 | final int action = MotionEventCompat.getActionMasked(ev);
693 |
694 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
695 | mReturningToStart = false;
696 | }
697 |
698 | if (!isEnabled() || mReturningToStart || canChildScrollUp()) {
699 | // Fail fast if we're not in a state where a swipe is possible
700 | return false;
701 | }
702 |
703 | switch (action) {
704 | case MotionEvent.ACTION_DOWN:
705 |
706 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
707 | mIsBeingDragged = false;
708 | break;
709 |
710 | case MotionEvent.ACTION_MOVE: {
711 |
712 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
713 | if (pointerIndex < 0) {
714 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
715 | return false;
716 | }
717 |
718 | final float y = MotionEventCompat.getY(ev, pointerIndex);
719 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
720 | if (mIsBeingDragged) {
721 | mProgress.showArrow(true);
722 | float originalDragPercent = overscrollTop / mTotalDragDistance;
723 | if (originalDragPercent < 0) {
724 | return false;
725 | }
726 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
727 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
728 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
729 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset
730 | - mOriginalOffsetTop : mSpinnerFinalOffset;
731 | float tensionSlingshotPercent = Math.max(0,
732 | Math.min(extraOS, slingshotDist * 2) / slingshotDist);
733 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
734 | (tensionSlingshotPercent / 4), 2)) * 2f;
735 | float extraMove = (slingshotDist) * tensionPercent * 2;
736 |
737 | int targetY = mOriginalOffsetTop
738 | + (int) ((slingshotDist * dragPercent) + extraMove);
739 | // where 1.0f is a full circle
740 | if (mCircleView.getVisibility() != View.VISIBLE) {
741 | mCircleView.setVisibility(View.VISIBLE);
742 | }
743 | if (!mScale) {
744 | ViewCompat.setScaleX(mCircleView, 1f);
745 | ViewCompat.setScaleY(mCircleView, 1f);
746 | }
747 | if (overscrollTop < mTotalDragDistance) {
748 | if (mScale) {
749 | setAnimationProgress(overscrollTop / mTotalDragDistance);
750 | }
751 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
752 | && !isAnimationRunning(mAlphaStartAnimation)) {
753 | // Animate the alpha
754 | startProgressAlphaStartAnimation();
755 | }
756 | float strokeStart = (float) (adjustedPercent * .8f);
757 | mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
758 | mProgress.setArrowScale(Math.min(1f, adjustedPercent));
759 | } else {
760 | if (mProgress.getAlpha() < MAX_ALPHA
761 | && !isAnimationRunning(mAlphaMaxAnimation)) {
762 | // Animate the alpha
763 | startProgressAlphaMaxAnimation();
764 | }
765 | }
766 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
767 | mProgress.setProgressRotation(rotation);
768 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop,
769 | true /* requires update */);
770 | }
771 | break;
772 | }
773 | case MotionEventCompat.ACTION_POINTER_DOWN: {
774 |
775 | final int index = MotionEventCompat.getActionIndex(ev);
776 | mActivePointerId = MotionEventCompat.getPointerId(ev, index);
777 | break;
778 | }
779 |
780 | case MotionEventCompat.ACTION_POINTER_UP:
781 |
782 | onSecondaryPointerUp(ev);
783 | break;
784 |
785 | case MotionEvent.ACTION_UP:
786 | case MotionEvent.ACTION_CANCEL: {
787 |
788 | if (mActivePointerId == INVALID_POINTER) {
789 | if (action == MotionEvent.ACTION_UP) {
790 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
791 | }
792 | return false;
793 | }
794 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
795 | final float y = MotionEventCompat.getY(ev, pointerIndex);
796 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
797 | mIsBeingDragged = false;
798 | if (overscrollTop > mTotalDragDistance) {
799 | setRefreshing(true, true /* notify */);
800 | } else {
801 | // cancel refresh
802 | mRefreshing = false;
803 | mProgress.setStartEndTrim(0f, 0f);
804 | Animation.AnimationListener listener = null;
805 | if (!mScale) {
806 | listener = new Animation.AnimationListener() {
807 |
808 | @Override
809 | public void onAnimationStart(Animation animation) {
810 | }
811 |
812 | @Override
813 | public void onAnimationEnd(Animation animation) {
814 | if (!mScale) {
815 | startScaleDownAnimation(null);
816 | }
817 | }
818 |
819 | @Override
820 | public void onAnimationRepeat(Animation animation) {
821 | }
822 |
823 | };
824 | }
825 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
826 | mProgress.showArrow(false);
827 | }
828 | mActivePointerId = INVALID_POINTER;
829 | return false;
830 | }
831 |
832 | }
833 |
834 | return true;
835 | }
836 |
837 | private void animateOffsetToCorrectPosition(int from, Animation.AnimationListener listener) {
838 | mFrom = from;
839 | mAnimateToCorrectPosition.reset();
840 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
841 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
842 | if (listener != null) {
843 | mCircleView.setAnimationListener(listener);
844 | }
845 | mCircleView.clearAnimation();
846 | mCircleView.startAnimation(mAnimateToCorrectPosition);
847 | }
848 |
849 | private void animateOffsetToStartPosition(int from, Animation.AnimationListener listener) {
850 | if (mScale) {
851 | // Scale the item back down
852 | startScaleDownReturnToStartAnimation(from, listener);
853 | } else {
854 | mFrom = from;
855 | mAnimateToStartPosition.reset();
856 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
857 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
858 | if (listener != null) {
859 | mCircleView.setAnimationListener(listener);
860 | }
861 | mCircleView.clearAnimation();
862 | mCircleView.startAnimation(mAnimateToStartPosition);
863 | }
864 | }
865 |
866 | private final Animation mAnimateToCorrectPosition = new Animation() {
867 | @Override
868 | public void applyTransformation(float interpolatedTime, Transformation t) {
869 | int targetTop = 0;
870 | int endTarget = 0;
871 | if (!mUsingCustomStart) {
872 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));
873 | } else {
874 | endTarget = (int) mSpinnerFinalOffset;
875 | }
876 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
877 | int offset = targetTop - mCircleView.getTop();
878 | setTargetOffsetTopAndBottom(offset, false /* requires update */);
879 | }
880 | };
881 |
882 | private void moveToStart(float interpolatedTime) {
883 | int targetTop = 0;
884 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
885 | int offset = targetTop - mCircleView.getTop();
886 | setTargetOffsetTopAndBottom(offset, false /* requires update */);
887 | }
888 |
889 | private final Animation mAnimateToStartPosition = new Animation() {
890 | @Override
891 | public void applyTransformation(float interpolatedTime, Transformation t) {
892 | moveToStart(interpolatedTime);
893 | }
894 | };
895 |
896 | private void startScaleDownReturnToStartAnimation(int from,
897 | Animation.AnimationListener listener) {
898 | mFrom = from;
899 | if (isAlphaUsedForScale()) {
900 | mStartingScale = mProgress.getAlpha();
901 | } else {
902 | mStartingScale = ViewCompat.getScaleX(mCircleView);
903 | }
904 | mScaleDownToStartAnimation = new Animation() {
905 | @Override
906 | public void applyTransformation(float interpolatedTime, Transformation t) {
907 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime));
908 | setAnimationProgress(targetScale);
909 | moveToStart(interpolatedTime);
910 | }
911 | };
912 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);
913 | if (listener != null) {
914 | mCircleView.setAnimationListener(listener);
915 | }
916 | mCircleView.clearAnimation();
917 | mCircleView.startAnimation(mScaleDownToStartAnimation);
918 | }
919 |
920 | private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
921 | mCircleView.bringToFront();
922 | mCircleView.offsetTopAndBottom(offset);
923 | mCurrentTargetOffsetTop = mCircleView.getTop();
924 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
925 | invalidate();
926 | }
927 | }
928 |
929 | private void onSecondaryPointerUp(MotionEvent ev) {
930 | final int pointerIndex = MotionEventCompat.getActionIndex(ev);
931 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
932 | if (pointerId == mActivePointerId) {
933 | // This was our active pointer going up. Choose a new
934 | // active pointer and adjust accordingly.
935 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
936 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
937 | }
938 | int test = mActivePointerId;
939 | }
940 |
941 | /**
942 | * Classes that wish to be notified when the swipe gesture correctly
943 | * triggers a refresh should implement this interface.
944 | */
945 | public interface OnRefreshListener {
946 | public void onRefresh();
947 | }
948 |
949 | /**
950 | * @author zfeng
951 | * Classes that wish to be notified when the swipe gesture correctly
952 | * triggers a load should implement this interface.
953 | */
954 | public interface LoadMoreListener{
955 | public void loadMore();
956 | }
957 | }
958 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_recycler.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndyZhaofeng/SwipeRefreshLoadDemo/4a8f496a2a52af88a5c055f379072499cf114284/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndyZhaofeng/SwipeRefreshLoadDemo/4a8f496a2a52af88a5c055f379072499cf114284/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndyZhaofeng/SwipeRefreshLoadDemo/4a8f496a2a52af88a5c055f379072499cf114284/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndyZhaofeng/SwipeRefreshLoadDemo/4a8f496a2a52af88a5c055f379072499cf114284/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #28a9ea
4 | #212121
5 | #7FFF00
6 | #8A2BE2
7 | #7FFFD4
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TestSwipeRefreshLayout
3 |
4 | Hello world!
5 | Settings
6 |
7 |
8 | - a
9 | - b
10 | - c
11 | - d
12 | - e
13 | - f
14 | - g
15 | - h
16 | - i
17 | - j
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
28 |
29 |
30 |
38 |
39 |
40 |
45 |
46 |
47 |
52 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndyZhaofeng/SwipeRefreshLoadDemo/4a8f496a2a52af88a5c055f379072499cf114284/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------