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

Commit 4c33b9f7 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "Remove material progress drawable"

parents b9731a10 d7f4a3cd
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));
        }
    }
}