1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.support.wearable.view; 18 19 import android.animation.ObjectAnimator; 20 import android.animation.TimeInterpolator; 21 import android.animation.ValueAnimator; 22 import android.annotation.TargetApi; 23 import android.graphics.Canvas; 24 import android.graphics.ColorFilter; 25 import android.graphics.Paint; 26 import android.graphics.PixelFormat; 27 import android.graphics.RectF; 28 import android.graphics.drawable.Drawable; 29 import android.os.Build; 30 import android.util.Property; 31 import android.view.animation.LinearInterpolator; 32 33 /** 34 * Drawable for showing an indeterminate progress indicator. 35 * 36 * TODO: When Material progress drawable is available in the support library stop using this. 37 * 38 * @hide 39 */ 40 @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) 41 class ProgressDrawable extends Drawable { 42 43 private static Property<ProgressDrawable, Integer> LEVEL = 44 new Property<ProgressDrawable, Integer>(Integer.class, "level") { 45 @Override 46 public Integer get(ProgressDrawable drawable) { 47 return drawable.getLevel(); 48 } 49 50 @Override 51 public void set(ProgressDrawable drawable, Integer value) { 52 drawable.setLevel(value); 53 drawable.invalidateSelf(); 54 } 55 }; 56 /** Max level for a level drawable, as specified in developer docs for {@link Drawable}. */ 57 private static final int MAX_LEVEL = 10000; 58 59 /** How many different sections are there, five gives us the material style star. **/ 60 private static final int NUMBER_OF_SEGMENTS = 5; 61 62 private static final int LEVELS_PER_SEGMENT = MAX_LEVEL / NUMBER_OF_SEGMENTS; 63 private static final float STARTING_ANGLE = -90f; 64 private static final long ANIMATION_DURATION = 6000; 65 private static final int FULL_CIRCLE = 360; 66 private static final int MAX_SWEEP = 306; 67 private static final int CORRECTION_ANGLE = FULL_CIRCLE - MAX_SWEEP; 68 /** How far through each cycle does the bar stop growing and start shrinking, half way. **/ 69 private static final float GROW_SHRINK_RATIO = 0.5f; 70 // TODO: replace this with BakedBezierInterpolator when its available in support library. 71 private static final TimeInterpolator mInterpolator = Gusterpolator.INSTANCE; 72 73 private final RectF mInnerCircleBounds = new RectF(); 74 private final Paint mPaint = new Paint(); 75 private final ObjectAnimator mAnimator; 76 private float mCircleBorderWidth; 77 private int mCircleBorderColor; 78 ProgressDrawable()79 public ProgressDrawable() { 80 mPaint.setAntiAlias(true); 81 mPaint.setStyle(Paint.Style.STROKE); 82 mAnimator = ObjectAnimator.ofInt(this, LEVEL, 0, MAX_LEVEL); 83 mAnimator.setRepeatCount(ValueAnimator.INFINITE); 84 mAnimator.setRepeatMode(ValueAnimator.RESTART); 85 mAnimator.setDuration(ANIMATION_DURATION); 86 mAnimator.setInterpolator(new LinearInterpolator()); 87 } 88 setRingColor(int color)89 public void setRingColor(int color) { 90 mCircleBorderColor = color; 91 } 92 setRingWidth(float width)93 public void setRingWidth(float width) { 94 mCircleBorderWidth = width; 95 } 96 startAnimation()97 public void startAnimation() { 98 mAnimator.start(); 99 } 100 stopAnimation()101 public void stopAnimation() { 102 mAnimator.cancel(); 103 } 104 105 @Override draw(Canvas canvas)106 public void draw(Canvas canvas) { 107 canvas.save(); 108 mInnerCircleBounds.set(getBounds()); 109 mInnerCircleBounds.inset(mCircleBorderWidth / 2.0f, mCircleBorderWidth / 2.0f); 110 mPaint.setStrokeWidth(mCircleBorderWidth); 111 mPaint.setColor(mCircleBorderColor); 112 113 float sweepAngle = FULL_CIRCLE; 114 boolean growing = false; 115 float correctionAngle = 0; 116 int level = getLevel(); 117 118 int currentSegment = level / LEVELS_PER_SEGMENT; 119 int offset = currentSegment * LEVELS_PER_SEGMENT; 120 float progress = (level - offset) / (float) LEVELS_PER_SEGMENT; 121 122 growing = progress < GROW_SHRINK_RATIO; 123 correctionAngle = CORRECTION_ANGLE * progress; 124 125 if (growing) { 126 sweepAngle = MAX_SWEEP * mInterpolator.getInterpolation( 127 lerpInv(0f, GROW_SHRINK_RATIO, progress)); 128 } else { 129 sweepAngle = MAX_SWEEP * (1.0f - mInterpolator.getInterpolation( 130 lerpInv(GROW_SHRINK_RATIO, 1.0f, progress))); 131 } 132 133 sweepAngle = Math.max(1, sweepAngle); 134 135 canvas.rotate( 136 level * (1.0f / MAX_LEVEL) * 2 * FULL_CIRCLE + STARTING_ANGLE + correctionAngle, 137 mInnerCircleBounds.centerX(), 138 mInnerCircleBounds.centerY()); 139 canvas.drawArc(mInnerCircleBounds, 140 growing ? 0 : MAX_SWEEP - sweepAngle, 141 sweepAngle, 142 false, 143 mPaint); 144 canvas.restore(); 145 } 146 147 @Override 148 public void setAlpha(int i) { 149 // Not supported. 150 } 151 152 @Override 153 public void setColorFilter(ColorFilter colorFilter) { 154 // Not supported. 155 } 156 157 @Override 158 public int getOpacity() { 159 return PixelFormat.OPAQUE; 160 } 161 162 @Override 163 protected boolean onLevelChange(int level) { 164 return true; // Changing the level of this drawable does change its appearance. 165 } 166 167 /** 168 * Returns the interpolation scalar (s) that satisfies the equation: 169 * {@code value = }lerp(a, b, s) 170 * 171 * <p>If {@code a == b}, then this function will return 0. 172 */ 173 private static float lerpInv(float a, float b, float value) { 174 return a != b ? ((value - a) / (b - a)) : 0.0f; 175 } 176 } 177