diff options
author | Alan Viverette <alanv@google.com> | 2014-06-04 18:44:09 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-06-04 18:44:09 +0000 |
commit | 3b1e08fc9fc8c8eb557a260ae9b8e809864b471a (patch) | |
tree | 5692cfe79285ac1b5dfa944625e68c4ef98041f7 /graphics | |
parent | 3decd141a51849fa7f84cb4e5a28fa047ff9be37 (diff) | |
parent | bb938107b7ef1f6660b9f9cbafacd9d0a427bedc (diff) | |
download | frameworks_base-3b1e08fc9fc8c8eb557a260ae9b8e809864b471a.zip frameworks_base-3b1e08fc9fc8c8eb557a260ae9b8e809864b471a.tar.gz frameworks_base-3b1e08fc9fc8c8eb557a260ae9b8e809864b471a.tar.bz2 |
am 62ea9554: Merge "Temporary drawable for Quantum progress indicator" into lmp-preview-dev
* commit '62ea9554753820df25343e52e0dcab090e5755b5':
Temporary drawable for Quantum progress indicator
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/java/android/graphics/drawable/Drawable.java | 3 | ||||
-rw-r--r-- | graphics/java/android/graphics/drawable/QuantumProgressDrawable.java | 491 |
2 files changed, 494 insertions, 0 deletions
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 76dd1c8..3a32e80 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1023,6 +1023,9 @@ public abstract class Drawable { drawable = new StateListDrawable(); } else if (name.equals("animated-selector")) { drawable = new AnimatedStateListDrawable(); + } else if (name.equals("quantum-progress")) { + // TODO: Replace this with something less ridiculous. + drawable = new QuantumProgressDrawable(); } else if (name.equals("level-list")) { drawable = new LevelListDrawable(); } else if (name.equals("layer-list")) { diff --git a/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java b/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java new file mode 100644 index 0000000..d756eb1 --- /dev/null +++ b/graphics/java/android/graphics/drawable/QuantumProgressDrawable.java @@ -0,0 +1,491 @@ +/* + * 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.Paint.Cap; +import android.graphics.Paint.Style; +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 Quantum theme. + * + * TODO: Replace this class with something less ridiculous. + */ +class QuantumProgressDrawable 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 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 QuantumProgressState mState; + + private boolean mMutated; + + public QuantumProgressDrawable() { + this(new QuantumProgressState(null), null); + } + + private QuantumProgressDrawable(QuantumProgressState state, Theme theme) { + mState = state; + if (theme != null && state.canApplyTheme()) { + applyTheme(theme); + } + + mRing = new Ring(mCallback); + mMutated = false; + + initializeFromState(); + setupAnimators(); + } + + private void initializeFromState() { + final QuantumProgressState 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); + } + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mState = new QuantumProgressState(mState); + mMutated = true; + } + return this; + } + + @Override + protected boolean onStateChange(int[] state) { + boolean changed = super.onStateChange(state); + + final int color = mState.mColor.getColorForState(state, Color.TRANSPARENT); + if (color != mRing.getColor()) { + mRing.setColor(color); + 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.QuantumProgressDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.QuantumProgressDrawable_visible); + updateStateFromTypedArray(a); + a.recycle(); + + initializeFromState(); + } + + @Override + public void applyTheme(Theme t) { + final TypedArray a = t.resolveAttributes(mState.mThemeAttrs, + R.styleable.QuantumProgressDrawable); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + final QuantumProgressState state = mState; + state.mThemeAttrs = a.extractThemeAttrs(); + state.mWidth = a.getDimensionPixelSize( + R.styleable.QuantumProgressDrawable_width, state.mWidth); + state.mHeight = a.getDimensionPixelSize( + R.styleable.QuantumProgressDrawable_height, state.mHeight); + state.mInnerRadius = a.getDimension( + R.styleable.QuantumProgressDrawable_innerRadius, state.mInnerRadius); + state.mStrokeWidth = a.getDimension( + R.styleable.QuantumProgressDrawable_thickness, state.mStrokeWidth); + + if (a.hasValue(R.styleable.QuantumProgressDrawable_color)) { + state.mColor = a.getColorStateList(R.styleable.QuantumProgressDrawable_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) { + mRing.draw(c, getBounds()); + } + + @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 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(1000 * 80 / 60); + 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(1000 * 80 / 60); + 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(1000 * 80 / 60); + rotation.setInterpolator(LINEAR_INTERPOLATOR); + rotation.setRepeatCount(ObjectAnimator.INFINITE); + rotation.setRepeatMode(ObjectAnimator.RESTART); + + mAnimators.add(endTrim); + mAnimators.add(startTrim); + mAnimators.add(rotation); + } + + 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 QuantumProgressState 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); + + public QuantumProgressState(QuantumProgressState orig) { + if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; + mStrokeWidth = orig.mStrokeWidth; + mInnerRadius = orig.mInnerRadius; + mWidth = orig.mWidth; + mHeight = orig.mHeight; + mColor = orig.mColor; + } + } + + @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 QuantumProgressDrawable(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)); + } + } +} |