136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley/* 236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Copyright (C) 2017 The Android Open Source Project 336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Licensed under the Apache License, Version 2.0 (the "License"); 536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * you may not use this file except in compliance with the License. 636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * You may obtain a copy of the License at 736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * http://www.apache.org/licenses/LICENSE-2.0 936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 1036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Unless required by applicable law or agreed to in writing, software 1136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * distributed under the License is distributed on an "AS IS" BASIS, 1236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * See the License for the specific language governing permissions and 1436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * limitations under the License. 1536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 1636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 1736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleypackage android.support.wear.widget; 1836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 1936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.animation.ObjectAnimator; 2036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.animation.TimeInterpolator; 2136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.animation.ValueAnimator; 2236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.annotation.TargetApi; 2336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.Canvas; 2436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.ColorFilter; 2536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.Paint; 2636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.PixelFormat; 2736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.RectF; 2836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.drawable.Drawable; 2936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.os.Build; 3036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.support.annotation.RestrictTo; 3136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.support.annotation.RestrictTo.Scope; 3236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.util.Property; 3336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.view.animation.LinearInterpolator; 3436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 3536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley/** 3636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Drawable for showing an indeterminate progress indicator. 3736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 3836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * @hide 3936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 4036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) 4136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley@RestrictTo(Scope.LIBRARY_GROUP) 4236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyclass ProgressDrawable extends Drawable { 4336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 4436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final Property<ProgressDrawable, Integer> LEVEL = 4536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley new Property<ProgressDrawable, Integer>(Integer.class, "level") { 4636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 4736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public Integer get(ProgressDrawable drawable) { 4836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return drawable.getLevel(); 4936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 5036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 5136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 5236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void set(ProgressDrawable drawable, Integer value) { 5336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley drawable.setLevel(value); 5436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley drawable.invalidateSelf(); 5536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 5636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley }; 5736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 5836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Max level for a level drawable, as specified in developer docs for {@link Drawable}. 5936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 6036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int MAX_LEVEL = 10000; 6136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 6236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 6336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * How many different sections are there, five gives us the material style star. * 6436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 6536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int NUMBER_OF_SEGMENTS = 5; 6636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 6736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int LEVELS_PER_SEGMENT = MAX_LEVEL / NUMBER_OF_SEGMENTS; 6836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final float STARTING_ANGLE = -90f; 6936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final long ANIMATION_DURATION = 6000; 7036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int FULL_CIRCLE = 360; 7136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int MAX_SWEEP = 306; 7236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int CORRECTION_ANGLE = FULL_CIRCLE - MAX_SWEEP; 7336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 7436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * How far through each cycle does the bar stop growing and start shrinking, half way. * 7536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 7636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final float GROW_SHRINK_RATIO = 0.5f; 7736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley // TODO: replace this with BakedBezierInterpolator when its available in support library. 78e6d045e13c43411c459e3535ede6abc6b6aa1c7bSean Kelley private static final TimeInterpolator sInterpolator = BezierSCurveInterpolator.INSTANCE; 7936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 8036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private final RectF mInnerCircleBounds = new RectF(); 8136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private final Paint mPaint = new Paint(); 8236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private final ObjectAnimator mAnimator; 8336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private float mCircleBorderWidth; 8436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private int mCircleBorderColor; 8536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 8636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley ProgressDrawable() { 8736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setAntiAlias(true); 8836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setStyle(Paint.Style.STROKE); 8936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator = ObjectAnimator.ofInt(this, LEVEL, 0, MAX_LEVEL); 9036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setRepeatCount(ValueAnimator.INFINITE); 9136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setRepeatMode(ValueAnimator.RESTART); 9236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setDuration(ANIMATION_DURATION); 9336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setInterpolator(new LinearInterpolator()); 9436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 9536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 9636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 9736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Returns the interpolation scalar (s) that satisfies the equation: 9836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * {@code value = }lerp(a, b, s) 9936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 10036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * <p>If {@code a == b}, then this function will return 0. 10136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 10236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static float lerpInv(float a, float b, float value) { 10336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return a != b ? ((value - a) / (b - a)) : 0.0f; 10436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 10536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 10636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setRingColor(int color) { 10736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mCircleBorderColor = color; 10836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 10936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 11036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setRingWidth(float width) { 11136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mCircleBorderWidth = width; 11236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 11336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 11436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void startAnimation() { 11536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley if (!mAnimator.isStarted()) { 11636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.start(); 11736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 11836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 11936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 12036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void stopAnimation() { 12136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.cancel(); 12236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 12336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 12436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 12536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void draw(Canvas canvas) { 12636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.save(); 12736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.set(getBounds()); 12836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.inset(mCircleBorderWidth / 2.0f, mCircleBorderWidth / 2.0f); 12936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setStrokeWidth(mCircleBorderWidth); 13036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setColor(mCircleBorderColor); 13136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 13236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley int level = getLevel(); 13336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley int currentSegment = level / LEVELS_PER_SEGMENT; 13436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley int offset = currentSegment * LEVELS_PER_SEGMENT; 13536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley float progress = (level - offset) / (float) LEVELS_PER_SEGMENT; 13636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 13736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley boolean growing = progress < GROW_SHRINK_RATIO; 13836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley float correctionAngle = CORRECTION_ANGLE * progress; 13936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 14036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley float sweepAngle; 14136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 14236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley if (growing) { 14336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley sweepAngle = MAX_SWEEP 14436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * sInterpolator.getInterpolation(lerpInv(0f, GROW_SHRINK_RATIO, progress)); 14536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } else { 14636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley sweepAngle = 14736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley MAX_SWEEP 14836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * (1.0f - sInterpolator.getInterpolation( 14936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley lerpInv(GROW_SHRINK_RATIO, 1.0f, progress))); 15036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 15136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 15236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley sweepAngle = Math.max(1, sweepAngle); 15336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 15436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.rotate( 15536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley level * (1.0f / MAX_LEVEL) * 2 * FULL_CIRCLE + STARTING_ANGLE + correctionAngle, 15636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.centerX(), 15736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.centerY()); 15836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.drawArc( 15936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds, growing ? 0 : MAX_SWEEP - sweepAngle, sweepAngle, false, 16036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint); 16136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.restore(); 16236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 16336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 16436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 16536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setAlpha(int i) { 16636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley // Not supported. 16736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 16836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 16936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 17036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setColorFilter(ColorFilter colorFilter) { 17136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley // Not supported. 17236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 17336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 17436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 17536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public int getOpacity() { 17636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return PixelFormat.OPAQUE; 17736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 17836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 17936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 18036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley protected boolean onLevelChange(int level) { 18136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return true; // Changing the level of this drawable does change its appearance. 18236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 18336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley} 184