16412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh/*
26412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * Copyright (C) 2015 The Android Open Source Project
36412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh *
46412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * Licensed under the Apache License, Version 2.0 (the "License");
56412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * you may not use this file except in compliance with the License.
66412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * You may obtain a copy of the License at
76412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh *
86412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh *      http://www.apache.org/licenses/LICENSE-2.0
96412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh *
106412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * Unless required by applicable law or agreed to in writing, software
116412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * distributed under the License is distributed on an "AS IS" BASIS,
126412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * See the License for the specific language governing permissions and
146412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * limitations under the License
156412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh */
166412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
176412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughpackage android.support.wearable.view;
186412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
196412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.animation.ObjectAnimator;
206412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.animation.TimeInterpolator;
216412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.animation.ValueAnimator;
226412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.annotation.TargetApi;
236412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.graphics.Canvas;
246412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.graphics.ColorFilter;
256412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.graphics.Paint;
266412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.graphics.PixelFormat;
276412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.graphics.RectF;
286412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.graphics.drawable.Drawable;
296412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.os.Build;
306412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.util.Property;
316412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughimport android.view.animation.LinearInterpolator;
326412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
336412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh/**
346412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * Drawable for showing an indeterminate progress indicator.
356412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh *
366412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * TODO: When Material progress drawable is available in the support library stop using this.
376412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh *
386412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh * @hide
396412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh */
406412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
416412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hughclass ProgressDrawable extends Drawable {
426412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
436412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static Property<ProgressDrawable, Integer> LEVEL =
446412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh            new Property<ProgressDrawable, Integer>(Integer.class, "level") {
456412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        @Override
466412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        public Integer get(ProgressDrawable drawable) {
476412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh            return drawable.getLevel();
486412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        }
496412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
506412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        @Override
516412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        public void set(ProgressDrawable drawable, Integer value) {
526412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh            drawable.setLevel(value);
536412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh            drawable.invalidateSelf();
546412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        }
556412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    };
566412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    /** Max level for a level drawable, as specified in developer docs for {@link Drawable}. */
576412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final int MAX_LEVEL = 10000;
586412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
596412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    /** How many different sections are there, five gives us the material style star. **/
606412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final int NUMBER_OF_SEGMENTS = 5;
616412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
626412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final int LEVELS_PER_SEGMENT = MAX_LEVEL / NUMBER_OF_SEGMENTS;
636412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final float STARTING_ANGLE = -90f;
646412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final long ANIMATION_DURATION = 6000;
656412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final int FULL_CIRCLE = 360;
666412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final int MAX_SWEEP = 306;
676412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final int CORRECTION_ANGLE = FULL_CIRCLE - MAX_SWEEP;
686412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    /** How far through each cycle does the bar stop growing and start shrinking, half way. **/
696412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final float GROW_SHRINK_RATIO = 0.5f;
706412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    // TODO: replace this with BakedBezierInterpolator when its available in support library.
716412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static final TimeInterpolator mInterpolator = Gusterpolator.INSTANCE;
726412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
736412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private final RectF mInnerCircleBounds = new RectF();
746412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private final Paint mPaint = new Paint();
756412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private final ObjectAnimator mAnimator;
766412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private float mCircleBorderWidth;
776412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private int mCircleBorderColor;
786412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
796412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public ProgressDrawable() {
806412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mPaint.setAntiAlias(true);
816412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mPaint.setStyle(Paint.Style.STROKE);
826412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator = ObjectAnimator.ofInt(this, LEVEL, 0, MAX_LEVEL);
836412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
846412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator.setRepeatMode(ValueAnimator.RESTART);
856412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator.setDuration(ANIMATION_DURATION);
866412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator.setInterpolator(new LinearInterpolator());
876412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
886412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
896412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void setRingColor(int color) {
906412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mCircleBorderColor = color;
916412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
926412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
936412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void setRingWidth(float width) {
946412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mCircleBorderWidth = width;
956412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
966412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
976412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void startAnimation() {
986412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator.start();
996412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1006412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1016412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void stopAnimation() {
1026412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mAnimator.cancel();
1036412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1046412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1056412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    @Override
1066412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void draw(Canvas canvas) {
1076412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        canvas.save();
1086412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mInnerCircleBounds.set(getBounds());
1096412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mInnerCircleBounds.inset(mCircleBorderWidth / 2.0f, mCircleBorderWidth / 2.0f);
1106412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mPaint.setStrokeWidth(mCircleBorderWidth);
1116412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        mPaint.setColor(mCircleBorderColor);
1126412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1136412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        float sweepAngle = FULL_CIRCLE;
1146412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        boolean growing = false;
1156412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        float correctionAngle = 0;
1166412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        int level = getLevel();
1176412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1186412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        int currentSegment = level / LEVELS_PER_SEGMENT;
1196412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        int offset = currentSegment * LEVELS_PER_SEGMENT;
1206412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        float progress = (level - offset) / (float) LEVELS_PER_SEGMENT;
1216412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1226412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        growing = progress < GROW_SHRINK_RATIO;
1236412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        correctionAngle = CORRECTION_ANGLE * progress;
1246412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1256412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        if (growing) {
1266412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh            sweepAngle = MAX_SWEEP * mInterpolator.getInterpolation(
1276412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                    lerpInv(0f, GROW_SHRINK_RATIO, progress));
1286412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        } else {
1296412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh            sweepAngle = MAX_SWEEP * (1.0f - mInterpolator.getInterpolation(
1306412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                    lerpInv(GROW_SHRINK_RATIO, 1.0f, progress)));
1316412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        }
1326412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1336412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        sweepAngle = Math.max(1, sweepAngle);
1346412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1356412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        canvas.rotate(
1366412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                level * (1.0f / MAX_LEVEL) * 2 * FULL_CIRCLE + STARTING_ANGLE + correctionAngle,
1376412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                mInnerCircleBounds.centerX(),
1386412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                mInnerCircleBounds.centerY());
1396412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        canvas.drawArc(mInnerCircleBounds,
1406412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                growing ? 0 : MAX_SWEEP - sweepAngle,
1416412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                sweepAngle,
1426412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                false,
1436412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh                mPaint);
1446412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        canvas.restore();
1456412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1466412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1476412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    @Override
1486412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void setAlpha(int i) {
1496412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        // Not supported.
1506412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1516412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1526412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    @Override
1536412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public void setColorFilter(ColorFilter colorFilter) {
1546412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        // Not supported.
1556412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1566412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1576412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    @Override
1586412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    public int getOpacity() {
1596412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        return PixelFormat.OPAQUE;
1606412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1616412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1626412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    @Override
1636412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    protected boolean onLevelChange(int level) {
1646412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        return true; // Changing the level of this drawable does change its appearance.
1656412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1666412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh
1676412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    /**
1686412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh     * Returns the interpolation scalar (s) that satisfies the equation:
1696412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh     * {@code value = }lerp(a, b, s)
1706412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh     *
1716412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh     * <p>If {@code a == b}, then this function will return 0.
1726412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh     */
1736412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    private static float lerpInv(float a, float b, float value) {
1746412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh        return a != b ? ((value - a) / (b - a)) : 0.0f;
1756412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh    }
1766412dc4a8f47b134af6c5abb5f7f30321bb3ce68Anthony Hugh}
177