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 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.wear.widget; 1836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 1936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.animation.ObjectAnimator; 2036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.animation.TimeInterpolator; 2136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.animation.ValueAnimator; 2236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.Canvas; 2336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.ColorFilter; 2436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.Paint; 2536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.PixelFormat; 2636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.RectF; 2736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.graphics.drawable.Drawable; 2836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.util.Property; 2936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyimport android.view.animation.LinearInterpolator; 3036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 31d75a466859fee504b717c529094e318d1278f831Aurimas Liutikasimport androidx.annotation.RestrictTo; 32d75a466859fee504b717c529094e318d1278f831Aurimas Liutikasimport androidx.annotation.RestrictTo.Scope; 33d75a466859fee504b717c529094e318d1278f831Aurimas Liutikas 3436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley/** 3536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Drawable for showing an indeterminate progress indicator. 3636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 3736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * @hide 3836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 39acf36a11d0d08da4a94134e8a125e863f13cb625Aurimas Liutikas@RestrictTo(Scope.LIBRARY) 4036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelleyclass ProgressDrawable extends Drawable { 4136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 4236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final Property<ProgressDrawable, Integer> LEVEL = 4336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley new Property<ProgressDrawable, Integer>(Integer.class, "level") { 4436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 4536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public Integer get(ProgressDrawable drawable) { 4636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return drawable.getLevel(); 4736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 4836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 4936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 5036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void set(ProgressDrawable drawable, Integer value) { 5136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley drawable.setLevel(value); 5236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley drawable.invalidateSelf(); 5336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 5436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley }; 5536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 5636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Max level for a level drawable, as specified in developer docs for {@link Drawable}. 5736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 5836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int MAX_LEVEL = 10000; 5936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 6036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 6136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * How many different sections are there, five gives us the material style star. * 6236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 6336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int NUMBER_OF_SEGMENTS = 5; 6436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 6536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int LEVELS_PER_SEGMENT = MAX_LEVEL / NUMBER_OF_SEGMENTS; 6636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final float STARTING_ANGLE = -90f; 6736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final long ANIMATION_DURATION = 6000; 6836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int FULL_CIRCLE = 360; 6936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int MAX_SWEEP = 306; 7036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final int CORRECTION_ANGLE = FULL_CIRCLE - MAX_SWEEP; 7136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 7236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * How far through each cycle does the bar stop growing and start shrinking, half way. * 7336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 7436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static final float GROW_SHRINK_RATIO = 0.5f; 7536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley // TODO: replace this with BakedBezierInterpolator when its available in support library. 76e6d045e13c43411c459e3535ede6abc6b6aa1c7bSean Kelley private static final TimeInterpolator sInterpolator = BezierSCurveInterpolator.INSTANCE; 7736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 7836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private final RectF mInnerCircleBounds = new RectF(); 7936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private final Paint mPaint = new Paint(); 8036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private final ObjectAnimator mAnimator; 8136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private float mCircleBorderWidth; 8236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private int mCircleBorderColor; 8336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 8436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley ProgressDrawable() { 8536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setAntiAlias(true); 8636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setStyle(Paint.Style.STROKE); 8736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator = ObjectAnimator.ofInt(this, LEVEL, 0, MAX_LEVEL); 8836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setRepeatCount(ValueAnimator.INFINITE); 8936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setRepeatMode(ValueAnimator.RESTART); 9036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setDuration(ANIMATION_DURATION); 9136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.setInterpolator(new LinearInterpolator()); 9236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 9336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 9436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley /** 9536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * Returns the interpolation scalar (s) that satisfies the equation: 9636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * {@code value = }lerp(a, b, s) 9736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * 9836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * <p>If {@code a == b}, then this function will return 0. 9936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley */ 10036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley private static float lerpInv(float a, float b, float value) { 10136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return a != b ? ((value - a) / (b - a)) : 0.0f; 10236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 10336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 10436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setRingColor(int color) { 10536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mCircleBorderColor = color; 10636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 10736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 10836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setRingWidth(float width) { 10936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mCircleBorderWidth = width; 11036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 11136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 11236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void startAnimation() { 11336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley if (!mAnimator.isStarted()) { 11436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.start(); 11536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 11636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 11736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 11836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void stopAnimation() { 11936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mAnimator.cancel(); 12036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 12136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 12236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 12336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void draw(Canvas canvas) { 12436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.save(); 12536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.set(getBounds()); 12636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.inset(mCircleBorderWidth / 2.0f, mCircleBorderWidth / 2.0f); 12736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setStrokeWidth(mCircleBorderWidth); 12836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint.setColor(mCircleBorderColor); 12936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 13036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley int level = getLevel(); 13136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley int currentSegment = level / LEVELS_PER_SEGMENT; 13236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley int offset = currentSegment * LEVELS_PER_SEGMENT; 13336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley float progress = (level - offset) / (float) LEVELS_PER_SEGMENT; 13436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 13536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley boolean growing = progress < GROW_SHRINK_RATIO; 13636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley float correctionAngle = CORRECTION_ANGLE * progress; 13736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 13836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley float sweepAngle; 13936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 14036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley if (growing) { 14136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley sweepAngle = MAX_SWEEP 14236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * sInterpolator.getInterpolation(lerpInv(0f, GROW_SHRINK_RATIO, progress)); 14336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } else { 14436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley sweepAngle = 14536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley MAX_SWEEP 14636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley * (1.0f - sInterpolator.getInterpolation( 14736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley lerpInv(GROW_SHRINK_RATIO, 1.0f, progress))); 14836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 14936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 15036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley sweepAngle = Math.max(1, sweepAngle); 15136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 15236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.rotate( 15336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley level * (1.0f / MAX_LEVEL) * 2 * FULL_CIRCLE + STARTING_ANGLE + correctionAngle, 15436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.centerX(), 15536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds.centerY()); 15636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.drawArc( 15736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mInnerCircleBounds, growing ? 0 : MAX_SWEEP - sweepAngle, sweepAngle, false, 15836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley mPaint); 15936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley canvas.restore(); 16036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 16136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 16236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 16336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setAlpha(int i) { 16436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley // Not supported. 16536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 16636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 16736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 16836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public void setColorFilter(ColorFilter colorFilter) { 16936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley // Not supported. 17036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 17136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 17236545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 17336545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley public int getOpacity() { 17436545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return PixelFormat.OPAQUE; 17536545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 17636545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley 17736545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley @Override 17836545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley protected boolean onLevelChange(int level) { 17936545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley return true; // Changing the level of this drawable does change its appearance. 18036545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley } 18136545bb3f2b12af352e550c278cff9026a18ca54Sean Kelley} 182