Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d7f4a3cd authored by Alan Viverette's avatar Alan Viverette
Browse files

Remove material progress drawable

BUG: 16138805
Change-Id: If8a8981e6ce741d563e870e3c09cbb5f39d30ac9
parent 2db72ad4
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -1019,9 +1019,6 @@ public abstract class Drawable {
            drawable = new StateListDrawable();
        } else if (name.equals("animated-selector")) {
            drawable = new AnimatedStateListDrawable();
        } else if (name.equals("material-progress")) {
            // TODO: Replace this with something less ridiculous.
            drawable = new MaterialProgressDrawable();
        } else if (name.equals("level-list")) {
            drawable = new LevelListDrawable();
        } else if (name.equals("layer-list")) {
+0 −548
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.graphics.drawable;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff.Mode;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.LinearInterpolator;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Fancy progress indicator for Material theme.
 *
 * TODO: Replace this class with something less ridiculous.
 */
class MaterialProgressDrawable extends Drawable implements Animatable {
    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
    private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
    private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();

    /** The duration of a single progress spin in milliseconds. */
    private static final int ANIMATION_DURATION = 1000 * 80 / 60;

    /** The number of points in the progress "star". */
    private static final int NUM_POINTS = 5;

    /** The list of animators operating on this drawable. */
    private final ArrayList<Animator> mAnimators = new ArrayList<Animator>();

    /** The indicator ring, used to manage animation state. */
    private final Ring mRing;

    private MaterialProgressState mState;
    private PorterDuffColorFilter mTintFilter;

    /** Canvas rotation in degrees. */
    private float mRotation;

    private boolean mMutated;

    public MaterialProgressDrawable() {
        this(new MaterialProgressState(null), null);
    }

    private MaterialProgressDrawable(MaterialProgressState state, Theme theme) {
        mState = state;
        if (theme != null && state.canApplyTheme()) {
            applyTheme(theme);
        }

        mRing = new Ring(mCallback);
        mMutated = false;

        initializeFromState();
        setupAnimators();
    }

    private void initializeFromState() {
        final MaterialProgressState state = mState;

        final Ring ring = mRing;
        ring.setStrokeWidth(state.mStrokeWidth);

        final int color = state.mColor.getColorForState(getState(), Color.TRANSPARENT);
        ring.setColor(color);

        final float minEdge = Math.min(state.mWidth, state.mHeight);
        if (state.mInnerRadius <= 0 || minEdge < 0) {
            ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0f));
        } else {
            float insets = minEdge / 2.0f - state.mInnerRadius;
            ring.setInsets(insets);
        }

        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
    }

    @Override
    public Drawable mutate() {
        if (!mMutated && super.mutate() == this) {
            mState = new MaterialProgressState(mState);
            mMutated = true;
        }
        return this;
    }

    @Override
    protected boolean onStateChange(int[] stateSet) {
        boolean changed = super.onStateChange(stateSet);

        final MaterialProgressState state = mState;
        final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT);
        if (color != mRing.getColor()) {
            mRing.setColor(color);
            changed = true;
        }

        if (state.mTint != null && state.mTintMode != null) {
            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
            changed = true;
        }

        return changed;
    }

    @Override
    public boolean isStateful() {
        return super.isStateful() || mState.mColor.isStateful();
    }

    @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
            throws XmlPullParserException, IOException {
        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.MaterialProgressDrawable);
        super.inflateWithAttributes(r, parser, a, R.styleable.MaterialProgressDrawable_visible);
        updateStateFromTypedArray(a);
        a.recycle();

        initializeFromState();
    }

    @Override
    public void applyTheme(Theme t) {
        final TypedArray a = t.resolveAttributes(mState.mThemeAttrs,
                R.styleable.MaterialProgressDrawable);
        updateStateFromTypedArray(a);
        a.recycle();
    }

    private void updateStateFromTypedArray(TypedArray a) {
        final MaterialProgressState state = mState;
        state.mThemeAttrs = a.extractThemeAttrs();
        state.mWidth = a.getDimensionPixelSize(
                R.styleable.MaterialProgressDrawable_width, state.mWidth);
        state.mHeight = a.getDimensionPixelSize(
                R.styleable.MaterialProgressDrawable_height, state.mHeight);
        state.mInnerRadius = a.getDimension(
                R.styleable.MaterialProgressDrawable_innerRadius, state.mInnerRadius);
        state.mStrokeWidth = a.getDimension(
                R.styleable.MaterialProgressDrawable_thickness, state.mStrokeWidth);

        if (a.hasValue(R.styleable.MaterialProgressDrawable_color)) {
            state.mColor = a.getColorStateList(R.styleable.MaterialProgressDrawable_color);
        }
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        boolean changed = super.setVisible(visible, restart);
        if (visible) {
            if (changed || restart) {
                start();
            }
        } else {
            stop();
        }
        return changed;
    }

    @Override
    public int getIntrinsicHeight() {
        return mState.mHeight;
    }

    @Override
    public int getIntrinsicWidth() {
        return mState.mWidth;
    }

    @Override
    public void draw(Canvas c) {
        final Rect bounds = getBounds();
        final int saveCount = c.save();
        c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
        mRing.draw(c, bounds);
        c.restoreToCount(saveCount);
    }

    @Override
    public void setAlpha(int alpha) {
        mRing.setAlpha(alpha);
    }

    @Override
    public int getAlpha() {
        return mRing.getAlpha();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mRing.setColorFilter(colorFilter);
    }

    @Override
    public ColorFilter getColorFilter() {
        return mRing.getColorFilter();
    }

    @Override
    public void setTint(ColorStateList tint, Mode tintMode) {
        if (mState.mTint != tint || mState.mTintMode != tintMode) {
            mState.mTint = tint;
            mState.mTintMode = tintMode;

            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
            invalidateSelf();
        }
    }

    @SuppressWarnings("unused")
    private void setRotation(float rotation) {
        mRotation = rotation;
        invalidateSelf();
    }

    @SuppressWarnings("unused")
    private float getRotation() {
        return mRotation;
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public boolean isRunning() {
        final ArrayList<Animator> animators = mAnimators;
        final int N = animators.size();
        for (int i = 0; i < N; i++) {
            final Animator animator = animators.get(i);
            if (animator.isRunning()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void start() {
        final ArrayList<Animator> animators = mAnimators;
        final int N = animators.size();
        for (int i = 0; i < N; i++) {
            final Animator animator = animators.get(i);
            if (animator.isPaused()) {
                animator.resume();
            } else if (!animator.isRunning()){
                animator.start();
            }
        }
    }

    @Override
    public void stop() {
        final ArrayList<Animator> animators = mAnimators;
        final int N = animators.size();
        for (int i = 0; i < N; i++) {
            final Animator animator = animators.get(i);
            animator.pause();
        }
    }

    private void setupAnimators() {
        final Ring ring = mRing;

        final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f);
        endTrim.setDuration(ANIMATION_DURATION);
        endTrim.setInterpolator(START_CURVE_INTERPOLATOR);
        endTrim.setRepeatCount(ObjectAnimator.INFINITE);
        endTrim.setRepeatMode(ObjectAnimator.RESTART);

        final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f);
        startTrim.setDuration(ANIMATION_DURATION);
        startTrim.setInterpolator(END_CURVE_INTERPOLATOR);
        startTrim.setRepeatCount(ObjectAnimator.INFINITE);
        startTrim.setRepeatMode(ObjectAnimator.RESTART);

        final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f);
        rotation.setDuration(ANIMATION_DURATION);
        rotation.setInterpolator(LINEAR_INTERPOLATOR);
        rotation.setRepeatCount(ObjectAnimator.INFINITE);
        rotation.setRepeatMode(ObjectAnimator.RESTART);

        final ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", 0.0f, 360.0f);
        groupRotation.setDuration(NUM_POINTS * ANIMATION_DURATION);
        groupRotation.setInterpolator(LINEAR_INTERPOLATOR);
        groupRotation.setRepeatCount(ObjectAnimator.INFINITE);
        groupRotation.setRepeatMode(ObjectAnimator.RESTART);

        mAnimators.add(endTrim);
        mAnimators.add(startTrim);
        mAnimators.add(rotation);
        mAnimators.add(groupRotation);
    }

    private final Callback mCallback = new Callback() {
        @Override
        public void invalidateDrawable(Drawable d) {
            invalidateSelf();
        }

        @Override
        public void scheduleDrawable(Drawable d, Runnable what, long when) {
            scheduleSelf(what, when);
        }

        @Override
        public void unscheduleDrawable(Drawable d, Runnable what) {
            unscheduleSelf(what);
        }
    };

    private static class MaterialProgressState extends ConstantState {
        private int[] mThemeAttrs = null;
        private float mStrokeWidth = 5.0f;
        private float mInnerRadius = -1.0f;
        private int mWidth = -1;
        private int mHeight = -1;
        private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT);
        private ColorStateList mTint = null;
        private Mode mTintMode = null;

        public MaterialProgressState(MaterialProgressState orig) {
            if (orig != null) {
                mThemeAttrs = orig.mThemeAttrs;
                mStrokeWidth = orig.mStrokeWidth;
                mInnerRadius = orig.mInnerRadius;
                mWidth = orig.mWidth;
                mHeight = orig.mHeight;
                mColor = orig.mColor;
                mTint = orig.mTint;
                mTintMode = orig.mTintMode;
            }
        }

        @Override
        public boolean canApplyTheme() {
            return mThemeAttrs != null;
        }

        @Override
        public Drawable newDrawable() {
            return newDrawable(null, null);
        }

        @Override
        public Drawable newDrawable(Resources res) {
            return newDrawable(res, null);
        }

        @Override
        public Drawable newDrawable(Resources res, Theme theme) {
            return new MaterialProgressDrawable(this, theme);
        }

        @Override
        public int getChangingConfigurations() {
            return 0;
        }
    }

    private static class Ring {
        private final RectF mTempBounds = new RectF();
        private final Paint mPaint = new Paint();

        private final Callback mCallback;

        private float mStartTrim = 0.0f;
        private float mEndTrim = 0.0f;
        private float mRotation = 0.0f;
        private float mStrokeWidth = 5.0f;
        private float mStrokeInset = 2.5f;

        private int mAlpha = 0xFF;
        private int mColor = Color.BLACK;

        public Ring(Callback callback) {
            mCallback = callback;

            mPaint.setStrokeCap(Cap.ROUND);
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Style.STROKE);
        }

        public void draw(Canvas c, Rect bounds) {
            final RectF arcBounds = mTempBounds;
            arcBounds.set(bounds);
            arcBounds.inset(mStrokeInset, mStrokeInset);

            final float startAngle = (mStartTrim + mRotation) * 360;
            final float endAngle = (mEndTrim + mRotation) * 360;
            float sweepAngle = endAngle - startAngle;

            // Ensure the sweep angle isn't too small to draw.
            final float diameter = Math.min(arcBounds.width(), arcBounds.height());
            final float minAngle = (float) (360.0 / (diameter * Math.PI));
            if (sweepAngle < minAngle && sweepAngle > -minAngle) {
                sweepAngle = Math.signum(sweepAngle) * minAngle;
            }

            c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
        }

        public void setColorFilter(ColorFilter filter) {
            mPaint.setColorFilter(filter);
            invalidateSelf();
        }

        public ColorFilter getColorFilter() {
            return mPaint.getColorFilter();
        }

        public void setAlpha(int alpha) {
            mAlpha = alpha;
            mPaint.setColor(mColor & 0xFFFFFF | alpha << 24);
            invalidateSelf();
        }

        public int getAlpha() {
            return mAlpha;
        }

        public void setColor(int color) {
            mColor = color;
            mPaint.setColor(color & 0xFFFFFF | mAlpha << 24);
            invalidateSelf();
        }

        public int getColor() {
            return mColor;
        }

        public void setStrokeWidth(float strokeWidth) {
            mStrokeWidth = strokeWidth;
            mPaint.setStrokeWidth(strokeWidth);
            invalidateSelf();
        }

        @SuppressWarnings("unused")
        public float getStrokeWidth() {
            return mStrokeWidth;
        }

        @SuppressWarnings("unused")
        public void setStartTrim(float startTrim) {
            mStartTrim = startTrim;
            invalidateSelf();
        }

        @SuppressWarnings("unused")
        public float getStartTrim() {
            return mStartTrim;
        }

        @SuppressWarnings("unused")
        public void setEndTrim(float endTrim) {
            mEndTrim = endTrim;
            invalidateSelf();
        }

        @SuppressWarnings("unused")
        public float getEndTrim() {
            return mEndTrim;
        }

        @SuppressWarnings("unused")
        public void setRotation(float rotation) {
            mRotation = rotation;
            invalidateSelf();
        }

        @SuppressWarnings("unused")
        public float getRotation() {
            return mRotation;
        }

        public void setInsets(float insets) {
            mStrokeInset = insets;
        }

        @SuppressWarnings("unused")
        public float getInsets() {
            return mStrokeInset;
        }

        private void invalidateSelf() {
            mCallback.invalidateDrawable(null);
        }
    }

    /**
     * Squishes the interpolation curve into the second half of the animation.
     */
    private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
        @Override
        public float getInterpolation(float input) {
            return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
        }
    }

    /**
     * Squishes the interpolation curve into the first half of the animation.
     */
    private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
        @Override
        public float getInterpolation(float input) {
            return super.getInterpolation(Math.min(1, input * 2.0f));
        }
    }
}