/* * Copyright (C) 2015 Thomas Robert Altstidl * * 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.tr4android.support.extension.drawable; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; 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.ColorInt; import android.support.v4.view.animation.PathInterpolatorCompat; import android.view.animation.Interpolator; import com.tr4android.appcompat.extension.R; import com.tr4android.support.extension.animation.AnimationUtils; import com.tr4android.support.extension.animation.ValueAnimatorCompat; import com.tr4android.support.extension.utils.ThemeUtils; public class IndeterminateProgressDrawable extends Drawable implements Animatable { private static final String TAG = "Indeterminate Progress"; // Interpolator used for path start private static final Interpolator PATH_START_INTERPOLATOR; static { Path mPathStart = new Path(); mPathStart.lineTo(0.5f, 0); mPathStart.cubicTo(0.7f, 0, 0.6f, 1, 1, 1); PATH_START_INTERPOLATOR = PathInterpolatorCompat.create(mPathStart); } // Interpolator used for path end private static final Interpolator PATH_END_INTERPOLATOR; static { Path mPathEnd = new Path(); mPathEnd.cubicTo(0.2f, 0, 0.1f, 1, 0.5f, 1); mPathEnd.lineTo(1, 1); PATH_END_INTERPOLATOR = PathInterpolatorCompat.create(mPathEnd); } // Paint private Paint mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private float mArcStrokeWidth = -1f; // auto private float mArcPadding = -1f; // auto private RectF mArcRect = new RectF(); // Values that determine the current drawing state private int mRotationCount = 0; private float mRotation; private float mOffset; private float mStart; private float mEnd; // Animator private ValueAnimatorCompat mAnimator; public IndeterminateProgressDrawable(Context context, @ColorInt int color, float stroke, float padding) { mArcStrokeWidth = stroke; mArcPadding = padding; // The paint used to draw the spinning wheel mArcPaint.setStyle(Paint.Style.STROKE); mArcPaint.setStrokeCap(Paint.Cap.SQUARE); mArcPaint.setStrokeJoin(Paint.Join.MITER); mArcPaint.setColor(color); // The animator used to animate the spinning wheel (works back to API 7) mAnimator = AnimationUtils.createAnimator(); mAnimator.setFloatValues(0f, 0.25f); mAnimator.setDuration(1333); mAnimator.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR); mAnimator.setRepeatCount(ValueAnimatorCompat.INFINITE); mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimatorCompat animator) { mOffset = animator.getAnimatedFloatValue(); float fraction = animator.getAnimatedFraction(); mStart = PATH_START_INTERPOLATOR.getInterpolation(fraction) * 0.75f; mEnd = PATH_END_INTERPOLATOR.getInterpolation(fraction) * 0.75f; mRotation = (mRotationCount * 144) + mOffset * 576; invalidateSelf(); } }); mAnimator.setListener(new ValueAnimatorCompat.AnimatorListener() { @Override public void onAnimationStart(ValueAnimatorCompat animator) { } @Override public void onAnimationEnd(ValueAnimatorCompat animator) { } @Override public void onAnimationCancel(ValueAnimatorCompat animator) { } @Override public void onAnimationRepeat(ValueAnimatorCompat animator) { // Update rotation count (rotation does 1 cycle for every 5 animator cycles) mRotationCount = (mRotationCount + 1) % 5; } }); } @Override public void draw(Canvas canvas) { int saveCount = canvas.save(); canvas.rotate(mRotation, mArcRect.centerX(), mArcRect.centerY()); float startAngle = -90 + 360 * (mOffset + mStart); float sweepAngle = 360 * (mEnd - mStart); canvas.drawArc(mArcRect, startAngle, sweepAngle, false, mArcPaint); canvas.restoreToCount(saveCount); } @Override public void setBounds(Rect bounds) { calculateArcMetrics(bounds); super.setBounds(bounds); } @Override public void setBounds(int left, int top, int right, int bottom) { calculateArcMetrics(new Rect(left, top, right, bottom)); super.setBounds(left, top, right, bottom); } @Override public void setAlpha(int i) { mArcPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { mArcPaint.setColorFilter(colorFilter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void start() { mAnimator.start(); } @Override public void stop() { mAnimator.cancel(); } @Override public boolean isRunning() { return mAnimator.isRunning(); } /** * Helper that calculates the bounds and the stroke width of the progress arc * * @param bounds the bounds of the drawable */ private void calculateArcMetrics(Rect bounds) { float size = Math.min(bounds.height(), bounds.width()); float yOffset = (bounds.height() - size) / 2f; float xOffset = (bounds.width() - size) / 2f; float strokeWidth; float padding; if (mArcStrokeWidth == -1f && mArcPadding == -1f) { // auto calculate bounds strokeWidth = 4f / 48f * size; padding = 5f / 48f * size; } else if (mArcStrokeWidth == -1f) { // auto calculate stroke width strokeWidth = 4f / 48f * size; padding = mArcPadding + strokeWidth / 2; } else if (mArcPadding == -1f) { // auto calculate padding strokeWidth = mArcStrokeWidth; padding = 3f / 48f * size + strokeWidth / 2; } else { strokeWidth = mArcStrokeWidth; padding = mArcPadding + strokeWidth / 2; } mArcPaint.setStrokeWidth(strokeWidth); mArcRect.set(bounds.left + padding + xOffset, bounds.top + padding + yOffset, bounds.right - padding - xOffset, bounds.bottom - padding - yOffset); } public static class Builder { private Context mContext; private int mColor; private float mPadding; private float mStrokeWidth; public Builder(Context context) { mContext = context; // Default values mColor = ThemeUtils.getThemeAttrColor(mContext, R.attr.colorAccent); mPadding = -1f; // auto calculates mStrokeWidth = -1f; //auto calculates } public Builder setColor(@ColorInt int color) { mColor = color; return this; } public Builder setPadding(float padding) { mPadding = padding; return this; } public Builder setStrokeWidth(float strokeWidth) { mStrokeWidth = strokeWidth; return this; } public IndeterminateProgressDrawable build() { return new IndeterminateProgressDrawable(mContext, mColor, mStrokeWidth, mPadding); } } }