Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/appmenu/PulseDrawable.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/PulseDrawable.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/PulseDrawable.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b9cac118bbff001326e4c1096c16e37052d94c48 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/PulseDrawable.java |
| @@ -0,0 +1,263 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.chrome.browser.appmenu; |
| + |
| +import android.content.res.Resources; |
| +import android.graphics.Canvas; |
| +import android.graphics.ColorFilter; |
| +import android.graphics.Paint; |
| +import android.graphics.PixelFormat; |
| +import android.graphics.Rect; |
| +import android.graphics.drawable.Animatable; |
| +import android.graphics.drawable.Drawable; |
| +import android.os.SystemClock; |
| +import android.support.annotation.ColorInt; |
| +import android.support.annotation.ColorRes; |
| +import android.view.animation.Interpolator; |
| + |
| +import org.chromium.base.ApiCompatibilityUtils; |
| + |
| +/** |
| + * A custom {@link Drawable} that will animate a pulse using the {@link PulseInterpolator}. Meant |
| + * to be created with a {@link PulseDrawable#Painter} that does the actual drawing work based on |
| + * the pulse interpolation value. |
| + */ |
| +public class PulseDrawable extends Drawable implements Animatable { |
| + private static final long PULSE_DURATION_MS = 2500; |
| + private static final long FRAME_RATE = 60; |
| + |
| + /** |
| + * An interface that does the actual drawing work for this {@link Drawable}. Not meant to be |
| + * stateful, as this could be shared across multiple instances of this drawable if it gets |
| + * copied or mutated. |
| + */ |
| + public interface Painter { |
| + /** |
| + * Called when this drawable updates it's pulse interpolation. Should mutate the drawable |
| + * as necessary. This is responsible for invalidating this {@link Drawable} if something |
| + * needs to be redrawn. |
| + * |
| + * @param drawable The {@link PulseDrawable} that is updated. |
| + * @param interpolation The current progress of whatever is being pulsed. |
| + */ |
| + void modifyDrawable(PulseDrawable drawable, float interpolation); |
| + |
| + /** |
| + * Called when this {@link PulseDrawable} needs to draw. Should perform any draw operation |
| + * for the specific type of pulse. |
| + * @param drawable The calling {@link PulseDrawable}. |
| + * @param paint A {@link Paint} object to use. This will automatically have the |
| + * color set. |
| + * @param canvas The {@link Canvas} to draw to. |
| + * @param interpolation The current progress of whatever is being pulsed. |
| + */ |
| + void draw(PulseDrawable drawable, Paint paint, Canvas canvas, float interpolation); |
| + } |
| + |
| + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| + private final Rect mInset = new Rect(); |
| + private final Rect mOriginalBounds = new Rect(); |
| + private final Rect mInsetBounds = new Rect(); |
| + |
| + protected PulseState mState; |
|
Ted C
2017/04/12 18:05:57
why protected?
David Trainor- moved to gerrit
2017/04/12 18:59:37
Ah I was planning on overriding this class for the
|
| + private boolean mMutated; |
| + private boolean mRunning; |
| + |
| + /** |
| + * Creates a new {@link PulseDrawable} instance. |
| + * @param res A {@link Resources} object used to load the {@link Drawable} parameters. |
| + * @param color A color resource. |
| + * @param interpolator An {@link Interpolator} that defines how the pulse will fade in and out. |
| + * @param painter The {@link Painter} that will be responsible for drawing the pulse. |
| + */ |
| + public PulseDrawable( |
| + Resources res, @ColorRes int color, Interpolator interpolator, Painter painter) { |
| + this(new PulseState(res, color, interpolator, painter)); |
| + } |
| + |
| + private PulseDrawable(PulseState state) { |
| + mState = state; |
| + } |
| + |
| + /** What color to set the pulse to. */ |
| + public void setColor(@ColorInt int color) { |
|
Ted C
2017/04/12 18:05:57
I'd rather this not be configurable right now. I'
David Trainor- moved to gerrit
2017/04/12 18:59:37
I can have it set light/dark if you'd like. We on
|
| + int alpha = getAlpha(); |
| + mState.color = mState.drawColor = color; |
| + setAlpha(alpha); |
| + invalidateSelf(); |
| + } |
| + |
| + /** How much to inset the bounds of this {@link Drawable} by. */ |
| + public void setInset(int left, int top, int right, int bottom) { |
| + mInset.set(left, top, right, bottom); |
| + setBounds(mOriginalBounds); |
|
Ted C
2017/04/12 18:05:57
Should we only call this if !mOriginalBounds.isEmp
David Trainor- moved to gerrit
2017/04/12 18:59:37
Yes good point.
|
| + } |
| + |
| + // Animatable implementation. |
| + @Override |
| + public void start() { |
| + if (mRunning) { |
| + unscheduleSelf(mNextFrame); |
| + scheduleSelf(mNextFrame, SystemClock.uptimeMillis() + 1000 / FRAME_RATE); |
| + } else { |
| + mRunning = true; |
| + if (mState.startTime == 0) mState.startTime = SystemClock.uptimeMillis(); |
| + mNextFrame.run(); |
| + } |
| + } |
| + |
| + @Override |
| + public void stop() { |
| + mRunning = false; |
| + mState.startTime = 0; |
| + unscheduleSelf(mNextFrame); |
| + } |
| + |
| + @Override |
| + public boolean isRunning() { |
| + return mRunning; |
| + } |
| + |
| + // Drawable implementation. |
| + // Overriding only this method because {@link Drawable#setBounds(Rect)} calls into this. |
| + @Override |
| + public void setBounds(int left, int top, int right, int bottom) { |
| + mOriginalBounds.set(left, top, right, bottom); |
| + mInsetBounds.set( |
| + left + mInset.left, top + mInset.top, right - mInset.right, bottom - mInset.bottom); |
| + super.setBounds( |
| + mInsetBounds.left, mInsetBounds.top, mInsetBounds.right, mInsetBounds.bottom); |
| + } |
| + |
| + @Override |
| + public void draw(Canvas canvas) { |
| + mPaint.setColor(mState.drawColor); |
| + mState.painter.draw(this, mPaint, canvas, mState.progress); |
| + } |
| + |
| + @Override |
| + public void setAlpha(int alpha) { |
| + // Encode the alpha into the color. |
| + alpha += alpha >> 7; // make it 0..256 |
| + final int baseAlpha = mState.color >>> 24; |
| + final int useAlpha = baseAlpha * alpha >> 8; |
| + final int useColor = (mState.color << 8 >>> 8) | (useAlpha << 24); |
| + if (mState.drawColor != useColor) { |
| + mState.drawColor = useColor; |
| + invalidateSelf(); |
| + } |
| + } |
| + |
| + @Override |
| + public int getAlpha() { |
| + return mState.drawColor >>> 24; |
| + } |
| + |
| + @Override |
| + public void setColorFilter(ColorFilter colorFilter) { |
| + mPaint.setColorFilter(colorFilter); |
| + } |
| + |
| + @Override |
| + public int getOpacity() { |
| + return PixelFormat.TRANSLUCENT; |
| + } |
| + |
| + @Override |
| + public boolean setVisible(boolean visible, boolean restart) { |
| + final boolean changed = super.setVisible(visible, restart); |
| + if (visible) { |
| + if (changed || restart) start(); |
| + } else { |
| + stop(); |
| + } |
| + return changed; |
| + } |
| + |
| + @Override |
| + public Drawable mutate() { |
| + if (!mMutated && super.mutate() == this) { |
| + mState = new PulseState(mState); |
| + mMutated = true; |
| + } |
| + return this; |
| + } |
| + |
| + @Override |
| + public ConstantState getConstantState() { |
| + return mState; |
| + } |
| + |
| + private void stepPulse() { |
| + long curTime = SystemClock.uptimeMillis(); |
| + long msIntoAnim = (curTime - mState.startTime) % PULSE_DURATION_MS; |
| + float progress = ((float) msIntoAnim) / ((float) PULSE_DURATION_MS); |
| + mState.progress = mState.interpolator.getInterpolation(progress); |
| + mState.painter.modifyDrawable(PulseDrawable.this, mState.progress); |
| + } |
| + |
| + /** |
| + * The {@link ConstantState} subclass for this {@link PulseDrawable}. |
| + */ |
| + private static final class PulseState extends ConstantState { |
| + // Current Paint State. |
| + /** The current color, including alpha, to draw. */ |
| + public int drawColor; |
| + |
| + /** The original color to draw (may not include any alpha updates. */ |
|
Ted C
2017/04/12 18:05:57
may not, or does not? also missing trailing )
I
David Trainor- moved to gerrit
2017/04/12 18:59:37
It depends on whether or not the color passed in h
|
| + public int color; |
| + |
| + // Current Animation State |
| + /** The time from {@link SystemClock#updateMillis()} that this animation started at. */ |
| + public long startTime; |
| + |
| + /** The current progress from 0 to 1 of the pulse. */ |
| + public float progress; |
| + |
| + /** The {@link Interpolator} that makes the pulse and generates the progress. */ |
| + public Interpolator interpolator; |
| + |
| + /** |
| + * The {@link Painter} object that is responsible for modifying and drawing this |
| + * {@link PulseDrawable}. |
| + */ |
| + public Painter painter; |
| + |
| + PulseState(Resources res, @ColorRes int color, Interpolator interpolator, Painter painter) { |
| + this.color = this.drawColor = ApiCompatibilityUtils.getColor(res, color); |
| + |
| + this.interpolator = new PulseInterpolator(interpolator); |
| + this.painter = painter; |
| + } |
| + |
| + PulseState(PulseState other) { |
| + drawColor = other.drawColor; |
| + color = other.color; |
| + |
| + startTime = other.startTime; |
| + |
| + interpolator = other.interpolator; |
| + painter = other.painter; |
| + } |
| + |
| + @Override |
| + public Drawable newDrawable() { |
| + return new PulseDrawable(this); |
| + } |
| + |
| + @Override |
| + public int getChangingConfigurations() { |
| + return 0; |
| + } |
| + } |
| + |
| + private final Runnable mNextFrame = new Runnable() { |
|
Ted C
2017/04/12 18:05:57
I'd put this above the constructor as it is a loca
David Trainor- moved to gerrit
2017/04/12 18:59:37
Done.
|
| + @Override |
| + public void run() { |
| + stepPulse(); |
| + if (mRunning) scheduleSelf(mNextFrame, SystemClock.uptimeMillis() + 1000 / FRAME_RATE); |
| + } |
| + }; |
| +} |