/* * Copyright (C) 2014 Saúl Díaz * * 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 python.progressbar; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; /** * Circular Progress Drawable. * <p/> * This drawable will produce a circular shape with a ring surrounding it. The ring can appear * both filled and give a little cue when it is empty. * <p/> * The inner circle size, the progress of the outer ring and if it is loading parameters can be * controlled, as well the different colors for the three components. * * @author Saul Diaz <sefford@gmail.com> */ public class CircularProgressDrawable extends Drawable { /** * Factor to convert the factor to paint the arc. * <p/> * In this way the developer can use a more user-friendly [0..1f] progress */ public static final int PROGRESS_FACTOR = -360; /** * Property Inner Circle Scale. * <p/> * The inner ring is supposed to stay 3/4 radius off the outer ring at 100% scale, but this * property can make it grow or shrink via this equation: InnerRadius * Scale. */ public static final String CIRCLE_FILL_PROPERTY = "circleScale"; /** * Property Progress of the outer circle. * <p/> * The progress of the circle. If {@link #setIndeterminate(boolean) indeterminate flag} is set * to FALSE, this property will be used to indicate the completion of the outer circle [0..1f]. * <p/> * If set to TRUE, the drawable will activate the loading mode, where the drawable will * show a 90º arc which will be spinning around the outer circle as much as progress goes. */ public static final String PROGRESS_PROPERTY = "progress"; /** * Logger Tag for Logging purposes. */ public static final String TAG = "CircularProgressDrawable"; /** * Paint object to draw the element. */ private final Paint paint; /** * Ring progress. */ protected float progress; /** * Color for the empty outer ring. */ protected int outlineColor; /** * Color for the completed ring. */ protected int ringColor; /** * Color for the inner circle. */ protected int centerColor; /** * Rectangle where the filling ring will be drawn into. */ protected final RectF arcElements; /** * Width of the filling ring. */ protected final int ringWidth; /** * Scale of the inner circle. It will affect the inner circle size on this equation: * ([Bigger length of the Drawable] / 2) * 0.75. */ protected float circleScale; /** * Set if it is an indeterminate */ protected boolean indeterminate; /** * Creates a new CouponDrawable. * * @param ringWidth Width of the filled ring * @param outlineColor Color for the outline color * @param ringColor Color for the filled ring * @param centerColor Color for the center element */ public CircularProgressDrawable(int ringWidth, int outlineColor, int ringColor, int centerColor) { this.progress = 0; this.outlineColor = outlineColor; this.ringColor = ringColor; this.centerColor = centerColor; this.paint = new Paint(); this.paint.setAntiAlias(true); this.ringWidth = ringWidth; this.arcElements = new RectF(); this.circleScale = 1; this.indeterminate = false; } @Override public void draw(Canvas canvas) { final Rect bounds = getBounds(); // Calculations on the different components sizes int size = bounds.height() > bounds.width() ? bounds.width() : bounds.height(); float outerRadius = ((size / 2) * 0.75f) * 0.937f; float innerRadius = ((size / 2) * 0.75f) * 0.75f; float offsetX = (bounds.width() - outerRadius * 2) / 2; float offsetY = (bounds.height() - outerRadius * 2) / 2; // Outline Circle paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(1); paint.setColor(outlineColor); canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius, paint); // Inner circle paint.setStyle(Paint.Style.FILL); paint.setColor(centerColor); canvas.drawCircle(bounds.centerX(), bounds.centerY(), innerRadius * circleScale, paint); int halfRingWidth = ringWidth / 2; float arcX0 = offsetX + halfRingWidth; float arcY0 = offsetY + halfRingWidth; float arcX = offsetX + outerRadius * 2 - halfRingWidth; float arcY = offsetY + outerRadius * 2 - halfRingWidth; // Outer Circle paint.setColor(ringColor); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(ringWidth); paint.setStrokeCap(Paint.Cap.ROUND); arcElements.set(arcX0, arcY0, arcX, arcY); if (indeterminate) { canvas.drawArc(arcElements, progress, 90, false, paint); } else { canvas.drawArc(arcElements, 89, progress, false, paint); } } @Override public void setAlpha(int alpha) { paint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { // Empty } @Override public int getOpacity() { return paint.getAlpha(); } /** * Returns the progress of the outer ring. * <p/> * Will output a correct value only when the indeterminate mode is set to FALSE. * * @return Progress of the outer ring. */ public float getProgress() { return progress / PROGRESS_FACTOR; } /** * Sets the progress [0..1f] * * @param progress Sets the progress */ public void setProgress(float progress) { if (indeterminate) { this.progress = progress; } else { this.progress = PROGRESS_FACTOR * progress; } invalidateSelf(); } /** * Returns the inner circle scale. * * @return Inner circle scale in float multiplier. */ public float getCircleScale() { return circleScale; } /** * Sets the inner circle scale. * * @param circleScale Inner circle scale. */ public void setCircleScale(float circleScale) { this.circleScale = circleScale; invalidateSelf(); } /** * Get the indeterminate status of the Drawable * * @return TRUE if the Drawable is in indeterminate mode or FALSE if it is in progress mode. */ public boolean isIndeterminate() { return indeterminate; } /** * Sets the indeterminate parameter. * <p/> * The indeterminate parameter will change the behavior of the Drawable. If the indeterminate * mode is set to FALSE, the outer ring will be able to be filled by using {@link #setProgress(float) setProgress}. * <p/> * Otherwise the drawable will enter "loading mode" and a 90º arc will be able to be spinned around * the inner circle. * <p/> * <b>By default, indeterminate mode is set to FALSE.</b> * * @param indeterminate TRUE to activate loading mode. FALSE to activate progress mode. */ public void setIndeterminate(boolean indeterminate) { this.indeterminate = indeterminate; } /** * Gets the outline color. * * @return Outline color of the empty ring. */ public int getOutlineColor() { return outlineColor; } /** * Gets the filled ring color. * * @return Returns the filled ring color. */ public int getRingColor() { return ringColor; } /** * Gets the color of the inner circle. * * @return Inner circle color. */ public int getCenterColor() { return centerColor; } /** * Sets the empty progress outline color. * * @param outlineColor Outline color in Integer format. */ public void setOutlineColor(int outlineColor) { this.outlineColor = outlineColor; invalidateSelf(); } /** * Sets the progress ring color. * * @param ringColor Ring color in Integer format. */ public void setRingColor(int ringColor) { this.ringColor = ringColor; invalidateSelf(); } /** * Sets the inner circle color. * * @param centerColor Inner circle color in Integer format. */ public void setCenterColor(int centerColor) { this.centerColor = centerColor; invalidateSelf(); } }