diff --git a/app/build.gradle b/app/build.gradle index a5ba89995..1d9f59c96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -83,7 +83,8 @@ dependencies { compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:design:23.3.0' compile 'com.github.amlcurran.showcaseview:library:5.4.1' - compile 'com.github.seven332:unifile:0.1.0' + compile 'com.github.seven332:unifile:0.1.1' + compile 'com.github.seven332:refreshlayout:0.1.0' compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.8.7' compile 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' compile 'org.jsoup:jsoup:1.8.3' diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java index 46ca8979c..1700b135e 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java @@ -71,6 +71,7 @@ import com.hippo.ehviewer.ui.annotation.DrawerLifeCircle; import com.hippo.ehviewer.ui.annotation.ViewLifeCircle; import com.hippo.ehviewer.ui.annotation.WholeLifeCircle; import com.hippo.ehviewer.widget.SearchBar; +import com.hippo.refreshlayout.RefreshLayout; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.Announcer; import com.hippo.scene.SceneFragment; @@ -79,7 +80,6 @@ import com.hippo.util.DrawableManager; import com.hippo.widget.ContentLayout; import com.hippo.widget.FabLayout; import com.hippo.widget.SearchBarMover; -import com.hippo.widget.refreshlayout.RefreshLayout; import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.ObjectUtils; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index b96dc65b0..ac43e43cb 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -76,6 +76,7 @@ import com.hippo.ehviewer.ui.CommonOperations; import com.hippo.ehviewer.ui.MainActivity; import com.hippo.ehviewer.widget.SearchBar; import com.hippo.ehviewer.widget.SearchLayout; +import com.hippo.refreshlayout.RefreshLayout; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.Announcer; import com.hippo.scene.SceneFragment; @@ -86,7 +87,6 @@ import com.hippo.view.ViewTransition; import com.hippo.widget.ContentLayout; import com.hippo.widget.FabLayout; import com.hippo.widget.SearchBarMover; -import com.hippo.widget.refreshlayout.RefreshLayout; import com.hippo.yorozuya.AnimationUtils; import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.MathUtils; diff --git a/app/src/main/java/com/hippo/widget/ContentLayout.java b/app/src/main/java/com/hippo/widget/ContentLayout.java index 279609d77..df4169435 100644 --- a/app/src/main/java/com/hippo/widget/ContentLayout.java +++ b/app/src/main/java/com/hippo/widget/ContentLayout.java @@ -36,10 +36,10 @@ import com.hippo.easyrecyclerview.FastScroller; import com.hippo.easyrecyclerview.HandlerDrawable; import com.hippo.easyrecyclerview.LayoutManagerUtils; import com.hippo.ehviewer.R; +import com.hippo.refreshlayout.RefreshLayout; import com.hippo.util.DrawableManager; import com.hippo.util.ExceptionUtils; import com.hippo.view.ViewTransition; -import com.hippo.widget.refreshlayout.RefreshLayout; import com.hippo.yorozuya.IntIdGenerator; import com.hippo.yorozuya.IntList; import com.hippo.yorozuya.LayoutUtils; diff --git a/app/src/main/java/com/hippo/widget/refreshlayout/CircleImageView.java b/app/src/main/java/com/hippo/widget/refreshlayout/CircleImageView.java deleted file mode 100644 index 2140b160b..000000000 --- a/app/src/main/java/com/hippo/widget/refreshlayout/CircleImageView.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.widget.refreshlayout; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RadialGradient; -import android.graphics.Shader; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; -import android.support.annotation.ColorRes; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.ViewCompat; -import android.view.animation.Animation; -import android.widget.ImageView; - -/** - * Private class created to work around issues with AnimationListeners being - * called before the animation is actually complete and support shadows on older - * platforms. - */ -@SuppressLint("ViewConstructor") -class CircleImageView extends ImageView { - - private static final int KEY_SHADOW_COLOR = 0x1E000000; - private static final int FILL_SHADOW_COLOR = 0x3D000000; - // PX - private static final float X_OFFSET = 0f; - private static final float Y_OFFSET = 1.75f; - private static final float SHADOW_RADIUS = 3.5f; - private static final int SHADOW_ELEVATION = 4; - - private Animation.AnimationListener mListener; - private int mShadowRadius; - - @SuppressWarnings("deprecation") - public CircleImageView(Context context, int color, final float radius) { - super(context); - final float density = getContext().getResources().getDisplayMetrics().density; - final int diameter = (int) (radius * density * 2); - final int shadowYOffset = (int) (density * Y_OFFSET); - final int shadowXOffset = (int) (density * X_OFFSET); - - mShadowRadius = (int) (density * SHADOW_RADIUS); - - ShapeDrawable circle; - if (elevationSupported()) { - circle = new ShapeDrawable(new OvalShape()); - ViewCompat.setElevation(this, SHADOW_ELEVATION * density); - } else { - OvalShape oval = new OvalShadow(mShadowRadius, diameter); - circle = new ShapeDrawable(oval); - ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); - circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, - KEY_SHADOW_COLOR); - final int padding = mShadowRadius; - // set padding so the inner image sits correctly within the shadow. - setPadding(padding, padding, padding, padding); - } - circle.getPaint().setColor(color); - setBackgroundDrawable(circle); - } - - private boolean elevationSupported() { - return android.os.Build.VERSION.SDK_INT >= 21; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (!elevationSupported()) { - setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight() - + mShadowRadius*2); - } - } - - public void setAnimationListener(Animation.AnimationListener listener) { - mListener = listener; - } - - @Override - public void onAnimationStart() { - super.onAnimationStart(); - if (mListener != null) { - mListener.onAnimationStart(getAnimation()); - } - } - - @Override - public void onAnimationEnd() { - super.onAnimationEnd(); - if (mListener != null) { - mListener.onAnimationEnd(getAnimation()); - } - } - - /** - * Update the background color of the circle image view. - * - * @param colorRes Id of a color resource. - */ - public void setBackgroundColorRes(@ColorRes int colorRes) { - setBackgroundColor(ContextCompat.getColor(getContext(), colorRes)); - } - - @Override - public void setBackgroundColor(int color) { - if (getBackground() instanceof ShapeDrawable) { - ((ShapeDrawable) getBackground()).getPaint().setColor(color); - } - } - - private class OvalShadow extends OvalShape { - private final RadialGradient mRadialGradient; - private final Paint mShadowPaint; - private final int mCircleDiameter; - - public OvalShadow(int shadowRadius, int circleDiameter) { - super(); - mShadowPaint = new Paint(); - mShadowRadius = shadowRadius; - mCircleDiameter = circleDiameter; - mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, - mShadowRadius, new int[] { - FILL_SHADOW_COLOR, Color.TRANSPARENT - }, null, Shader.TileMode.CLAMP); - mShadowPaint.setShader(mRadialGradient); - } - - @Override - public void draw(Canvas canvas, Paint paint) { - final int viewWidth = CircleImageView.this.getWidth(); - final int viewHeight = CircleImageView.this.getHeight(); - canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), - mShadowPaint); - canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); - } - } -} diff --git a/app/src/main/java/com/hippo/widget/refreshlayout/MaterialProgressDrawable.java b/app/src/main/java/com/hippo/widget/refreshlayout/MaterialProgressDrawable.java deleted file mode 100644 index b887b0a95..000000000 --- a/app/src/main/java/com/hippo/widget/refreshlayout/MaterialProgressDrawable.java +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.widget.refreshlayout; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.Drawable; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.v4.view.animation.FastOutSlowInInterpolator; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; -import android.view.animation.Transformation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; - -/** - * Fancy progress indicator for Material theme. - */ -class MaterialProgressDrawable extends Drawable implements Animatable { - private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); - - private static final float FULL_ROTATION = 1080.0f; - @Retention(RetentionPolicy.CLASS) - @IntDef({LARGE, DEFAULT}) - public @interface ProgressDrawableSize {} - // Maps to ProgressBar.Large style - static final int LARGE = 0; - // Maps to ProgressBar default style - static final int DEFAULT = 1; - - // Maps to ProgressBar default style - private static final int CIRCLE_DIAMETER = 40; - private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width - private static final float STROKE_WIDTH = 2.5f; - - // Maps to ProgressBar.Large style - private static final int CIRCLE_DIAMETER_LARGE = 56; - private static final float CENTER_RADIUS_LARGE = 12.5f; - private static final float STROKE_WIDTH_LARGE = 3f; - - private final int[] COLORS = new int[] { - Color.BLACK - }; - - /** - * The value in the linear interpolator for animating the drawable at which - * the color transition should start - */ - private static final float COLOR_START_DELAY_OFFSET = 0.75f; - private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; - private static final float START_TRIM_DURATION_OFFSET = 0.5f; - - /** The duration of a single progress spin in milliseconds. */ - private static final int ANIMATION_DURATION = 1332; - - /** The number of points in the progress "star". */ - private static final float NUM_POINTS = 5f; - /** The list of animators operating on this drawable. */ - private final ArrayList mAnimators = new ArrayList(); - - /** The indicator ring, used to manage animation state. */ - private final Ring mRing; - - /** Canvas rotation in degrees. */ - private float mRotation; - - /** Layout info for the arrowhead in dp */ - private static final int ARROW_WIDTH = 10; - private static final int ARROW_HEIGHT = 5; - private static final float ARROW_OFFSET_ANGLE = 5; - - /** Layout info for the arrowhead for the large spinner in dp */ - private static final int ARROW_WIDTH_LARGE = 12; - private static final int ARROW_HEIGHT_LARGE = 6; - private static final float MAX_PROGRESS_ARC = .8f; - - private final Resources mResources; - private final View mParent; - private Animation mAnimation; - private float mRotationCount; - private double mWidth; - private double mHeight; - boolean mFinishing; - - public MaterialProgressDrawable(Context context, View parent) { - mParent = parent; - mResources = context.getResources(); - - mRing = new Ring(mCallback); - mRing.setColors(COLORS); - - updateSizes(DEFAULT); - setupAnimators(); - } - - private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, - double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { - final Ring ring = mRing; - final DisplayMetrics metrics = mResources.getDisplayMetrics(); - final float screenDensity = metrics.density; - - mWidth = progressCircleWidth * screenDensity; - mHeight = progressCircleHeight * screenDensity; - ring.setStrokeWidth((float) strokeWidth * screenDensity); - ring.setCenterRadius(centerRadius * screenDensity); - ring.setColorIndex(0); - ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); - ring.setInsets((int) mWidth, (int) mHeight); - } - - /** - * Set the overall size for the progress spinner. This updates the radius - * and stroke width of the ring. - * - * @param size One of {@link #LARGE} or - * {@link #DEFAULT} - */ - public void updateSizes(@ProgressDrawableSize int size) { - if (size == LARGE) { - setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, - STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); - } else { - setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, - ARROW_WIDTH, ARROW_HEIGHT); - } - } - - /** - * @param show Set to true to display the arrowhead on the progress spinner. - */ - public void showArrow(boolean show) { - mRing.setShowArrow(show); - } - - /** - * @param scale Set the scale of the arrowhead for the spinner. - */ - public void setArrowScale(float scale) { - mRing.setArrowScale(scale); - } - - /** - * Set the start and end trim for the progress spinner arc. - * - * @param startAngle start angle - * @param endAngle end angle - */ - public void setStartEndTrim(float startAngle, float endAngle) { - mRing.setStartTrim(startAngle); - mRing.setEndTrim(endAngle); - } - - /** - * Set the amount of rotation to apply to the progress spinner. - * - * @param rotation Rotation is from [0..1] - */ - public void setProgressRotation(float rotation) { - mRing.setRotation(rotation); - } - - /** - * Update the background color of the circle image view. - */ - public void setBackgroundColor(int color) { - mRing.setBackgroundColor(color); - } - - /** - * Set the colors used in the progress animation from color resources. - * The first color will also be the color of the bar that grows in response - * to a user swipe gesture. - * - * @param colors the colors for scheme - */ - public void setColorSchemeColors(int... colors) { - mRing.setColors(colors); - mRing.setColorIndex(0); - } - - @Override - public int getIntrinsicHeight() { - return (int) mHeight; - } - - @Override - public int getIntrinsicWidth() { - return (int) mWidth; - } - - @Override - public void draw(Canvas c) { - final Rect bounds = getBounds(); - final int saveCount = c.save(); - c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); - mRing.draw(c, bounds); - c.restoreToCount(saveCount); - } - - @Override - public void setAlpha(int alpha) { - mRing.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mRing.getAlpha(); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mRing.setColorFilter(colorFilter); - } - - @SuppressWarnings("unused") - void setRotation(float rotation) { - mRotation = rotation; - invalidateSelf(); - } - - @SuppressWarnings("unused") - private float getRotation() { - return mRotation; - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public boolean isRunning() { - final ArrayList animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animation animator = animators.get(i); - if (animator.hasStarted() && !animator.hasEnded()) { - return true; - } - } - return false; - } - - @Override - public void start() { - mAnimation.reset(); - mRing.storeOriginals(); - // Already showing some part of the ring - if (mRing.getEndTrim() != mRing.getStartTrim()) { - mFinishing = true; - mAnimation.setDuration(ANIMATION_DURATION/2); - mParent.startAnimation(mAnimation); - } else { - mRing.setColorIndex(0); - mRing.resetOriginals(); - mAnimation.setDuration(ANIMATION_DURATION); - mParent.startAnimation(mAnimation); - } - } - - @Override - public void stop() { - mParent.clearAnimation(); - setRotation(0); - mRing.setShowArrow(false); - mRing.setColorIndex(0); - mRing.resetOriginals(); - } - - private float getMinProgressArc(Ring ring) { - return (float) Math.toRadians( - ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); - } - - // Adapted from ArgbEvaluator.java - private int evaluateColorChange(float fraction, int startValue, int endValue) { - int startA = (startValue >> 24) & 0xff; - int startR = (startValue >> 16) & 0xff; - int startG = (startValue >> 8) & 0xff; - int startB = startValue & 0xff; - - int endA = (endValue >> 24) & 0xff; - int endR = (endValue >> 16) & 0xff; - int endG = (endValue >> 8) & 0xff; - int endB = endValue & 0xff; - - return (startA + (int)(fraction * (endA - startA))) << 24 | - (startR + (int)(fraction * (endR - startR))) << 16 | - (startG + (int)(fraction * (endG - startG))) << 8 | - (startB + (int)(fraction * (endB - startB))); - } - - /** - * Update the ring color if this is within the last 25% of the animation. - * The new ring color will be a translation from the starting ring color to - * the next color. - */ - private void updateRingColor(float interpolatedTime, Ring ring) { - if (interpolatedTime > COLOR_START_DELAY_OFFSET) { - // scale the interpolatedTime so that the full - // transformation from 0 - 1 takes place in the - // remaining time - ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) - / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), - ring.getNextColor())); - } - } - - private void applyFinishTranslation(float interpolatedTime, Ring ring) { - // shrink back down and complete a full rotation before - // starting other circles - // Rotation goes between [0..1]. - updateRingColor(interpolatedTime, ring); - float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) - + 1f); - final float minProgressArc = getMinProgressArc(ring); - final float startTrim = ring.getStartingStartTrim() - + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) - * interpolatedTime; - ring.setStartTrim(startTrim); - ring.setEndTrim(ring.getStartingEndTrim()); - final float rotation = ring.getStartingRotation() - + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); - ring.setRotation(rotation); - } - - private void setupAnimators() { - final Ring ring = mRing; - final Animation animation = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - if (mFinishing) { - applyFinishTranslation(interpolatedTime, ring); - } else { - // The minProgressArc is calculated from 0 to create an - // angle that matches the stroke width. - final float minProgressArc = getMinProgressArc(ring); - final float startingEndTrim = ring.getStartingEndTrim(); - final float startingTrim = ring.getStartingStartTrim(); - final float startingRotation = ring.getStartingRotation(); - - updateRingColor(interpolatedTime, ring); - - // Moving the start trim only occurs in the first 50% of a - // single ring animation - if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { - // scale the interpolatedTime so that the full - // transformation from 0 - 1 takes place in the - // remaining time - final float scaledTime = (interpolatedTime) - / (1.0f - START_TRIM_DURATION_OFFSET); - final float startTrim = startingTrim - + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR - .getInterpolation(scaledTime)); - ring.setStartTrim(startTrim); - } - - // Moving the end trim starts after 50% of a single ring - // animation completes - if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { - // scale the interpolatedTime so that the full - // transformation from 0 - 1 takes place in the - // remaining time - final float minArc = MAX_PROGRESS_ARC - minProgressArc; - float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) - / (1.0f - START_TRIM_DURATION_OFFSET); - final float endTrim = startingEndTrim - + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); - ring.setEndTrim(endTrim); - } - - final float rotation = startingRotation + (0.25f * interpolatedTime); - ring.setRotation(rotation); - - float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) - + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); - setRotation(groupRotation); - } - } - }; - animation.setRepeatCount(Animation.INFINITE); - animation.setRepeatMode(Animation.RESTART); - animation.setInterpolator(LINEAR_INTERPOLATOR); - animation.setAnimationListener(new Animation.AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - mRotationCount = 0; - } - - @Override - public void onAnimationEnd(Animation animation) { - // do nothing - } - - @Override - public void onAnimationRepeat(Animation animation) { - ring.storeOriginals(); - ring.goToNextColor(); - ring.setStartTrim(ring.getEndTrim()); - if (mFinishing) { - // finished closing the last ring from the swipe gesture; go - // into progress mode - mFinishing = false; - animation.setDuration(ANIMATION_DURATION); - ring.setShowArrow(false); - } else { - mRotationCount = (mRotationCount + 1) % (NUM_POINTS); - } - } - }); - mAnimation = animation; - } - - private final Callback mCallback = new Callback() { - @Override - public void invalidateDrawable(Drawable d) { - invalidateSelf(); - } - - @Override - public void scheduleDrawable(Drawable d, Runnable what, long when) { - scheduleSelf(what, when); - } - - @Override - public void unscheduleDrawable(Drawable d, Runnable what) { - unscheduleSelf(what); - } - }; - - private static class Ring { - private final RectF mTempBounds = new RectF(); - private final Paint mPaint = new Paint(); - private final Paint mArrowPaint = new Paint(); - - private final Callback mCallback; - - private float mStartTrim = 0.0f; - private float mEndTrim = 0.0f; - private float mRotation = 0.0f; - private float mStrokeWidth = 5.0f; - private float mStrokeInset = 2.5f; - - private int[] mColors; - // mColorIndex represents the offset into the available mColors that the - // progress circle should currently display. As the progress circle is - // animating, the mColorIndex moves by one to the next available color. - private int mColorIndex; - private float mStartingStartTrim; - private float mStartingEndTrim; - private float mStartingRotation; - private boolean mShowArrow; - private Path mArrow; - private float mArrowScale; - private double mRingCenterRadius; - private int mArrowWidth; - private int mArrowHeight; - private int mAlpha; - private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private int mBackgroundColor; - private int mCurrentColor; - - public Ring(Callback callback) { - mCallback = callback; - - mPaint.setStrokeCap(Paint.Cap.SQUARE); - mPaint.setAntiAlias(true); - mPaint.setStyle(Style.STROKE); - - mArrowPaint.setStyle(Style.FILL); - mArrowPaint.setAntiAlias(true); - } - - public void setBackgroundColor(int color) { - mBackgroundColor = color; - } - - /** - * Set the dimensions of the arrowhead. - * - * @param width Width of the hypotenuse of the arrow head - * @param height Height of the arrow point - */ - public void setArrowDimensions(float width, float height) { - mArrowWidth = (int) width; - mArrowHeight = (int) height; - } - - /** - * Draw the progress spinner - */ - public void draw(Canvas c, Rect bounds) { - final RectF arcBounds = mTempBounds; - arcBounds.set(bounds); - arcBounds.inset(mStrokeInset, mStrokeInset); - - final float startAngle = (mStartTrim + mRotation) * 360; - final float endAngle = (mEndTrim + mRotation) * 360; - float sweepAngle = endAngle - startAngle; - - mPaint.setColor(mCurrentColor); - c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); - - drawTriangle(c, startAngle, sweepAngle, bounds); - - if (mAlpha < 255) { - mCirclePaint.setColor(mBackgroundColor); - mCirclePaint.setAlpha(255 - mAlpha); - c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, - mCirclePaint); - } - } - - private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { - if (mShowArrow) { - if (mArrow == null) { - mArrow = new Path(); - mArrow.setFillType(Path.FillType.EVEN_ODD); - } else { - mArrow.reset(); - } - - // Adjust the position of the triangle so that it is inset as - // much as the arc, but also centered on the arc. - float inset = (int) mStrokeInset / 2 * mArrowScale; - float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); - float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); - - // Update the path each time. This works around an issue in SKIA - // where concatenating a rotation matrix to a scale matrix - // ignored a starting negative rotation. This appears to have - // been fixed as of API 21. - mArrow.moveTo(0, 0); - mArrow.lineTo(mArrowWidth * mArrowScale, 0); - mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight - * mArrowScale)); - mArrow.offset(x - inset, y); - mArrow.close(); - // draw a triangle - mArrowPaint.setColor(mCurrentColor); - c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), - bounds.exactCenterY()); - c.drawPath(mArrow, mArrowPaint); - } - } - - /** - * Set the colors the progress spinner alternates between. - * - * @param colors Array of integers describing the colors. Must be non-null. - */ - public void setColors(@NonNull int[] colors) { - mColors = colors; - // if colors are reset, make sure to reset the color index as well - setColorIndex(0); - } - - /** - * Set the absolute color of the progress spinner. This is should only - * be used when animating between current and next color when the - * spinner is rotating. - * - * @param color int describing the color. - */ - public void setColor(int color) { - mCurrentColor = color; - } - - /** - * @param index Index into the color array of the color to display in - * the progress spinner. - */ - public void setColorIndex(int index) { - mColorIndex = index; - mCurrentColor = mColors[mColorIndex]; - } - - /** - * @return int describing the next color the progress spinner should use when drawing. - */ - public int getNextColor() { - return mColors[getNextColorIndex()]; - } - - private int getNextColorIndex() { - return (mColorIndex + 1) % (mColors.length); - } - - /** - * Proceed to the next available ring color. This will automatically - * wrap back to the beginning of colors. - */ - public void goToNextColor() { - setColorIndex(getNextColorIndex()); - } - - public void setColorFilter(ColorFilter filter) { - mPaint.setColorFilter(filter); - invalidateSelf(); - } - - /** - * @param alpha Set the alpha of the progress spinner and associated arrowhead. - */ - public void setAlpha(int alpha) { - mAlpha = alpha; - } - - /** - * @return Current alpha of the progress spinner and arrowhead. - */ - public int getAlpha() { - return mAlpha; - } - - /** - * @param strokeWidth Set the stroke width of the progress spinner in pixels. - */ - public void setStrokeWidth(float strokeWidth) { - mStrokeWidth = strokeWidth; - mPaint.setStrokeWidth(strokeWidth); - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getStrokeWidth() { - return mStrokeWidth; - } - - @SuppressWarnings("unused") - public void setStartTrim(float startTrim) { - mStartTrim = startTrim; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getStartTrim() { - return mStartTrim; - } - - public float getStartingStartTrim() { - return mStartingStartTrim; - } - - public float getStartingEndTrim() { - return mStartingEndTrim; - } - - public int getStartingColor() { - return mColors[mColorIndex]; - } - - @SuppressWarnings("unused") - public void setEndTrim(float endTrim) { - mEndTrim = endTrim; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getEndTrim() { - return mEndTrim; - } - - @SuppressWarnings("unused") - public void setRotation(float rotation) { - mRotation = rotation; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getRotation() { - return mRotation; - } - - public void setInsets(int width, int height) { - final float minEdge = (float) Math.min(width, height); - float insets; - if (mRingCenterRadius <= 0 || minEdge < 0) { - insets = (float) Math.ceil(mStrokeWidth / 2.0f); - } else { - insets = (float) (minEdge / 2.0f - mRingCenterRadius); - } - mStrokeInset = insets; - } - - @SuppressWarnings("unused") - public float getInsets() { - return mStrokeInset; - } - - /** - * @param centerRadius Inner radius in px of the circle the progress - * spinner arc traces. - */ - public void setCenterRadius(double centerRadius) { - mRingCenterRadius = centerRadius; - } - - public double getCenterRadius() { - return mRingCenterRadius; - } - - /** - * @param show Set to true to show the arrow head on the progress spinner. - */ - public void setShowArrow(boolean show) { - if (mShowArrow != show) { - mShowArrow = show; - invalidateSelf(); - } - } - - /** - * @param scale Set the scale of the arrowhead for the spinner. - */ - public void setArrowScale(float scale) { - if (scale != mArrowScale) { - mArrowScale = scale; - invalidateSelf(); - } - } - - /** - * @return The amount the progress spinner is currently rotated, between [0..1]. - */ - public float getStartingRotation() { - return mStartingRotation; - } - - /** - * If the start / end trim are offset to begin with, store them so that - * animation starts from that offset. - */ - public void storeOriginals() { - mStartingStartTrim = mStartTrim; - mStartingEndTrim = mEndTrim; - mStartingRotation = mRotation; - } - - /** - * Reset the progress spinner to default rotation, start and end angles. - */ - public void resetOriginals() { - mStartingStartTrim = 0; - mStartingEndTrim = 0; - mStartingRotation = 0; - setStartTrim(0); - setEndTrim(0); - setRotation(0); - } - - private void invalidateSelf() { - mCallback.invalidateDrawable(null); - } - } -} diff --git a/app/src/main/java/com/hippo/widget/refreshlayout/RefreshLayout.java b/app/src/main/java/com/hippo/widget/refreshlayout/RefreshLayout.java deleted file mode 100644 index 547467eca..000000000 --- a/app/src/main/java/com/hippo/widget/refreshlayout/RefreshLayout.java +++ /dev/null @@ -1,1397 +0,0 @@ -/* - * Copyright 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.widget.refreshlayout; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.support.annotation.NonNull; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ScrollingView; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Transformation; -import android.widget.AbsListView; - -import com.hippo.yorozuya.MathUtils; - -/** - * The SwipeRefreshLayout should be used whenever the user can refresh the - * contents of a view via a vertical swipe gesture. The activity that - * instantiates this view should add an OnRefreshListener to be notified - * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout - * will notify the listener each and every time the gesture is completed again; - * the listener is responsible for correctly determining when to actually - * initiate a refresh of its content. If the listener determines there should - * not be a refresh, it must call setRefreshing(false) to cancel any visual - * indication of a refresh. If an activity wishes to show just the progress - * animation, it should call setRefreshing(true). To disable the gesture and - * progress animation, call setEnabled(false) on the view. - *

- * This layout should be made the parent of the view that will be refreshed as a - * result of the gesture and can only support one direct child. This view will - * also be made the target of the gesture and will be forced to match both the - * width and the height supplied in this layout. The SwipeRefreshLayout does not - * provide accessibility events; instead, a menu item must be provided to allow - * refresh of the content wherever this gesture is used. - *

- */ -public class RefreshLayout extends ViewGroup { - // Maps to ProgressBar.Large style - public static final int LARGE = MaterialProgressDrawable.LARGE; - // Maps to ProgressBar default style - public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; - - private static final String LOG_TAG = RefreshLayout.class.getSimpleName(); - - private static final int MAX_ALPHA = 255; - private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); - - private static final int CIRCLE_DIAMETER = 40; - private static final int CIRCLE_DIAMETER_LARGE = 56; - - private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; - private static final int INVALID_POINTER = -1; - private static final float DRAG_RATE = .5f; - - // Max amount of circle that can be filled by progress during swipe gesture, - // where 1.0 is a full circle - private static final float MAX_PROGRESS_ANGLE = .8f; - - private static final int SCALE_DOWN_DURATION = 150; - - private static final int ALPHA_ANIMATION_DURATION = 300; - - private static final int ANIMATE_TO_TRIGGER_DURATION = 200; - - private static final int ANIMATE_TO_START_DURATION = 200; - - // Default background for the progress spinner - private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; - // Default offset in dips from the top of the view to where the progress spinner should stop - private static final int DEFAULT_CIRCLE_TARGET = 64; - - private View mTarget; // the target of the gesture - private OnRefreshListener mListener; - private boolean mHeaderRefreshing = false; - private int mTouchSlop; - private int mMediumAnimationDuration; - - private float mHeaderTotalDragDistance = -1; - - // Whether or not the starting offset has been determined. - private boolean mHeaderOriginalOffsetCalculated = false; - - private float mInitialMotionY; - private float mInitialDownY; - private float mLastMotionY; - private int mActivePointerId = INVALID_POINTER; - - private boolean mIsHeaderBeingDragged; - private boolean mIsFooterBeingDragged; - - // Target is returning to its start offset because it was cancelled or a - // refresh was triggered. - private boolean mReturningToStart; - private final DecelerateInterpolator mDecelerateInterpolator; - private final AccelerateInterpolator mAccelerateInterpolator; - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.enabled - }; - - private CircleImageView mCircleView; - private int mCircleViewIndex = -1; - protected int mHeaderFrom; - private float mHeaderStartingScale; - protected int mHeaderOriginalOffsetTop; - private int mHeaderCurrentTargetOffsetTop; - private MaterialProgressDrawable mProgress; - private Animation mHeaderScaleAnimation; - private Animation mHeaderScaleDownAnimation; - private Animation mHeaderAlphaStartAnimation; - private Animation mHeaderAlphaMaxAnimation; - private Animation mHeaderScaleDownToStartAnimation; - private float mSpinnerFinalOffset; - private boolean mHeaderNotify; - private int mCircleWidth; - private int mCircleHeight; - // Whether this item is scaled up rather than clipped - private boolean mHeaderScale; - // Whether the client has set a custom starting position; - private boolean mHeaderUsingCustomStart; - - private AnimationListener mHeaderRefreshListener = new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - if (mHeaderRefreshing) { - // Make sure the progress view is fully visible - mProgress.setAlpha(MAX_ALPHA); - mProgress.start(); - if (mHeaderNotify) { - if (mListener != null) { - mListener.onHeaderRefresh(); - } - } - } else { - mProgress.stop(); - mCircleView.setVisibility(View.GONE); - setColorViewAlpha(MAX_ALPHA); - // Return the circle to its start position - if (mHeaderScale) { - setAnimationProgress(0 /* animation complete and view is hidden */); - } else { - setHeaderTargetOffsetTopAndBottom(mHeaderOriginalOffsetTop - mHeaderCurrentTargetOffsetTop, - true /* requires update */); - } - } - mHeaderCurrentTargetOffsetTop = mCircleView.getTop(); - } - }; - - private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300; - private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f; - private static final float PROGRESS_BAR_HEIGHT = 4; - private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f; - private static final int REFRESH_TRIGGER_DISTANCE = 120; - - private SwipeProgressBar mProgressBar; //the thing that shows progress is going - private int mFooterOriginalOffsetTop; - private int mFooterFrom; - private boolean mFooterRefreshing = false; - private float mFooterDistanceToTriggerSync = -1; - private float mFooterFromPercentage = 0; - private float mFooterCurrPercentage = 0; - private int mProgressBarHeight; - private int mFooterCurrentTargetOffsetTop; - - private final Animation mAnimateFooterToStartPosition = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - int targetTop = 0; - if (mFooterFrom != mFooterOriginalOffsetTop) { - targetTop = (mFooterFrom + (int)((mFooterOriginalOffsetTop - mFooterFrom) * interpolatedTime)); - } - int offset = targetTop - mTarget.getTop(); - final int currentTop = mTarget.getTop(); - if (offset + currentTop > 0) { - offset = 0 - currentTop; - } - //setFooterTargetOffsetTopAndBottom(offset); - } - }; - - private Animation mShrinkTrigger = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - float percent = mFooterFromPercentage + ((0 - mFooterFromPercentage) * interpolatedTime); - mProgressBar.setTriggerPercentage(percent); - } - }; - - private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - // Once the target content has returned to its start position, reset - // the target offset to 0 - mFooterCurrentTargetOffsetTop = 0; - } - }; - - private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - mFooterCurrPercentage = 0; - } - }; - - private final Runnable mReturnToStartPosition = new Runnable() { - @Override - public void run() { - mReturningToStart = true; - animateFooterOffsetToStartPosition(mFooterCurrentTargetOffsetTop + getPaddingTop(), - mReturnToStartPositionListener); - } - }; - - // Cancel the refresh gesture and animate everything back to its original state. - private final Runnable mCancel = new Runnable() { - @Override - public void run() { - mReturningToStart = true; - // Timeout fired since the user last moved their finger; animate the - // trigger to 0 and put the target back at its original position - if (mProgressBar != null) { - mFooterFromPercentage = mFooterCurrPercentage; - mShrinkTrigger.setDuration(mMediumAnimationDuration); - mShrinkTrigger.setAnimationListener(mShrinkAnimationListener); - mShrinkTrigger.reset(); - mShrinkTrigger.setInterpolator(mDecelerateInterpolator); - startAnimation(mShrinkTrigger); - } - animateFooterOffsetToStartPosition(mFooterCurrentTargetOffsetTop + getPaddingTop(), - mReturnToStartPositionListener); - } - }; - - private boolean mEnableSwipeHeader = true; - private boolean mEnableSwipeFooter = true; - - private void setColorViewAlpha(int targetAlpha) { - mCircleView.getBackground().setAlpha(targetAlpha); - mProgress.setAlpha(targetAlpha); - } - - /** - * The refresh indicator starting and resting position is always positioned - * near the top of the refreshing content. This position is a consistent - * location, but can be adjusted in either direction based on whether or not - * there is a toolbar or actionbar present. - * - * @param scale Set to true if there is no view at a higher z-order than - * where the progress spinner is set to appear. - * @param start The offset in pixels from the top of this view at which the - * progress spinner should appear. - * @param end The offset in pixels from the top of this view at which the - * progress spinner should come to rest after a successful swipe - * gesture. - */ - public void setProgressViewOffset(boolean scale, int start, int end) { - mHeaderScale = scale; - mCircleView.setVisibility(View.GONE); - mHeaderOriginalOffsetTop = mHeaderCurrentTargetOffsetTop = start; - mSpinnerFinalOffset = end; - mHeaderUsingCustomStart = true; - mCircleView.invalidate(); - } - - /** - * The refresh indicator resting position is always positioned near the top - * of the refreshing content. This position is a consistent location, but - * can be adjusted in either direction based on whether or not there is a - * toolbar or actionbar present. - * - * @param scale Set to true if there is no view at a higher z-order than - * where the progress spinner is set to appear. - * @param end The offset in pixels from the top of this view at which the - * progress spinner should come to rest after a successful swipe - * gesture. - */ - public void setProgressViewEndTarget(boolean scale, int end) { - mSpinnerFinalOffset = end; - mHeaderScale = scale; - mCircleView.invalidate(); - } - - /** - * One of DEFAULT, or LARGE. - */ - public void setSize(int size) { - if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { - return; - } - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - if (size == MaterialProgressDrawable.LARGE) { - mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); - } else { - mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); - } - // force the bounds of the progress circle inside the circle view to - // update by setting it to null before updating its size and then - // re-setting it - mCircleView.setImageDrawable(null); - mProgress.updateSizes(size); - mCircleView.setImageDrawable(mProgress); - } - - /** - * Simple constructor to use when creating a SwipeRefreshLayout from code. - * - * @param context - */ - public RefreshLayout(Context context) { - this(context, null); - } - - /** - * Constructor that is called when inflating SwipeRefreshLayout from XML. - * - * @param context - * @param attrs - */ - public RefreshLayout(Context context, AttributeSet attrs) { - super(context, attrs); - - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - - mMediumAnimationDuration = getResources().getInteger( - android.R.integer.config_mediumAnimTime); - - setWillNotDraw(false); - mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); - mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR); - - final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - setEnabled(a.getBoolean(0, true)); - a.recycle(); - - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); - mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); - - createProgressView(); - ViewCompat.setChildrenDrawingOrderEnabled(this, true); - // the absolute offset has to take into account that the circle starts at an offset - mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; - mHeaderTotalDragDistance = mSpinnerFinalOffset; - - mProgressBar = new SwipeProgressBar(this); - mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - removeCallbacks(mCancel); - removeCallbacks(mReturnToStartPosition); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - removeCallbacks(mReturnToStartPosition); - removeCallbacks(mCancel); - } - - protected int getChildDrawingOrder(int childCount, int i) { - if (mCircleViewIndex < 0) { - return i; - } else if (i == childCount - 1) { - // Draw the selected child last - return mCircleViewIndex; - } else if (i >= mCircleViewIndex) { - // Move the children after the selected child earlier one - return i + 1; - } else { - // Keep the children before the selected child the same - return i; - } - } - - private void createProgressView() { - mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2); - mProgress = new MaterialProgressDrawable(getContext(), this); - mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); - mCircleView.setImageDrawable(mProgress); - mCircleView.setVisibility(View.GONE); - addView(mCircleView); - } - - /** - * Set the listener to be notified when a refresh is triggered via the swipe - * gesture. - */ - public void setOnRefreshListener(OnRefreshListener listener) { - mListener = listener; - } - - /** - * Pre API 11, alpha is used to make the progress circle appear instead of scale. - */ - private boolean isAlphaUsedForScale() { - return android.os.Build.VERSION.SDK_INT < 11; - } - - /** - * Notify the widget that refresh state has changed. Do not call this when - * refresh is triggered by a swipe gesture. - * - * @param refreshing Whether or not the view should show refresh progress. - */ - public void setHeaderRefreshing(boolean refreshing) { - if (mFooterRefreshing && refreshing) { - // Can't header and footer refresh both - return; - } - - if (refreshing && mHeaderRefreshing != refreshing) { - // scale and show - mHeaderRefreshing = refreshing; - int endTarget = 0; - if (!mHeaderUsingCustomStart) { - endTarget = (int) (mSpinnerFinalOffset + mHeaderOriginalOffsetTop); - } else { - endTarget = (int) mSpinnerFinalOffset; - } - setHeaderTargetOffsetTopAndBottom(endTarget - mHeaderCurrentTargetOffsetTop, - true /* requires update */); - mHeaderNotify = false; - startScaleUpAnimation(mHeaderRefreshListener); - } else { - setHeaderRefreshing(refreshing, false /* notify */); - } - } - - private void setHeaderRefreshing(boolean refreshing, final boolean notify) { - if (mFooterRefreshing && refreshing) { - // Can't header and footer refresh both - return; - } - - if (mHeaderRefreshing != refreshing) { - mHeaderNotify = notify; - ensureTarget(); - mHeaderRefreshing = refreshing; - if (mHeaderRefreshing) { - animateHeaderOffsetToCorrectPosition(mHeaderCurrentTargetOffsetTop, mHeaderRefreshListener); - } else { - startScaleDownAnimation(mHeaderRefreshListener); - } - } - } - - /** - * Notify the widget that refresh state has changed. Do not call this when - * refresh is triggered by a swipe gesture. - * - * @param refreshing Whether or not the view should show refresh progress. - */ - public void setFooterRefreshing(boolean refreshing) { - if (mHeaderRefreshing && refreshing) { - // Can't header and footer refresh both - return; - } - - if (mFooterRefreshing != refreshing) { - ensureTarget(); - mFooterCurrPercentage = 0; - mFooterRefreshing = refreshing; - if (mFooterRefreshing) { - mProgressBar.start(); - } else { - mProgressBar.stop(); - } - } - } - - private void startScaleUpAnimation(AnimationListener listener) { - mCircleView.setVisibility(View.VISIBLE); - if (android.os.Build.VERSION.SDK_INT >= 11) { - // Pre API 11, alpha is used in place of scale up to show the - // progress circle appearing. - // Don't adjust the alpha during appearance otherwise. - mProgress.setAlpha(MAX_ALPHA); - } - mHeaderScaleAnimation = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - setAnimationProgress(interpolatedTime); - } - }; - mHeaderScaleAnimation.setDuration(mMediumAnimationDuration); - if (listener != null) { - mCircleView.setAnimationListener(listener); - } - mCircleView.clearAnimation(); - mCircleView.startAnimation(mHeaderScaleAnimation); - } - - /** - * Pre API 11, this does an alpha animation. - * @param progress - */ - private void setAnimationProgress(float progress) { - if (isAlphaUsedForScale()) { - setColorViewAlpha((int) (progress * MAX_ALPHA)); - } else { - ViewCompat.setScaleX(mCircleView, progress); - ViewCompat.setScaleY(mCircleView, progress); - } - } - - private void startScaleDownAnimation(AnimationListener listener) { - mHeaderScaleDownAnimation = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - setAnimationProgress(1 - interpolatedTime); - } - }; - mHeaderScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); - mCircleView.setAnimationListener(listener); - mCircleView.clearAnimation(); - mCircleView.startAnimation(mHeaderScaleDownAnimation); - } - - private void startHeaderProgressAlphaStartAnimation() { - mHeaderAlphaStartAnimation = startHeaderAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); - } - - private void startHeaderProgressAlphaMaxAnimation() { - mHeaderAlphaMaxAnimation = startHeaderAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); - } - - private Animation startHeaderAlphaAnimation(final int startingAlpha, final int endingAlpha) { - // Pre API 11, alpha is used in place of scale. Don't also use it to - // show the trigger point. - if (mHeaderScale && isAlphaUsedForScale()) { - return null; - } - Animation alpha = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - mProgress - .setAlpha((int) (startingAlpha+ ((endingAlpha - startingAlpha) - * interpolatedTime))); - } - }; - alpha.setDuration(ALPHA_ANIMATION_DURATION); - // Clear out the previous animation listeners. - mCircleView.setAnimationListener(null); - mCircleView.clearAnimation(); - mCircleView.startAnimation(alpha); - return alpha; - } - - public void setHeaderTranslationY(float translationY) { - mCircleView.setTranslationY(translationY); - } - - /** - * Set the background color of the progress spinner disc. - * - * @param colorRes Resource id of the color. - */ - public void setHeaderProgressBackgroundColorSchemeResource(int colorRes) { - setHeaderProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); - } - - /** - * Set the background color of the progress spinner disc. - * - * @param color - */ - public void setHeaderProgressBackgroundColorSchemeColor(int color) { - mCircleView.setBackgroundColor(color); - mProgress.setBackgroundColor(color); - } - - /** - * Set the color resources used in the progress animation from color resources. - * The first color will also be the color of the bar that grows in response - * to a user swipe gesture. - * - * @param colorResIds - */ - public void setHeaderColorSchemeResources(int... colorResIds) { - final Resources res = getResources(); - int[] colorRes = new int[colorResIds.length]; - for (int i = 0; i < colorResIds.length; i++) { - colorRes[i] = res.getColor(colorResIds[i]); - } - setHeaderColorSchemeColors(colorRes); - } - - /** - * Set the colors used in the progress animation. The first - * color will also be the color of the bar that grows in response to a user - * swipe gesture. - * - * @param colors - */ - public void setHeaderColorSchemeColors(int... colors) { - ensureTarget(); - mProgress.setColorSchemeColors(colors); - } - - /** - * Set the four colors used in the progress animation from color resources. - * The first color will also be the color of the bar that grows in response - * to a user swipe gesture. - */ - public void setFooterColorSchemeResources(int colorRes1, int colorRes2, int colorRes3, - int colorRes4) { - final Resources res = getResources(); - setFooterColorSchemeColors(res.getColor(colorRes1), res.getColor(colorRes2), - res.getColor(colorRes3), res.getColor(colorRes4)); - } - - /** - * Set the four colors used in the progress animation. The first color will - * also be the color of the bar that grows in response to a user swipe - * gesture. - */ - public void setFooterColorSchemeColors(int color1, int color2, int color3, int color4) { - ensureTarget(); - mProgressBar.setColorScheme(color1, color2, color3, color4); - } - - /** - * @return Whether the SwipeRefreshWidget is actively showing refresh - * progress. - */ - public boolean isRefreshing() { - return mHeaderRefreshing || mFooterRefreshing; - } - - public boolean isHeaderRefreshing() { - return mHeaderRefreshing; - } - - public boolean isFooterRefreshing() { - return mFooterRefreshing; - } - - private void ensureTarget() { - // Don't bother getting the parent height if the parent hasn't been laid - // out yet. - if (mTarget == null) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (!child.equals(mCircleView)) { - mTarget = child; - break; - } - } - mHeaderOriginalOffsetTop = mTarget.getTop() + getPaddingTop(); - } - if (mFooterDistanceToTriggerSync == -1) { - if (getParent() != null && ((View)getParent()).getHeight() > 0) { - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - mFooterDistanceToTriggerSync = (int) Math.min( - ((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR, - REFRESH_TRIGGER_DISTANCE * metrics.density); - } - } - } - - /** - * Set the distance to trigger a sync in dips - * - * @param distance - */ - public void setDistanceToTriggerSync(int distance) { - mHeaderTotalDragDistance = distance; - } - - private void setTriggerPercentage(float percent) { - /* - if (percent == 0f) { - // No-op. A null trigger means it's uninitialized, and setting it to zero-percent - // means we're trying to reset state, so there's nothing to reset in this case. - mFooterCurrPercentage = 0; - return; - } - */ - mFooterCurrPercentage = percent; - mProgressBar.setTriggerPercentage(percent); - } - - @Override - public void draw(@NonNull Canvas canvas) { - super.draw(canvas); - mProgressBar.draw(canvas); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final int width = right - left; - final int height = bottom - top; - if (getChildCount() == 0) { - return; - } - if (mTarget == null) { - ensureTarget(); - } - if (mTarget == null) { - return; - } - final View child = mTarget; - final int childLeft = getPaddingLeft(); - final int childTop = getPaddingTop(); - final int childWidth = width - getPaddingLeft() - getPaddingRight(); - final int childHeight = height - getPaddingTop() - getPaddingBottom(); - child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); - int circleWidth = mCircleView.getMeasuredWidth(); - int circleHeight = mCircleView.getMeasuredHeight(); - mCircleView.layout((width / 2 - circleWidth / 2), mHeaderCurrentTargetOffsetTop, - (width / 2 + circleWidth / 2), mHeaderCurrentTargetOffsetTop + circleHeight); - mProgressBar.setBounds(0, height - mProgressBarHeight, width, height); - } - - @Override - public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mTarget == null) { - ensureTarget(); - } - if (mTarget == null) { - return; - } - mTarget.measure(MeasureSpec.makeMeasureSpec( - getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), - MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( - getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); - mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY)); - if (!mHeaderUsingCustomStart && !mHeaderOriginalOffsetCalculated) { - mHeaderOriginalOffsetCalculated = true; - mHeaderCurrentTargetOffsetTop = mHeaderOriginalOffsetTop = -mCircleView.getMeasuredHeight(); - } - mCircleViewIndex = -1; - // Get the index of the circleview. - for (int index = 0; index < getChildCount(); index++) { - if (getChildAt(index) == mCircleView) { - mCircleViewIndex = index; - break; - } - } - } - - /** - * Get the diameter of the progress circle that is displayed as part of the - * swipe to refresh layout. This is not valid until a measure pass has - * completed. - * - * @return Diameter in pixels of the progress circle view. - */ - public int getProgressCircleDiameter() { - return mCircleView != null ?mCircleView.getMeasuredHeight() : 0; - } - - /** - * @return Whether it is possible for the child view of this layout to - * scroll up. Override this if the child view is a custom view. - */ - public boolean canChildScrollUp() { - if (android.os.Build.VERSION.SDK_INT < 14) { - if (mTarget instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTarget; - return absListView.getChildCount() > 0 - && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) - .getTop() < absListView.getPaddingTop()); - } else { - return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; - } - } else { - return ViewCompat.canScrollVertically(mTarget, -1); - } - } - - /** - * @return Whether it is possible for the child view of this layout to - * scroll down. Override this if the child view is a custom view. - */ - public boolean canChildScrollDown() { - if (mTarget instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTarget; - return absListView.getChildCount() > 0 - && (absListView.getLastVisiblePosition() < absListView.getCount() - 1 || - absListView.getChildAt(0).getBottom() < absListView.getWidth() - absListView.getPaddingBottom()); - } else { - return ViewCompat.canScrollVertically(mTarget, 1); - } - } - - /** - * @return {@code true} if child view almost scroll to bottom. - */ - public boolean isAlmostBottom() { - if (mTarget instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTarget; - return absListView.getLastVisiblePosition() >= absListView.getCount() - 1; - } else if (mTarget instanceof ScrollingView) { - final ScrollingView scrollingView = (ScrollingView) mTarget; - final int offset = scrollingView.computeVerticalScrollOffset(); - final int range = scrollingView.computeVerticalScrollRange() - - scrollingView.computeVerticalScrollExtent(); - return offset >= range; - } else { - return !ViewCompat.canScrollVertically(mTarget, 1); - } - } - - private boolean headerInterceptTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - switch (action) { - case MotionEvent.ACTION_DOWN: - setHeaderTargetOffsetTopAndBottom(mHeaderOriginalOffsetTop - mCircleView.getTop(), true); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mIsHeaderBeingDragged = false; - final float initialDownY = getMotionEventY(ev, mActivePointerId); - if (initialDownY == -1) { - return false; - } - mInitialDownY = initialDownY; - break; - - case MotionEvent.ACTION_MOVE: - if (mActivePointerId == INVALID_POINTER) { - Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); - return false; - } - - final float y = getMotionEventY(ev, mActivePointerId); - if (y == -1) { - return false; - } - final float yDiff = y - mInitialDownY; - if (yDiff > mTouchSlop && !mIsHeaderBeingDragged) { - mInitialMotionY = mInitialDownY + mTouchSlop; - mIsHeaderBeingDragged = true; - mProgress.setAlpha(STARTING_PROGRESS_ALPHA); - } - break; - - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mIsHeaderBeingDragged = false; - mActivePointerId = INVALID_POINTER; - break; - } - - return mIsHeaderBeingDragged; - } - - private boolean footerInterceptTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - switch (action) { - case MotionEvent.ACTION_DOWN: - mLastMotionY = mInitialMotionY = ev.getY(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mIsFooterBeingDragged = false; - mFooterCurrPercentage = 0; - break; - - case MotionEvent.ACTION_MOVE: - if (mActivePointerId == INVALID_POINTER) { - Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); - return false; - } - - final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - if (pointerIndex < 0) { - Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); - return false; - } - - final float y = MotionEventCompat.getY(ev, pointerIndex); - final float yDiff = y - mInitialMotionY; - if (yDiff < -mTouchSlop) { - mLastMotionY = y; - mIsFooterBeingDragged = true; - } - break; - - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mIsFooterBeingDragged = false; - mFooterCurrPercentage = 0; - mActivePointerId = INVALID_POINTER; - break; - } - - return mIsFooterBeingDragged; - } - - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - try { - ensureTarget(); - - final int action = MotionEventCompat.getActionMasked(ev); - - if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { - mReturningToStart = false; - } - - boolean mIsBeingDragged = false; - - if (isEnabled() && !mReturningToStart && !mHeaderRefreshing && !mFooterRefreshing) { - if (!mIsFooterBeingDragged && mEnableSwipeHeader && !canChildScrollUp()) { - mIsBeingDragged = headerInterceptTouchEvent(ev); - } - - if (!mIsHeaderBeingDragged && mEnableSwipeFooter && !canChildScrollDown()) { - mIsBeingDragged |= footerInterceptTouchEvent(ev); - } - } - - return mIsBeingDragged; - } catch (Exception e) { - return false; - } - } - - private float getMotionEventY(MotionEvent ev, int activePointerId) { - final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); - if (index < 0) { - return -1; - } - return MotionEventCompat.getY(ev, index); - } - - @Override - public void requestDisallowInterceptTouchEvent(boolean b) { - // Nope. - } - - private boolean isAnimationRunning(Animation animation) { - return animation != null && animation.hasStarted() && !animation.hasEnded(); - } - - private boolean headerTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mIsHeaderBeingDragged = false; - break; - - case MotionEvent.ACTION_MOVE: { - final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - if (pointerIndex < 0) { - Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); - return false; - } - - float y; - try { - y = MotionEventCompat.getY(ev, pointerIndex); - } catch (IllegalArgumentException e) { - y = mInitialMotionY; - } - final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; - if (mIsHeaderBeingDragged) { - mProgress.showArrow(true); - float originalDragPercent = overscrollTop / mHeaderTotalDragDistance; - if (originalDragPercent < 0) { - return false; - } - float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); - float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; - float extraOS = Math.abs(overscrollTop) - mHeaderTotalDragDistance; - float slingshotDist = mHeaderUsingCustomStart ? mSpinnerFinalOffset - - mHeaderOriginalOffsetTop : mSpinnerFinalOffset; - float tensionSlingshotPercent = Math.max(0, - Math.min(extraOS, slingshotDist * 2) / slingshotDist); - float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( - (tensionSlingshotPercent / 4), 2)) * 2f; - float extraMove = (slingshotDist) * tensionPercent * 2; - - int targetY = mHeaderOriginalOffsetTop - + (int) ((slingshotDist * dragPercent) + extraMove); - // where 1.0f is a full circle - if (mCircleView.getVisibility() != View.VISIBLE) { - mCircleView.setVisibility(View.VISIBLE); - } - if (!mHeaderScale) { - ViewCompat.setScaleX(mCircleView, 1f); - ViewCompat.setScaleY(mCircleView, 1f); - } - if (overscrollTop < mHeaderTotalDragDistance) { - if (mHeaderScale) { - setAnimationProgress(overscrollTop / mHeaderTotalDragDistance); - } - if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA - && !isAnimationRunning(mHeaderAlphaStartAnimation)) { - // Animate the alpha - startHeaderProgressAlphaStartAnimation(); - } - float strokeStart = adjustedPercent * .8f; - mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); - mProgress.setArrowScale(Math.min(1f, adjustedPercent)); - } else { - if (mProgress.getAlpha() < MAX_ALPHA - && !isAnimationRunning(mHeaderAlphaMaxAnimation)) { - // Animate the alpha - startHeaderProgressAlphaMaxAnimation(); - } - } - float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; - mProgress.setProgressRotation(rotation); - setHeaderTargetOffsetTopAndBottom(targetY - mHeaderCurrentTargetOffsetTop, - true /* requires update */); - } - break; - } - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int index = MotionEventCompat.getActionIndex(ev); - mActivePointerId = MotionEventCompat.getPointerId(ev, index); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - if (mActivePointerId == INVALID_POINTER) { - if (action == MotionEvent.ACTION_UP) { - Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); - } - return false; - } - final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float y = MotionEventCompat.getY(ev, pointerIndex); - final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; - mIsHeaderBeingDragged = false; - if (overscrollTop > mHeaderTotalDragDistance) { - setHeaderRefreshing(true, true /* notify */); - } else { - // cancel refresh - mHeaderRefreshing = false; - mProgress.setStartEndTrim(0f, 0f); - AnimationListener listener = null; - if (!mHeaderScale) { - listener = new AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - if (!mHeaderScale) { - startScaleDownAnimation(null); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - }; - } - animateHeaderOffsetToStartPosition(mHeaderCurrentTargetOffsetTop, listener); - mProgress.showArrow(false); - } - mActivePointerId = INVALID_POINTER; - return false; - } - } - - return mIsHeaderBeingDragged; - } - - private void startFooterRefresh() { - removeCallbacks(mCancel); - mReturnToStartPosition.run(); - setFooterRefreshing(true); - mListener.onFooterRefresh(); - } - - private boolean footerTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - - int pointerIndex; - float y; - float yDiff; - switch (action) { - case MotionEvent.ACTION_DOWN: - mLastMotionY = mInitialMotionY = ev.getY(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mIsFooterBeingDragged = false; - mFooterCurrPercentage = 0; - break; - - case MotionEvent.ACTION_MOVE: - pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - if (pointerIndex < 0) { - Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); - return false; - } - - y = MotionEventCompat.getY(ev, pointerIndex); - yDiff = y - mInitialMotionY; - - if (!mIsFooterBeingDragged && yDiff < -mTouchSlop) { - mIsFooterBeingDragged = true; - } - - if (mIsFooterBeingDragged) { - setTriggerPercentage( - mAccelerateInterpolator.getInterpolation( - MathUtils.clamp(-yDiff, 0, mFooterDistanceToTriggerSync) / mFooterDistanceToTriggerSync)); - mLastMotionY = y; - } - break; - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int index = MotionEventCompat.getActionIndex(ev); - mLastMotionY = MotionEventCompat.getY(ev, index); - mActivePointerId = MotionEventCompat.getPointerId(ev, index); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - if (mActivePointerId == INVALID_POINTER && pointerIndex < 0) { - if (action == MotionEvent.ACTION_UP) { - Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); - } - return false; - } - - try { - y = MotionEventCompat.getY(ev, pointerIndex); - } catch (Throwable e) { - y = 0; - } - - yDiff = y - mInitialMotionY; - - if (action == MotionEvent.ACTION_UP && -yDiff > mFooterDistanceToTriggerSync) { - // User movement passed distance; trigger a refresh - startFooterRefresh(); - } else { - mCancel.run(); - } - - mIsFooterBeingDragged = false; - mFooterCurrPercentage = 0; - mActivePointerId = INVALID_POINTER; - return false; - } - - return mIsFooterBeingDragged; - } - - @Override - public boolean onTouchEvent(@NonNull MotionEvent ev) { - try { - final int action = MotionEventCompat.getActionMasked(ev); - - if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { - mReturningToStart = false; - } - - if (isEnabled() && !mReturningToStart && !mHeaderRefreshing && !mFooterRefreshing) { - if (!mIsFooterBeingDragged && mEnableSwipeHeader && !canChildScrollUp()) { - headerTouchEvent(ev); - } - - if (!mIsHeaderBeingDragged && mEnableSwipeFooter && !canChildScrollDown()) { - footerTouchEvent(ev); - } - } - - return true; - } catch (Exception e) { - return false; - } - } - - private void animateHeaderOffsetToCorrectPosition(int from, AnimationListener listener) { - mHeaderFrom = from; - mAnimateHeaderToCorrectPosition.reset(); - mAnimateHeaderToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); - mAnimateHeaderToCorrectPosition.setInterpolator(mDecelerateInterpolator); - if (listener != null) { - mCircleView.setAnimationListener(listener); - } - mCircleView.clearAnimation(); - mCircleView.startAnimation(mAnimateHeaderToCorrectPosition); - } - - private void animateHeaderOffsetToStartPosition(int from, AnimationListener listener) { - if (mHeaderScale) { - // Scale the item back down - startHeaderScaleDownReturnToStartAnimation(from, listener); - } else { - mHeaderFrom = from; - mAnimateHeaderToStartPosition.reset(); - mAnimateHeaderToStartPosition.setDuration(ANIMATE_TO_START_DURATION); - mAnimateHeaderToStartPosition.setInterpolator(mDecelerateInterpolator); - if (listener != null) { - mCircleView.setAnimationListener(listener); - } - mCircleView.clearAnimation(); - mCircleView.startAnimation(mAnimateHeaderToStartPosition); - } - } - - private final Animation mAnimateHeaderToCorrectPosition = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - int targetTop = 0; - int endTarget = 0; - if (!mHeaderUsingCustomStart) { - endTarget = (int) (mSpinnerFinalOffset - Math.abs(mHeaderOriginalOffsetTop)); - } else { - endTarget = (int) mSpinnerFinalOffset; - } - targetTop = (mHeaderFrom + (int) ((endTarget - mHeaderFrom) * interpolatedTime)); - int offset = targetTop - mCircleView.getTop(); - setHeaderTargetOffsetTopAndBottom(offset, false /* requires update */); - mProgress.setArrowScale(1 - interpolatedTime); - } - }; - - private void moveHeaderToStart(float interpolatedTime) { - int targetTop = 0; - targetTop = (mHeaderFrom + (int) ((mHeaderOriginalOffsetTop - mHeaderFrom) * interpolatedTime)); - int offset = targetTop - mCircleView.getTop(); - setHeaderTargetOffsetTopAndBottom(offset, false /* requires update */); - } - - private final Animation mAnimateHeaderToStartPosition = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - moveHeaderToStart(interpolatedTime); - } - }; - - private void startHeaderScaleDownReturnToStartAnimation(int from, - AnimationListener listener) { - mHeaderFrom = from; - if (isAlphaUsedForScale()) { - mHeaderStartingScale = mProgress.getAlpha(); - } else { - mHeaderStartingScale = ViewCompat.getScaleX(mCircleView); - } - mHeaderScaleDownToStartAnimation = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - float targetScale = (mHeaderStartingScale + (-mHeaderStartingScale * interpolatedTime)); - setAnimationProgress(targetScale); - moveHeaderToStart(interpolatedTime); - } - }; - mHeaderScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); - if (listener != null) { - mCircleView.setAnimationListener(listener); - } - mCircleView.clearAnimation(); - mCircleView.startAnimation(mHeaderScaleDownToStartAnimation); - } - - private void setHeaderTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { - mCircleView.bringToFront(); - mCircleView.offsetTopAndBottom(offset); - mHeaderCurrentTargetOffsetTop = mCircleView.getTop(); - if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { - invalidate(); - } - } - - private void animateFooterOffsetToStartPosition(int from, AnimationListener listener) { - mFooterFrom = from; - mAnimateFooterToStartPosition.reset(); - mAnimateFooterToStartPosition.setDuration(mMediumAnimationDuration); - mAnimateFooterToStartPosition.setAnimationListener(listener); - mAnimateFooterToStartPosition.setInterpolator(mDecelerateInterpolator); - mTarget.startAnimation(mAnimateFooterToStartPosition); - } - - /* - private void setFooterTargetOffsetTopAndBottom(int offset) { - mTarget.offsetTopAndBottom(offset); - mFooterCurrentTargetOffsetTop = mTarget.getTop(); - } - */ - - /* - private void updateContentOffsetTop(int targetTop) { - final int currentTop = mTarget.getTop(); - if (targetTop < -mFooterDistanceToTriggerSync) { - targetTop = -(int) mFooterDistanceToTriggerSync; - } else if (targetTop > 0) { - targetTop = 0; - } - setFooterTargetOffsetTopAndBottom(targetTop - currentTop); - } - */ - - private void updatePositionTimeout() { - removeCallbacks(mCancel); - postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT); - } - - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = MotionEventCompat.getActionIndex(ev); - final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex); - mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); - } - } - - /** - * Classes that wish to be notified when the swipe gesture correctly - * triggers a refresh should implement this interface. - */ - public interface OnRefreshListener { - void onHeaderRefresh(); - - void onFooterRefresh(); - } - - /** - * Simple AnimationListener to avoid having to implement unneeded methods in - * AnimationListeners. - */ - private class BaseAnimationListener implements AnimationListener { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - } -} diff --git a/app/src/main/java/com/hippo/widget/refreshlayout/SwipeProgressBar.java b/app/src/main/java/com/hippo/widget/refreshlayout/SwipeProgressBar.java deleted file mode 100644 index 577149ae0..000000000 --- a/app/src/main/java/com/hippo/widget/refreshlayout/SwipeProgressBar.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.widget.refreshlayout; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.os.Build; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.animation.FastOutSlowInInterpolator; -import android.view.View; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; - - -/** - * Custom progress bar that shows a cycle of colors as widening circles that - * overdraw each other. When finished, the bar is cleared from the inside out as - * the main cycle continues. Before running, this can also indicate how close - * the user is to triggering something (e.g. how far they need to pull down to - * trigger a refresh). - */ -final class SwipeProgressBar { - - private static final boolean SUPPORT_CLIP_RECT_DIFFERENCE = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; - - // Default progress animation colors are grays. - private final static int COLOR1 = 0xB3000000; - private final static int COLOR2 = 0x80000000; - private final static int COLOR3 = 0x4d000000; - private final static int COLOR4 = 0x1a000000; - - // The duration of the animation cycle. - private static final int ANIMATION_DURATION_MS = 2000; - - // The duration of the animation to clear the bar. - private static final int FINISH_ANIMATION_DURATION_MS = 1000; - - // Interpolator for varying the speed of the animation. - private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); - - private final Paint mPaint = new Paint(); - private final RectF mClipRect = new RectF(); - private float mTriggerPercentage; - private long mStartTime; - private long mFinishTime; - private boolean mRunning; - - // Colors used when rendering the animation, - private int mColor1; - private int mColor2; - private int mColor3; - private int mColor4; - private final View mParent; - - private final Rect mBounds = new Rect(); - - public SwipeProgressBar(View parent) { - mParent = parent; - mColor1 = COLOR1; - mColor2 = COLOR2; - mColor3 = COLOR3; - mColor4 = COLOR4; - } - - /** - * Set the four colors used in the progress animation. The first color will - * also be the color of the bar that grows in response to a user swipe - * gesture. - * - * @param color1 Integer representation of a color. - * @param color2 Integer representation of a color. - * @param color3 Integer representation of a color. - * @param color4 Integer representation of a color. - */ - void setColorScheme(int color1, int color2, int color3, int color4) { - mColor1 = color1; - mColor2 = color2; - mColor3 = color3; - mColor4 = color4; - } - - /** - * Update the progress the user has made toward triggering the swipe - * gesture. and use this value to update the percentage of the trigger that - * is shown. - */ - void setTriggerPercentage(float triggerPercentage) { - mTriggerPercentage = triggerPercentage; - mStartTime = 0; - ViewCompat.postInvalidateOnAnimation( - mParent, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); - } - - /** - * Start showing the progress animation. - */ - void start() { - if (!mRunning) { - mTriggerPercentage = 0; - mStartTime = AnimationUtils.currentAnimationTimeMillis(); - mRunning = true; - mParent.postInvalidate(); - } - } - - /** - * Stop showing the progress animation. - */ - void stop() { - if (mRunning) { - mTriggerPercentage = 0; - mFinishTime = AnimationUtils.currentAnimationTimeMillis(); - mRunning = false; - mParent.postInvalidate(); - } - } - - /** - * @return Return whether the progress animation is currently running. - */ - boolean isRunning() { - return mRunning || mFinishTime > 0; - } - - void draw(Canvas canvas) { - // API < 18 do not support clipRect(Region.Op.DIFFERENCE). - // So draw twice for finish animation - if (draw(canvas, true)) { - draw(canvas, false); - } - } - - private boolean draw(Canvas canvas, boolean first) { - Rect bounds = mBounds; - final int width = bounds.width(); - final int cx = bounds.centerX(); - final int cy = bounds.centerY(); - boolean drawTriggerWhileFinishing = false; - boolean drawAgain = false; - int restoreCount = canvas.save(); - canvas.clipRect(bounds); - - if (mRunning || (mFinishTime > 0)) { - long now = AnimationUtils.currentAnimationTimeMillis(); - long elapsed = (now - mStartTime) % ANIMATION_DURATION_MS; - long iterations = (now - mStartTime) / ANIMATION_DURATION_MS; - float rawProgress = (elapsed / (ANIMATION_DURATION_MS / 100f)); - - // If we're not running anymore, that means we're running through - // the finish animation. - if (!mRunning) { - // If the finish animation is done, don't draw anything, and - // don't repost. - if ((now - mFinishTime) >= FINISH_ANIMATION_DURATION_MS) { - mFinishTime = 0; - return false; - } - - // Otherwise, use a 0 opacity alpha layer to clear the animation - // from the inside out. This layer will prevent the circles from - // drawing within its bounds. - long finishElapsed = (now - mFinishTime) % FINISH_ANIMATION_DURATION_MS; - float finishProgress = (finishElapsed / (FINISH_ANIMATION_DURATION_MS / 100f)); - float pct = (finishProgress / 100f); - // Radius of the circle is half of the screen. - float clearRadius = width / 2 * INTERPOLATOR.getInterpolation(pct); - if (SUPPORT_CLIP_RECT_DIFFERENCE) { - mClipRect.set(cx - clearRadius, bounds.top, cx + clearRadius, bounds.bottom); - canvas.clipRect(mClipRect, Region.Op.DIFFERENCE); - } else { - if (first) { - // First time left - drawAgain = true; - mClipRect.set(bounds.left, bounds.top, cx - clearRadius, bounds.bottom); - } else { - // Second time right - mClipRect.set(cx + clearRadius, bounds.top, bounds.right, bounds.bottom); - } - canvas.clipRect(mClipRect); - } - // Only draw the trigger if there is a space in the center of - // this refreshing view that needs to be filled in by the - // trigger. If the progress view is just still animating, let it - // continue animating. - drawTriggerWhileFinishing = true; - } - - // First fill in with the last color that would have finished drawing. - if (iterations == 0) { - canvas.drawColor(mColor1); - } else { - if (rawProgress >= 0 && rawProgress < 25) { - canvas.drawColor(mColor4); - } else if (rawProgress >= 25 && rawProgress < 50) { - canvas.drawColor(mColor1); - } else if (rawProgress >= 50 && rawProgress < 75) { - canvas.drawColor(mColor2); - } else { - canvas.drawColor(mColor3); - } - } - - // Then draw up to 4 overlapping concentric circles of varying radii, based on how far - // along we are in the cycle. - // progress 0-50 draw mColor2 - // progress 25-75 draw mColor3 - // progress 50-100 draw mColor4 - // progress 75 (wrap to 25) draw mColor1 - if ((rawProgress >= 0 && rawProgress <= 25)) { - float pct = (((rawProgress + 25) * 2) / 100f); - drawCircle(canvas, cx, cy, mColor1, pct); - } - if (rawProgress >= 0 && rawProgress <= 50) { - float pct = ((rawProgress * 2) / 100f); - drawCircle(canvas, cx, cy, mColor2, pct); - } - if (rawProgress >= 25 && rawProgress <= 75) { - float pct = (((rawProgress - 25) * 2) / 100f); - drawCircle(canvas, cx, cy, mColor3, pct); - } - if (rawProgress >= 50 && rawProgress <= 100) { - float pct = (((rawProgress - 50) * 2) / 100f); - drawCircle(canvas, cx, cy, mColor4, pct); - } - if ((rawProgress >= 75 && rawProgress <= 100)) { - float pct = (((rawProgress - 75) * 2) / 100f); - drawCircle(canvas, cx, cy, mColor1, pct); - } - if (mTriggerPercentage > 0 && drawTriggerWhileFinishing) { - // There is some portion of trigger to draw. Restore the canvas, - // then draw the trigger. Otherwise, the trigger does not appear - // until after the bar has finished animating and appears to - // just jump in at a larger width than expected. - canvas.restoreToCount(restoreCount); - restoreCount = canvas.save(); - canvas.clipRect(bounds); - drawTrigger(canvas, cx, cy); - } - // Keep running until we finish out the last cycle. - ViewCompat.postInvalidateOnAnimation( - mParent, bounds.left, bounds.top, bounds.right, bounds.bottom); - } else { - // Otherwise if we're in the middle of a trigger, draw that. - if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0) { - drawTrigger(canvas, cx, cy); - } - } - canvas.restoreToCount(restoreCount); - return drawAgain; - } - - private void drawTrigger(Canvas canvas, int cx, int cy) { - mPaint.setColor(mColor1); - canvas.drawCircle(cx, cy, cx * mTriggerPercentage, mPaint); - } - - /** - * Draws a circle centered in the view. - * - * @param canvas the canvas to draw on - * @param cx the center x coordinate - * @param cy the center y coordinate - * @param color the color to draw - * @param pct the percentage of the view that the circle should cover - */ - private void drawCircle(Canvas canvas, float cx, float cy, int color, float pct) { - mPaint.setColor(color); - canvas.save(); - canvas.translate(cx, cy); - float radiusScale = INTERPOLATOR.getInterpolation(pct); - canvas.scale(radiusScale, radiusScale); - canvas.drawCircle(0, 0, cx, mPaint); - canvas.restore(); - } - - /** - * Set the drawing bounds of this SwipeProgressBar. - */ - void setBounds(int left, int top, int right, int bottom) { - mBounds.left = left; - mBounds.top = top; - mBounds.right = right; - mBounds.bottom = bottom; - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/widget_content_layout.xml b/app/src/main/res/layout/widget_content_layout.xml index 3cb378d47..1ae84abe8 100644 --- a/app/src/main/res/layout/widget_content_layout.xml +++ b/app/src/main/res/layout/widget_content_layout.xml @@ -39,7 +39,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -50,7 +50,7 @@ android:layout_height="match_parent" android:clipToPadding="false"/> - +