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