animators = mAnimators;
173 | final int N = animators.size();
174 | for (int i = 0; i < N; i++) {
175 | final Animation animator = animators.get(i);
176 | if (animator.hasStarted() && !animator.hasEnded()) {
177 | return true;
178 | }
179 | }
180 | return false;
181 | }
182 |
183 | @Override
184 | public void draw(Canvas canvas) {
185 | final Rect bounds = getBounds();
186 | final int saveCount = canvas.save();
187 | canvas.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
188 | mSmile.draw(canvas, bounds);
189 | canvas.restoreToCount(saveCount);
190 | }
191 |
192 | @Override
193 | public void setAlpha(int alpha) {
194 |
195 | }
196 |
197 | @Override
198 | public void setColorFilter(ColorFilter colorFilter) {
199 |
200 | }
201 |
202 | public int getAlpha() {
203 | return mSmile.getAlpha();
204 | }
205 |
206 |
207 | @Override
208 | public int getOpacity() {
209 | return 0;
210 | }
211 |
212 | public void setPercentage(float v) {
213 | mSmile.setAnimatedValue(v);
214 | }
215 |
216 |
217 | @SuppressWarnings("unused")
218 | private float getRotation() {
219 | return mRotation;
220 | }
221 |
222 | /**
223 | * Update the ring color if this is within the last 25% of the animation.
224 | * The new ring color will be a translation from the starting ring color to
225 | * the next color.
226 | */
227 | void updateRingColor(float interpolatedTime, Smile smile) {
228 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
229 | Logger.d(interpolatedTime);
230 | // scale the interpolatedTime so that the full
231 | // transformation from 0 - 1 takes place in the
232 | // remaining time
233 | int color = evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
234 | / (1.0f - COLOR_START_DELAY_OFFSET), smile.getStartingColor(),
235 | smile.getNextColor());
236 | smile.setColor(color);
237 | }
238 | }
239 |
240 | // Adapted from ArgbEvaluator.java
241 | private int evaluateColorChange(float fraction, int startValue, int endValue) {
242 | int startInt = (Integer) startValue;
243 | int startA = (startInt >> 24) & 0xff;
244 | int startR = (startInt >> 16) & 0xff;
245 | int startG = (startInt >> 8) & 0xff;
246 | int startB = startInt & 0xff;
247 |
248 | int endInt = (Integer) endValue;
249 | int endA = (endInt >> 24) & 0xff;
250 | int endR = (endInt >> 16) & 0xff;
251 | int endG = (endInt >> 8) & 0xff;
252 | int endB = endInt & 0xff;
253 |
254 | return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
255 | | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
256 | | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
257 | | (int) ((startB + (int) (fraction * (endB - startB))));
258 | }
259 |
260 |
261 |
262 | /**
263 | * @param strokeWidth Set the stroke width of the progress spinner in pixels.
264 | */
265 | public void setStrokeWidth(float strokeWidth) {
266 | mSmile.setStrokeWidth(strokeWidth);
267 | }
268 |
269 |
270 | private static class Smile {
271 | private final RectF mTempBounds = new RectF();
272 | private final Paint mPaint = new Paint();
273 | private final Callback mCallback;
274 |
275 | private int[] mColors = {
276 | Color.BLACK
277 | };
278 |
279 | private int mColorIndex;
280 | private int mCurrentColor = Color.BLACK;
281 |
282 | public static final float ANIMATED_VALUE_MAX = 855;
283 | private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
284 | private int mUsedValue;
285 | private int mc;
286 |
287 | public int getAnimatedValue() {
288 | return animatedValue;
289 | }
290 |
291 | int animatedValue;
292 | private float mStrokeWidth;
293 | private int mAlpha;
294 |
295 | public Smile(Context context,Callback callback) {
296 | this.mCallback = callback;
297 | mPaint.setStyle(Paint.Style.STROKE); // 画笔类型
298 | mPaint.setAntiAlias(true); // 抗锯齿
299 | mPaint.setStrokeCap(Paint.Cap.ROUND);//圆角笔触
300 | mStrokeWidth = Utils.convertDpToPixel(context, (int) 4);
301 | mPaint.setStrokeWidth(mStrokeWidth);
302 |
303 | }
304 |
305 | /**
306 | * Set the colors the progress spinner alternates between.
307 | *
308 | * @param colors Array of integers describing the colors. Must be non-null.
309 | */
310 | public void setColors(@NonNull int[] colors) {
311 | mColors = colors;
312 | // if colors are reset, make sure to reset the color index as well
313 | setColorIndex(0);
314 | }
315 |
316 | /**
317 | * @return int describing the next color the progress spinner should use when drawing.
318 | */
319 | public int getNextColor() {
320 | return mColors[getNextColorIndex()];
321 | }
322 |
323 | private int getNextColorIndex() {
324 | return (mColorIndex + 1) % (mColors.length);
325 | }
326 |
327 | /**
328 | * Proceed to the next available ring color. This will automatically
329 | * wrap back to the beginning of colors.
330 | */
331 | public void goToNextColor() {
332 | setColorIndex(getNextColorIndex());
333 | }
334 |
335 |
336 | public int getAlpha() {
337 | return mAlpha;
338 | }
339 |
340 | /**
341 | * @param alpha Set the alpha of the progress spinner and associated arrowhead.
342 | */
343 | public void setAlpha(int alpha) {
344 | mAlpha = alpha;
345 | }
346 |
347 |
348 | /**
349 | * Set the absolute color of the progress spinner. This is should only
350 | * be used when animating between current and next color when the
351 | * spinner is rotating.
352 | *
353 | * @param color int describing the color.
354 | */
355 | public void setColor(int color) {
356 | mCurrentColor = color;
357 | }
358 |
359 |
360 | public void setColorIndex(int colorIndex) {
361 | mColorIndex = colorIndex;
362 | mCurrentColor = mColors[mColorIndex];
363 |
364 | }
365 |
366 |
367 | /**
368 | * @param strokeWidth Set the stroke width of the progress spinner in pixels.
369 | */
370 | public void setStrokeWidth(float strokeWidth) {
371 | mStrokeWidth = strokeWidth;
372 | mPaint.setStrokeWidth(strokeWidth);
373 | }
374 |
375 |
376 | public void draw(Canvas canvas, Rect bounds) {
377 | final RectF arcBounds = mTempBounds;
378 | arcBounds.set(bounds);
379 | canvas.translate(arcBounds.width() / 2, arcBounds.height() / 2);
380 |
381 | mPaint.setColor(mCurrentColor);
382 | smileAnimator(canvas, mPaint, arcBounds);
383 |
384 | }
385 |
386 |
387 | private void smileAnimator(Canvas canvas, Paint mPaint, RectF bounds) {
388 | float point = Math.min(bounds.width(), bounds.height()) * 0.4f / 2;
389 | float r = point * (float) Math.sqrt(2);
390 | RectF rectF = new RectF(-r, -r, r, r);
391 | canvas.save();
392 |
393 | // rotate
394 | if (animatedValue >= 135) {
395 | canvas.rotate(animatedValue - 135);
396 | }
397 |
398 | // draw mouth
399 | float startAngle = 0, sweepAngle = 0;
400 | if (animatedValue < 135) {
401 | startAngle = animatedValue + 5;
402 | sweepAngle = 170 + animatedValue / 3;
403 | } else if (animatedValue < 270) {
404 | startAngle = 135 + 5;
405 | sweepAngle = 170 + animatedValue / 3;
406 | } else if (animatedValue < 630) {
407 | startAngle = 135 + 5;
408 | sweepAngle = 260 - (animatedValue - 270) / 5;
409 | } else if (animatedValue < 720) {
410 | startAngle = 135 - (animatedValue - 630) / 2 + 5;
411 | sweepAngle = 260 - (animatedValue - 270) / 5;
412 | } else {
413 | startAngle = 135 - (animatedValue - 630) / 2 - (animatedValue - 720) / 6 + 5;
414 | sweepAngle = 170;
415 | }
416 | canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaint);
417 |
418 | // draw eye
419 | canvas.drawPoints(new float[]{
420 | -point, -point
421 | , point, -point
422 | }, mPaint);
423 |
424 | canvas.restore();
425 | }
426 |
427 | public void setAnimatedValue(float v) {
428 | mUsedValue = animatedValue;
429 | if (v <= 1) // 百分比
430 | this.animatedValue = (int) (ANIMATED_VALUE_MAX * v);
431 | else // 动画的值
432 | this.animatedValue = (int) v;
433 | if (mUsedValue != animatedValue)
434 | invalidateSelf();
435 | }
436 |
437 | private void invalidateSelf() {
438 | mCallback.invalidateDrawable(null);
439 | }
440 |
441 | public int getStartingColor() {
442 | return mColors[mColorIndex];
443 | }
444 | }
445 |
446 | private final Callback mCallback = new Callback() {
447 | @Override
448 | public void invalidateDrawable(Drawable d) {
449 | invalidateSelf();
450 | }
451 |
452 | @Override
453 | public void scheduleDrawable(Drawable d, Runnable what, long when) {
454 | scheduleSelf(what, when);
455 | }
456 |
457 | @Override
458 | public void unscheduleDrawable(Drawable d, Runnable what) {
459 | unscheduleSelf(what);
460 | }
461 | };
462 |
463 |
464 | }
465 |
--------------------------------------------------------------------------------
/refresh_view_library/src/main/java/com/song/refresh_view/PullToRefreshView.java:
--------------------------------------------------------------------------------
1 | package com.song.refresh_view;/*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import android.content.Context;
19 | import android.content.res.Resources;
20 | import android.content.res.TypedArray;
21 | import android.support.annotation.ColorInt;
22 | import android.support.annotation.ColorRes;
23 | import android.support.annotation.Nullable;
24 | import android.support.annotation.VisibleForTesting;
25 | import android.support.v4.view.MotionEventCompat;
26 | import android.support.v4.view.NestedScrollingChild;
27 | import android.support.v4.view.NestedScrollingChildHelper;
28 | import android.support.v4.view.NestedScrollingParent;
29 | import android.support.v4.view.NestedScrollingParentHelper;
30 | import android.support.v4.view.ViewCompat;
31 | import android.support.v4.widget.SwipeRefreshLayout;
32 | import android.util.AttributeSet;
33 | import android.util.DisplayMetrics;
34 | import android.util.Log;
35 | import android.view.MotionEvent;
36 | import android.view.View;
37 | import android.view.ViewConfiguration;
38 | import android.view.ViewGroup;
39 | import android.view.animation.Animation;
40 | import android.view.animation.Animation.AnimationListener;
41 | import android.view.animation.DecelerateInterpolator;
42 | import android.view.animation.Interpolator;
43 | import android.view.animation.Transformation;
44 | import android.widget.AbsListView;
45 | import android.widget.Toast;
46 |
47 | import static android.R.attr.animationDuration;
48 |
49 | /**
50 | * The SwipeRefreshLayout should be used whenever the user can refresh the
51 | * contents of a view via a vertical swipe gesture. The activity that
52 | * instantiates this view should add an OnRefreshListener to be notified
53 | * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout
54 | * will notify the listener each and every time the gesture is completed again;
55 | * the listener is responsible for correctly determining when to actually
56 | * initiate a refresh of its content. If the listener determines there should
57 | * not be a refresh, it must call setRefreshing(false) to cancel any visual
58 | * indication of a refresh. If an activity wishes to show just the progress
59 | * animation, it should call setRefreshing(true). To disable the gesture and
60 | * progress animation, call setEnabled(false) on the view.
61 | *
62 | * This layout should be made the parent of the view that will be refreshed as a
63 | * result of the gesture and can only support one direct child. This view will
64 | * also be made the target of the gesture and will be forced to match both the
65 | * width and the height supplied in this layout. The SwipeRefreshLayout does not
66 | * provide accessibility events; instead, a menu item must be provided to allow
67 | * refresh of the content wherever this gesture is used.
68 | *
69 | */
70 | public class PullToRefreshView extends ViewGroup implements NestedScrollingParent,
71 | NestedScrollingChild {
72 | // Maps to ProgressBar.Large style
73 | public static final int LARGE = SmileProgressDrawable.LARGE;
74 | // Maps to ProgressBar default style
75 | public static final int DEFAULT = SmileProgressDrawable.DEFAULT;
76 |
77 | @VisibleForTesting
78 | static final int CIRCLE_DIAMETER = 40;
79 | @VisibleForTesting
80 | static final int CIRCLE_DIAMETER_LARGE = 56;
81 |
82 | private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
83 |
84 | private static final int MAX_ALPHA = 255;
85 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
86 |
87 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
88 | private static final int INVALID_POINTER = -1;
89 | private static final float DRAG_RATE = .5f;
90 |
91 | // Max amount of circle that can be filled by progress during swipe gesture,
92 | // where 1.0 is a full circle
93 | private static final float MAX_PROGRESS_ANGLE = .8f;
94 |
95 | private static final int SCALE_DOWN_DURATION = 150;
96 |
97 | private static final int ALPHA_ANIMATION_DURATION = 300;
98 |
99 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
100 |
101 | private static final int ANIMATE_TO_START_DURATION = 200;
102 |
103 | // Default background for the progress spinner
104 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
105 | // Default offset in dips from the top of the view to where the progress spinner should stop
106 | private static final int DEFAULT_CIRCLE_TARGET = 64;
107 |
108 | private View mTarget; // the target of the gesture
109 | OnRefreshListener mListener;
110 | boolean mRefreshing = false;
111 | private int mTouchSlop;
112 | private float mTotalDragDistance = -1;
113 |
114 | // If nested scrolling is enabled, the total amount that needed to be
115 | // consumed by this as the nested scrolling parent is used in place of the
116 | // overscroll determined by MOVE events in the onTouch handler
117 | private float mTotalUnconsumed;
118 | private final NestedScrollingParentHelper mNestedScrollingParentHelper;
119 | private final NestedScrollingChildHelper mNestedScrollingChildHelper;
120 | private final int[] mParentScrollConsumed = new int[2];
121 | private final int[] mParentOffsetInWindow = new int[2];
122 | private boolean mNestedScrollInProgress;
123 |
124 | private int mMediumAnimationDuration;
125 | int mCurrentTargetOffsetTop;
126 |
127 | private float mInitialMotionY;
128 | private float mInitialDownY;
129 | private boolean mIsBeingDragged;
130 | private int mActivePointerId = INVALID_POINTER;
131 | // Whether this item is scaled up rather than clipped
132 | boolean mScale;
133 |
134 | // Target is returning to its start offset because it was cancelled or a
135 | // refresh was triggered.
136 | private boolean mReturningToStart;
137 | private final DecelerateInterpolator mDecelerateInterpolator;
138 | private static final int[] LAYOUT_ATTRS = new int[]{
139 | android.R.attr.enabled
140 | };
141 |
142 | CircleImageView mCircleView;
143 | private int mCircleViewIndex = -1;
144 |
145 | protected int mFrom;
146 |
147 | float mStartingScale;
148 |
149 | protected int mOriginalOffsetTop;
150 |
151 | SmileProgressDrawable mProgress;
152 |
153 | private Animation mScaleAnimation;
154 |
155 | private Animation mScaleDownAnimation;
156 |
157 | private Animation mAlphaStartAnimation;
158 |
159 | private Animation mAlphaMaxAnimation;
160 |
161 | private Animation mScaleDownToStartAnimation;
162 |
163 | float mSpinnerFinalOffset;
164 |
165 | boolean mNotify;
166 |
167 | private int mCircleDiameter;
168 |
169 | // Whether the client has set a custom starting position;
170 | boolean mUsingCustomStart;
171 |
172 | private OnChildScrollUpCallback mChildScrollUpCallback;
173 |
174 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
175 | @Override
176 | public void onAnimationStart(Animation animation) {
177 | }
178 |
179 | @Override
180 | public void onAnimationRepeat(Animation animation) {
181 | }
182 |
183 | @Override
184 | public void onAnimationEnd(Animation animation) {
185 | if (mRefreshing) {
186 | // Make sure the progress view is fully visible
187 | mProgress.setAlpha(MAX_ALPHA);
188 | mProgress.start();
189 | if (mNotify) {
190 | if (mListener != null) {
191 | mListener.onRefresh();
192 | }
193 | }
194 | mCurrentTargetOffsetTop = mCircleView.getTop();
195 | } else {
196 | reset();
197 | }
198 | }
199 | };
200 | private Toast mToast;
201 |
202 | void reset() {
203 | mCircleView.clearAnimation();
204 | mProgress.stop();
205 | mCircleView.setVisibility(View.GONE);
206 | setColorViewAlpha(MAX_ALPHA);
207 | // Return the circle to its start position
208 | if (mScale) {
209 | setAnimationProgress(0 /* animation complete and view is hidden */);
210 | } else {
211 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop,
212 | true /* requires update */);
213 | }
214 | mCurrentTargetOffsetTop = mCircleView.getTop();
215 | }
216 |
217 | @Override
218 | public void setEnabled(boolean enabled) {
219 | super.setEnabled(enabled);
220 | if (!enabled) {
221 | reset();
222 | }
223 | }
224 |
225 | @Override
226 | protected void onDetachedFromWindow() {
227 | super.onDetachedFromWindow();
228 | reset();
229 | }
230 |
231 | private void setColorViewAlpha(int targetAlpha) {
232 | mCircleView.getBackground().setAlpha(targetAlpha);
233 | mProgress.setAlpha(targetAlpha);
234 | }
235 |
236 | /**
237 | * The refresh indicator starting and resting position is always positioned
238 | * near the top of the refreshing content. This position is a consistent
239 | * location, but can be adjusted in either direction based on whether or not
240 | * there is a toolbar or actionbar present.
241 | *
242 | * Note: Calling this will reset the position of the refresh indicator to
243 | * start.
244 | *
245 | *
246 | * @param scale Set to true if there is no view at a higher z-order than where the progress
247 | * spinner is set to appear. Setting it to true will cause indicator to be scaled
248 | * up rather than clipped.
249 | * @param start The offset in pixels from the top of this view at which the
250 | * progress spinner should appear.
251 | * @param end The offset in pixels from the top of this view at which the
252 | * progress spinner should come to rest after a successful swipe
253 | * gesture.
254 | */
255 | public void setProgressViewOffset(boolean scale, int start, int end) {
256 | mScale = scale;
257 | mOriginalOffsetTop = start;
258 | mSpinnerFinalOffset = end;
259 | mUsingCustomStart = true;
260 | reset();
261 | mRefreshing = false;
262 | }
263 |
264 | /**
265 | * The refresh indicator resting position is always positioned near the top
266 | * of the refreshing content. This position is a consistent location, but
267 | * can be adjusted in either direction based on whether or not there is a
268 | * toolbar or actionbar present.
269 | *
270 | * @param scale Set to true if there is no view at a higher z-order than where the progress
271 | * spinner is set to appear. Setting it to true will cause indicator to be scaled
272 | * up rather than clipped.
273 | * @param end The offset in pixels from the top of this view at which the
274 | * progress spinner should come to rest after a successful swipe
275 | * gesture.
276 | */
277 | public void setProgressViewEndTarget(boolean scale, int end) {
278 | mSpinnerFinalOffset = end;
279 | mScale = scale;
280 | mCircleView.invalidate();
281 | }
282 |
283 | /**
284 | * One of DEFAULT, or LARGE.
285 | */
286 | public void setSize(int size) {
287 | if (size != SmileProgressDrawable.LARGE && size != SmileProgressDrawable.DEFAULT) {
288 | return;
289 | }
290 | final DisplayMetrics metrics = getResources().getDisplayMetrics();
291 | if (size == SmileProgressDrawable.LARGE) {
292 | mCircleDiameter = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
293 | } else {
294 | mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
295 | }
296 | // force the bounds of the progress circle inside the circle view to
297 | // update by setting it to null before updating its size and then
298 | // re-setting it
299 | mCircleView.setImageDrawable(null);
300 | mCircleView.setImageDrawable(mProgress);
301 | }
302 |
303 | /**
304 | * Simple constructor to use when creating a SwipeRefreshLayout from code.
305 | *
306 | * @param context
307 | */
308 | public PullToRefreshView(Context context) {
309 | this(context, null);
310 | }
311 |
312 | /**
313 | * Constructor that is called when inflating SwipeRefreshLayout from XML.
314 | *
315 | * @param context
316 | * @param attrs
317 | */
318 | public PullToRefreshView(Context context, AttributeSet attrs) {
319 | super(context, attrs);
320 |
321 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
322 |
323 | mMediumAnimationDuration = getResources().getInteger(
324 | android.R.integer.config_mediumAnimTime);
325 |
326 | setWillNotDraw(false);
327 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
328 |
329 | final DisplayMetrics metrics = getResources().getDisplayMetrics();
330 | mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
331 |
332 | createProgressView();
333 | ViewCompat.setChildrenDrawingOrderEnabled(this, true);
334 | // the absolute offset has to take into account that the circle starts at an offset
335 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
336 | mTotalDragDistance = mSpinnerFinalOffset;
337 | mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
338 |
339 | mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
340 | setNestedScrollingEnabled(true);
341 |
342 | mOriginalOffsetTop = mCurrentTargetOffsetTop = -mCircleDiameter;
343 | moveToStart(1.0f);
344 |
345 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
346 | setEnabled(a.getBoolean(0, true));
347 | a.recycle();
348 | }
349 |
350 | @Override
351 | protected int getChildDrawingOrder(int childCount, int i) {
352 | if (mCircleViewIndex < 0) {
353 | return i;
354 | } else if (i == childCount - 1) {
355 | // Draw the selected child last
356 | return mCircleViewIndex;
357 | } else if (i >= mCircleViewIndex) {
358 | // Move the children after the selected child earlier one
359 | return i + 1;
360 | } else {
361 | // Keep the children before the selected child the same
362 | return i;
363 | }
364 | }
365 |
366 |
367 | private void createProgressView() {
368 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT);
369 | mProgress = new SmileProgressDrawable(getContext(), this);
370 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
371 | mCircleView.setImageDrawable(mProgress);
372 | mCircleView.setVisibility(View.GONE);
373 | addView(mCircleView);
374 | }
375 |
376 | /**
377 | * Set the listener to be notified when a refresh is triggered via the swipe
378 | * gesture.
379 | */
380 | public void setOnRefreshListener(OnRefreshListener listener) {
381 | mListener = listener;
382 | }
383 |
384 | /**
385 | * Pre API 11, alpha is used to make the progress circle appear instead of scale.
386 | */
387 | private boolean isAlphaUsedForScale() {
388 | return android.os.Build.VERSION.SDK_INT < 11;
389 | }
390 |
391 | /**
392 | * Notify the widget that refresh state has changed. Do not call this when
393 | * refresh is triggered by a swipe gesture.
394 | *
395 | * @param refreshing Whether or not the view should show refresh progress.
396 | */
397 | public void setRefreshing(boolean refreshing) {
398 | if (refreshing && mRefreshing != refreshing) {
399 | // scale and show
400 | mRefreshing = refreshing;
401 | int endTarget = 0;
402 | if (!mUsingCustomStart) {
403 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);
404 | } else {
405 | endTarget = (int) mSpinnerFinalOffset;
406 | }
407 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,
408 | true /* requires update */);
409 | mNotify = false;
410 | startScaleUpAnimation(mRefreshListener);
411 | } else {
412 | setRefreshing(refreshing, false /* notify */);
413 | }
414 | }
415 |
416 | private void startScaleUpAnimation(AnimationListener listener) {
417 | mCircleView.setVisibility(View.VISIBLE);
418 | if (android.os.Build.VERSION.SDK_INT >= 11) {
419 | // Pre API 11, alpha is used in place of scale up to show the
420 | // progress circle appearing.
421 | // Don't adjust the alpha during appearance otherwise.
422 | mProgress.setAlpha(MAX_ALPHA);
423 | }
424 | mScaleAnimation = new Animation() {
425 | @Override
426 | public void applyTransformation(float interpolatedTime, Transformation t) {
427 | setAnimationProgress(interpolatedTime);
428 | }
429 | };
430 | mScaleAnimation.setDuration(mMediumAnimationDuration);
431 | if (listener != null) {
432 | mCircleView.setAnimationListener(listener);
433 | }
434 | mCircleView.clearAnimation();
435 | mCircleView.startAnimation(mScaleAnimation);
436 | }
437 |
438 | /**
439 | * Pre API 11, this does an alpha animation.
440 | *
441 | * @param progress
442 | */
443 | void setAnimationProgress(float progress) {
444 | if (isAlphaUsedForScale()) {
445 | setColorViewAlpha((int) (progress * MAX_ALPHA));
446 | } else {
447 | ViewCompat.setScaleX(mCircleView, progress);
448 | ViewCompat.setScaleY(mCircleView, progress);
449 | }
450 | }
451 |
452 | private void setRefreshing(boolean refreshing, final boolean notify) {
453 | if (mRefreshing != refreshing) {
454 | mNotify = notify;
455 | ensureTarget();
456 | mRefreshing = refreshing;
457 | if (mRefreshing) {
458 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
459 | } else {
460 | startScaleDownAnimation(mRefreshListener);
461 | }
462 | }
463 | }
464 |
465 | void startScaleDownAnimation(Animation.AnimationListener listener) {
466 | mScaleDownAnimation = new Animation() {
467 | @Override
468 | public void applyTransformation(float interpolatedTime, Transformation t) {
469 | setAnimationProgress(1 - interpolatedTime);
470 | }
471 | };
472 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
473 | mCircleView.setAnimationListener(listener);
474 | mCircleView.clearAnimation();
475 | mCircleView.startAnimation(mScaleDownAnimation);
476 | }
477 |
478 | private void startProgressAlphaStartAnimation() {
479 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);
480 | }
481 |
482 | private void startProgressAlphaMaxAnimation() {
483 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);
484 | }
485 |
486 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) {
487 | // Pre API 11, alpha is used in place of scale. Don't also use it to
488 | // show the trigger point.
489 | if (mScale && isAlphaUsedForScale()) {
490 | return null;
491 | }
492 | Animation alpha = new Animation() {
493 | @Override
494 | public void applyTransformation(float interpolatedTime, Transformation t) {
495 | mProgress.setAlpha(
496 | (int) (startingAlpha + ((endingAlpha - startingAlpha) * interpolatedTime)));
497 | }
498 | };
499 | alpha.setDuration(ALPHA_ANIMATION_DURATION);
500 | // Clear out the previous animation listeners.
501 | mCircleView.setAnimationListener(null);
502 | mCircleView.clearAnimation();
503 | mCircleView.startAnimation(alpha);
504 | return alpha;
505 | }
506 |
507 | /**
508 | * 设置绘制的宽度
509 | *
510 | * @param strokeWidth
511 | */
512 | public void setSmileStrokeWidth(float strokeWidth) {
513 | mProgress.setStrokeWidth(strokeWidth);
514 | }
515 |
516 | /**
517 | * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)}
518 | */
519 | @Deprecated
520 | public void setProgressBackgroundColor(int colorRes) {
521 | setProgressBackgroundColorSchemeResource(colorRes);
522 | }
523 |
524 | /**
525 | * Set the background color of the progress spinner disc.
526 | *
527 | * @param colorRes Resource id of the color.
528 | */
529 | public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) {
530 | setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes));
531 | }
532 |
533 | /**
534 | * Set the background color of the progress spinner disc.
535 | *
536 | * @param color
537 | */
538 | public void setProgressBackgroundColorSchemeColor(@ColorInt int color) {
539 | mCircleView.setBackgroundColor(color);
540 | mProgress.setBackgroundColor(color);
541 | }
542 |
543 | /**
544 | * @deprecated Use {@link #setColorSchemeResources(int...)}
545 | */
546 | @Deprecated
547 | public void setColorScheme(@ColorInt int... colors) {
548 | setColorSchemeResources(colors);
549 | }
550 |
551 | /**
552 | * Set the color resources used in the progress animation from color resources.
553 | * The first color will also be the color of the bar that grows in response
554 | * to a user swipe gesture.
555 | *
556 | * @param colorResIds
557 | */
558 | public void setColorSchemeResources(@ColorRes int... colorResIds) {
559 | final Resources res = getResources();
560 | int[] colorRes = new int[colorResIds.length];
561 | for (int i = 0; i < colorResIds.length; i++) {
562 | colorRes[i] = res.getColor(colorResIds[i]);
563 | }
564 | setColorSchemeColors(colorRes);
565 | }
566 |
567 | /**
568 | * Set the colors used in the progress animation. The first
569 | * color will also be the color of the bar that grows in response to a user
570 | * swipe gesture.
571 | *
572 | * @param colors
573 | */
574 | public void setColorSchemeColors(@ColorInt int... colors) {
575 | ensureTarget();
576 | mProgress.setColorSchemeColors(colors);
577 | }
578 |
579 | public void setSmileAnimationDuration(int animationDuration){
580 | mProgress.setAnimationDuration(animationDuration);
581 | }
582 | public void setSmileInterpolator(Interpolator interpolator){
583 | mProgress.setInterpolator(interpolator);
584 | }
585 | /**
586 | * @return Whether the SwipeRefreshWidget is actively showing refresh
587 | * progress.
588 | */
589 | public boolean isRefreshing() {
590 | return mRefreshing;
591 | }
592 |
593 | private void ensureTarget() {
594 | // Don't bother getting the parent height if the parent hasn't been laid
595 | // out yet.
596 | if (mTarget == null) {
597 | for (int i = 0; i < getChildCount(); i++) {
598 | View child = getChildAt(i);
599 | if (!child.equals(mCircleView)) {
600 | mTarget = child;
601 | break;
602 | }
603 | }
604 | }
605 | }
606 |
607 | /**
608 | * Set the distance to trigger a sync in dips
609 | *
610 | * @param distance
611 | */
612 | public void setDistanceToTriggerSync(int distance) {
613 | mTotalDragDistance = distance;
614 | }
615 |
616 | @Override
617 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
618 | final int width = getMeasuredWidth();
619 | final int height = getMeasuredHeight();
620 | if (getChildCount() == 0) {
621 | return;
622 | }
623 | if (mTarget == null) {
624 | ensureTarget();
625 | }
626 | if (mTarget == null) {
627 | return;
628 | }
629 | final View child = mTarget;
630 | final int childLeft = getPaddingLeft();
631 | final int childTop = getPaddingTop();
632 | final int childWidth = width - getPaddingLeft() - getPaddingRight();
633 | final int childHeight = height - getPaddingTop() - getPaddingBottom();
634 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
635 | int circleWidth = mCircleView.getMeasuredWidth();
636 | int circleHeight = mCircleView.getMeasuredHeight();
637 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
638 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
639 | }
640 |
641 | @Override
642 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
643 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
644 | if (mTarget == null) {
645 | ensureTarget();
646 | }
647 | if (mTarget == null) {
648 | return;
649 | }
650 | mTarget.measure(MeasureSpec.makeMeasureSpec(
651 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
652 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
653 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
654 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY),
655 | MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY));
656 | mCircleViewIndex = -1;
657 | // Get the index of the circleview.
658 | for (int index = 0; index < getChildCount(); index++) {
659 | if (getChildAt(index) == mCircleView) {
660 | mCircleViewIndex = index;
661 | break;
662 | }
663 | }
664 | }
665 |
666 | /**
667 | * Get the diameter of the progress circle that is displayed as part of the
668 | * swipe to refresh layout.
669 | *
670 | * @return Diameter in pixels of the progress circle view.
671 | */
672 | public int getProgressCircleDiameter() {
673 | return mCircleDiameter;
674 | }
675 |
676 | /**
677 | * @return Whether it is possible for the child view of this layout to
678 | * scroll up. Override this if the child view is a custom view.
679 | */
680 | public boolean canChildScrollUp() {
681 | if (mChildScrollUpCallback != null) {
682 | return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
683 | }
684 | if (android.os.Build.VERSION.SDK_INT < 14) {
685 | if (mTarget instanceof AbsListView) {
686 | final AbsListView absListView = (AbsListView) mTarget;
687 | return absListView.getChildCount() > 0
688 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
689 | .getTop() < absListView.getPaddingTop());
690 | } else {
691 | return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
692 | }
693 | } else {
694 | return ViewCompat.canScrollVertically(mTarget, -1);
695 | }
696 | }
697 |
698 | /**
699 | * Set a callback to override {@link SwipeRefreshLayout#canChildScrollUp()} method. Non-null
700 | * callback will return the value provided by the callback and ignore all internal logic.
701 | *
702 | * @param callback Callback that should be called when canChildScrollUp() is called.
703 | */
704 | public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) {
705 | mChildScrollUpCallback = callback;
706 | }
707 |
708 | @Override
709 | public boolean onInterceptTouchEvent(MotionEvent ev) {
710 | ensureTarget();
711 |
712 | final int action = MotionEventCompat.getActionMasked(ev);
713 | int pointerIndex;
714 |
715 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
716 | mReturningToStart = false;
717 | }
718 |
719 | if (!isEnabled() || mReturningToStart || canChildScrollUp()
720 | || mRefreshing || mNestedScrollInProgress) {
721 | // Fail fast if we're not in a state where a swipe is possible
722 | return false;
723 | }
724 |
725 | switch (action) {
726 | case MotionEvent.ACTION_DOWN:
727 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
728 | mActivePointerId = ev.getPointerId(0);
729 | mIsBeingDragged = false;
730 |
731 | pointerIndex = ev.findPointerIndex(mActivePointerId);
732 | if (pointerIndex < 0) {
733 | return false;
734 | }
735 | mInitialDownY = ev.getY(pointerIndex);
736 | break;
737 |
738 | case MotionEvent.ACTION_MOVE:
739 | if (mActivePointerId == INVALID_POINTER) {
740 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
741 | return false;
742 | }
743 |
744 | pointerIndex = ev.findPointerIndex(mActivePointerId);
745 | if (pointerIndex < 0) {
746 | return false;
747 | }
748 | final float y = ev.getY(pointerIndex);
749 | startDragging(y);
750 | break;
751 |
752 | case MotionEventCompat.ACTION_POINTER_UP:
753 | onSecondaryPointerUp(ev);
754 | break;
755 |
756 | case MotionEvent.ACTION_UP:
757 | case MotionEvent.ACTION_CANCEL:
758 | mIsBeingDragged = false;
759 | mActivePointerId = INVALID_POINTER;
760 | break;
761 | }
762 |
763 | return mIsBeingDragged;
764 | }
765 |
766 | @Override
767 | public void requestDisallowInterceptTouchEvent(boolean b) {
768 | // if this is a List < L or another view that doesn't support nested
769 | // scrolling, ignore this request so that the vertical scroll event
770 | // isn't stolen
771 | if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
772 | || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
773 | // Nope.
774 | } else {
775 | super.requestDisallowInterceptTouchEvent(b);
776 | }
777 | }
778 |
779 | // NestedScrollingParent
780 |
781 | @Override
782 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
783 | return isEnabled() && !mReturningToStart && !mRefreshing
784 | && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
785 | }
786 |
787 | @Override
788 | public void onNestedScrollAccepted(View child, View target, int axes) {
789 | // Reset the counter of how much leftover scroll needs to be consumed.
790 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
791 | // Dispatch up to the nested parent
792 | startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
793 | mTotalUnconsumed = 0;
794 | mNestedScrollInProgress = true;
795 | }
796 |
797 | @Override
798 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
799 | // If we are in the middle of consuming, a scroll, then we want to move the spinner back up
800 | // before allowing the list to scroll
801 | if (dy > 0 && mTotalUnconsumed > 0) {
802 | if (dy > mTotalUnconsumed) {
803 | consumed[1] = dy - (int) mTotalUnconsumed;
804 | mTotalUnconsumed = 0;
805 | } else {
806 | mTotalUnconsumed -= dy;
807 | consumed[1] = dy;
808 | }
809 | moveSpinner(mTotalUnconsumed);
810 | }
811 |
812 | // If a client layout is using a custom start position for the circle
813 | // view, they mean to hide it again before scrolling the child view
814 | // If we get back to mTotalUnconsumed == 0 and there is more to go, hide
815 | // the circle so it isn't exposed if its blocking content is moved
816 | if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
817 | && Math.abs(dy - consumed[1]) > 0) {
818 | mCircleView.setVisibility(View.GONE);
819 | }
820 |
821 | // Now let our nested parent consume the leftovers
822 | final int[] parentConsumed = mParentScrollConsumed;
823 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
824 | consumed[0] += parentConsumed[0];
825 | consumed[1] += parentConsumed[1];
826 | }
827 | }
828 |
829 | @Override
830 | public int getNestedScrollAxes() {
831 | return mNestedScrollingParentHelper.getNestedScrollAxes();
832 | }
833 |
834 | @Override
835 | public void onStopNestedScroll(View target) {
836 | mNestedScrollingParentHelper.onStopNestedScroll(target);
837 | mNestedScrollInProgress = false;
838 | // Finish the spinner for nested scrolling if we ever consumed any
839 | // unconsumed nested scroll
840 | if (mTotalUnconsumed > 0) {
841 | finishSpinner(mTotalUnconsumed);
842 | mTotalUnconsumed = 0;
843 | }
844 | // Dispatch up our nested parent
845 | stopNestedScroll();
846 | }
847 |
848 | @Override
849 | public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
850 | final int dxUnconsumed, final int dyUnconsumed) {
851 | // Dispatch up to the nested parent first
852 | dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
853 | mParentOffsetInWindow);
854 |
855 | // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
856 | // sometimes between two nested scrolling views, we need a way to be able to know when any
857 | // nested scrolling parent has stopped handling events. We do that by using the
858 | // 'offset in window 'functionality to see if we have been moved from the event.
859 | // This is a decent indication of whether we should take over the event stream or not.
860 | final int dy = dyUnconsumed + mParentOffsetInWindow[1];
861 | if (dy < 0 && !canChildScrollUp()) {
862 | mTotalUnconsumed += Math.abs(dy);
863 | moveSpinner(mTotalUnconsumed);
864 | }
865 | }
866 |
867 | // NestedScrollingChild
868 |
869 | @Override
870 | public void setNestedScrollingEnabled(boolean enabled) {
871 | mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
872 | }
873 |
874 | @Override
875 | public boolean isNestedScrollingEnabled() {
876 | return mNestedScrollingChildHelper.isNestedScrollingEnabled();
877 | }
878 |
879 | @Override
880 | public boolean startNestedScroll(int axes) {
881 | return mNestedScrollingChildHelper.startNestedScroll(axes);
882 | }
883 |
884 | @Override
885 | public void stopNestedScroll() {
886 | mNestedScrollingChildHelper.stopNestedScroll();
887 | }
888 |
889 | @Override
890 | public boolean hasNestedScrollingParent() {
891 | return mNestedScrollingChildHelper.hasNestedScrollingParent();
892 | }
893 |
894 | @Override
895 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
896 | int dyUnconsumed, int[] offsetInWindow) {
897 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
898 | dxUnconsumed, dyUnconsumed, offsetInWindow);
899 | }
900 |
901 | @Override
902 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
903 | return mNestedScrollingChildHelper.dispatchNestedPreScroll(
904 | dx, dy, consumed, offsetInWindow);
905 | }
906 |
907 | @Override
908 | public boolean onNestedPreFling(View target, float velocityX,
909 | float velocityY) {
910 | return dispatchNestedPreFling(velocityX, velocityY);
911 | }
912 |
913 | @Override
914 | public boolean onNestedFling(View target, float velocityX, float velocityY,
915 | boolean consumed) {
916 | return dispatchNestedFling(velocityX, velocityY, consumed);
917 | }
918 |
919 | @Override
920 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
921 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
922 | }
923 |
924 | @Override
925 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
926 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
927 | }
928 |
929 | private boolean isAnimationRunning(Animation animation) {
930 | return animation != null && animation.hasStarted() && !animation.hasEnded();
931 | }
932 |
933 | private void moveSpinner(float overscrollTop) {
934 |
935 | float originalDragPercent = overscrollTop / mTotalDragDistance;
936 |
937 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
938 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;// 1 可以下拉刷新
939 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
940 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop
941 | : mSpinnerFinalOffset;
942 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
943 | / slingshotDist);
944 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
945 | (tensionSlingshotPercent / 4), 2)) * 2f;
946 | float extraMove = (slingshotDist) * tensionPercent * 2;
947 |
948 | int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
949 | // where 1.0f is a full circle
950 | if (mCircleView.getVisibility() != View.VISIBLE) {
951 | mCircleView.setVisibility(View.VISIBLE);
952 | }
953 | if (!mScale) {
954 | ViewCompat.setScaleX(mCircleView, 1f);
955 | ViewCompat.setScaleY(mCircleView, 1f);
956 | }
957 |
958 | if (mScale) {
959 | setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));
960 | }
961 | if (overscrollTop < mTotalDragDistance) {
962 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
963 | && !isAnimationRunning(mAlphaStartAnimation)) {
964 | // Animate the alpha
965 | startProgressAlphaStartAnimation();
966 | }
967 | } else {
968 | if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {
969 | // Animate the alpha
970 | startProgressAlphaMaxAnimation();
971 | }
972 | }
973 | // float strokeStart = adjustedPercent * .8f;// TODO: 2016/10/13 0013
974 | // mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
975 | // mProgress.setArrowScale(Math.min(1f, adjustedPercent));
976 | //
977 | // float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
978 | // mProgress.setProgressRotation(rotation);
979 | mProgress.setPercentage(tensionPercent * 2); // 最高值0.5
980 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
981 | }
982 |
983 | private void finishSpinner(float overscrollTop) {
984 | if (overscrollTop > mTotalDragDistance) {
985 | setRefreshing(true, true /* notify */);
986 | } else {
987 | // cancel refresh
988 | mRefreshing = false;
989 | // mProgress.setStartEndTrim(0f, 0f); // TODO: 2016/10/13 0013
990 | Animation.AnimationListener listener = null;
991 | if (!mScale) {
992 | listener = new Animation.AnimationListener() {
993 |
994 | @Override
995 | public void onAnimationStart(Animation animation) {
996 | }
997 |
998 | @Override
999 | public void onAnimationEnd(Animation animation) {
1000 | if (!mScale) {
1001 | startScaleDownAnimation(null);
1002 | }
1003 | }
1004 |
1005 | @Override
1006 | public void onAnimationRepeat(Animation animation) {
1007 | }
1008 |
1009 | };
1010 | }
1011 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
1012 | }
1013 | }
1014 |
1015 | @Override
1016 | public boolean onTouchEvent(MotionEvent ev) {
1017 | final int action = MotionEventCompat.getActionMasked(ev);
1018 | int pointerIndex = -1;
1019 |
1020 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
1021 | mReturningToStart = false;
1022 | }
1023 |
1024 | if (!isEnabled() || mReturningToStart || canChildScrollUp()
1025 | || mRefreshing || mNestedScrollInProgress) {
1026 | // Fail fast if we're not in a state where a swipe is possible
1027 | return false;
1028 | }
1029 |
1030 | switch (action) {
1031 | case MotionEvent.ACTION_DOWN:
1032 | mActivePointerId = ev.getPointerId(0);
1033 | mIsBeingDragged = false;
1034 | break;
1035 |
1036 | case MotionEvent.ACTION_MOVE: {
1037 | pointerIndex = ev.findPointerIndex(mActivePointerId);
1038 | if (pointerIndex < 0) {
1039 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
1040 | return false;
1041 | }
1042 |
1043 | final float y = ev.getY(pointerIndex);
1044 | startDragging(y);
1045 |
1046 | if (mIsBeingDragged) {
1047 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
1048 | if (overscrollTop > 0) {
1049 | moveSpinner(overscrollTop);
1050 | } else {
1051 | return false;
1052 | }
1053 | }
1054 | break;
1055 | }
1056 | case MotionEventCompat.ACTION_POINTER_DOWN: {
1057 | pointerIndex = MotionEventCompat.getActionIndex(ev);
1058 | if (pointerIndex < 0) {
1059 | Log.e(LOG_TAG,
1060 | "Got ACTION_POINTER_DOWN event but have an invalid action index.");
1061 | return false;
1062 | }
1063 | mActivePointerId = ev.getPointerId(pointerIndex);
1064 | break;
1065 | }
1066 |
1067 | case MotionEventCompat.ACTION_POINTER_UP:
1068 | onSecondaryPointerUp(ev);
1069 | break;
1070 |
1071 | case MotionEvent.ACTION_UP: {
1072 | pointerIndex = ev.findPointerIndex(mActivePointerId);
1073 | if (pointerIndex < 0) {
1074 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
1075 | return false;
1076 | }
1077 |
1078 | if (mIsBeingDragged) {
1079 | final float y = ev.getY(pointerIndex);
1080 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
1081 | mIsBeingDragged = false;
1082 | finishSpinner(overscrollTop);
1083 | }
1084 | mActivePointerId = INVALID_POINTER;
1085 | return false;
1086 | }
1087 | case MotionEvent.ACTION_CANCEL:
1088 | return false;
1089 | }
1090 |
1091 | return true;
1092 | }
1093 |
1094 | private void startDragging(float y) {
1095 | final float yDiff = y - mInitialDownY;
1096 | if (yDiff > mTouchSlop && !mIsBeingDragged) {
1097 | mInitialMotionY = mInitialDownY + mTouchSlop;
1098 | mIsBeingDragged = true;
1099 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
1100 | }
1101 | }
1102 |
1103 | private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {
1104 | mFrom = from;
1105 | mAnimateToCorrectPosition.reset();
1106 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
1107 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
1108 | if (listener != null) {
1109 | mCircleView.setAnimationListener(listener);
1110 | }
1111 | mCircleView.clearAnimation();
1112 | mCircleView.startAnimation(mAnimateToCorrectPosition);
1113 | }
1114 |
1115 | private void animateOffsetToStartPosition(int from, AnimationListener listener) {
1116 | if (mScale) {
1117 | // Scale the item back down
1118 | startScaleDownReturnToStartAnimation(from, listener);
1119 | } else {
1120 | mFrom = from;
1121 | mAnimateToStartPosition.reset();
1122 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
1123 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
1124 | if (listener != null) {
1125 | mCircleView.setAnimationListener(listener);
1126 | }
1127 | mCircleView.clearAnimation();
1128 | mCircleView.startAnimation(mAnimateToStartPosition);
1129 | }
1130 | }
1131 |
1132 | private final Animation mAnimateToCorrectPosition = new Animation() {
1133 | @Override
1134 | public void applyTransformation(float interpolatedTime, Transformation t) {
1135 | int targetTop = 0;
1136 | int endTarget = 0;
1137 | if (!mUsingCustomStart) {
1138 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));
1139 | } else {
1140 | endTarget = (int) mSpinnerFinalOffset;
1141 | }
1142 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
1143 | int offset = targetTop - mCircleView.getTop();
1144 | setTargetOffsetTopAndBottom(offset, false /* requires update */);
1145 | }
1146 | };
1147 |
1148 | void moveToStart(float interpolatedTime) {
1149 | int targetTop = 0;
1150 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
1151 | int offset = targetTop - mCircleView.getTop();
1152 | setTargetOffsetTopAndBottom(offset, false /* requires update */);
1153 | }
1154 |
1155 | private final Animation mAnimateToStartPosition = new Animation() {
1156 | @Override
1157 | public void applyTransformation(float interpolatedTime, Transformation t) {
1158 | moveToStart(interpolatedTime);
1159 | }
1160 | };
1161 |
1162 | private void startScaleDownReturnToStartAnimation(int from,
1163 | Animation.AnimationListener listener) {
1164 | mFrom = from;
1165 | if (isAlphaUsedForScale()) {
1166 | mStartingScale = mProgress.getAlpha();
1167 | } else {
1168 | mStartingScale = ViewCompat.getScaleX(mCircleView);
1169 | }
1170 | mScaleDownToStartAnimation = new Animation() {
1171 | @Override
1172 | public void applyTransformation(float interpolatedTime, Transformation t) {
1173 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime));
1174 | setAnimationProgress(targetScale);
1175 | moveToStart(interpolatedTime);
1176 | }
1177 | };
1178 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);
1179 | if (listener != null) {
1180 | mCircleView.setAnimationListener(listener);
1181 | }
1182 | mCircleView.clearAnimation();
1183 | mCircleView.startAnimation(mScaleDownToStartAnimation);
1184 | }
1185 |
1186 | void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
1187 | mCircleView.bringToFront();
1188 | ViewCompat.offsetTopAndBottom(mCircleView, offset);
1189 | mCurrentTargetOffsetTop = mCircleView.getTop();
1190 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
1191 | invalidate();
1192 | }
1193 | }
1194 |
1195 | private void onSecondaryPointerUp(MotionEvent ev) {
1196 | final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1197 | final int pointerId = ev.getPointerId(pointerIndex);
1198 | if (pointerId == mActivePointerId) {
1199 | // This was our active pointer going up. Choose a new
1200 | // active pointer and adjust accordingly.
1201 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1202 | mActivePointerId = ev.getPointerId(newPointerIndex);
1203 | }
1204 | }
1205 |
1206 | /**
1207 | * Classes that wish to be notified when the swipe gesture correctly
1208 | * triggers a refresh should implement this interface.
1209 | */
1210 | public interface OnRefreshListener {
1211 | /**
1212 | * Called when a swipe gesture triggers a refresh.
1213 | */
1214 | void onRefresh();
1215 | }
1216 |
1217 | /**
1218 | * Classes that wish to override {@link SwipeRefreshLayout#canChildScrollUp()} method
1219 | * behavior should implement this interface.
1220 | */
1221 | public interface OnChildScrollUpCallback {
1222 | /**
1223 | * Callback that will be called when {@link SwipeRefreshLayout#canChildScrollUp()} method
1224 | * is called to allow the implementer to override its behavior.
1225 | *
1226 | * @param parent SwipeRefreshLayout that this callback is overriding.
1227 | * @param child The child view of SwipeRefreshLayout.
1228 | * @return Whether it is possible for the child view of parent layout to scroll up.
1229 | */
1230 | boolean canChildScrollUp(PullToRefreshView parent, @Nullable View child);
1231 | }
1232 | }
1233 |
--------------------------------------------------------------------------------