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