Index: chrome/android/java/src/org/chromium/chrome/browser/widget/PulseDrawable.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/PulseDrawable.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/PulseDrawable.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8b2aeaafe6d867f10cdd9b5dda91ca9da57ee5bc |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/PulseDrawable.java |
@@ -0,0 +1,318 @@ |
+// 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.widget; |
+ |
+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.v4.view.animation.FastOutSlowInInterpolator; |
+import android.support.v4.view.animation.PathInterpolatorCompat; |
+import android.view.animation.Interpolator; |
+ |
+import org.chromium.base.ApiCompatibilityUtils; |
+import org.chromium.base.ContextUtils; |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.util.MathUtils; |
+ |
+/** |
+ * 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. |
+ */ |
+ private 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); |
+ } |
+ |
+ /** |
+ * Creates a {@link PulseDrawable} that will fill the bounds with a pulsing color. |
+ * @return A new {@link PulseDrawable} instance. |
+ */ |
+ public static PulseDrawable createHighlight() { |
+ PulseDrawable.Painter painter = new PulseDrawable.Painter() { |
+ @Override |
+ public void modifyDrawable(PulseDrawable drawable, float interpolation) { |
+ drawable.setAlpha((int) MathUtils.interpolate(12, 75, interpolation)); |
+ } |
+ |
+ @Override |
+ public void draw( |
+ PulseDrawable drawable, Paint paint, Canvas canvas, float interpolation) { |
+ canvas.drawRect(drawable.getBounds(), paint); |
+ } |
+ }; |
+ |
+ return new PulseDrawable(new FastOutSlowInInterpolator(), painter); |
+ } |
+ |
+ /** |
+ * Creates a {@link PulseDrawable} that will draw a pulsing circle inside the bounds. |
+ * @return A new {@link PulseDrawable} instance. |
+ */ |
+ public static PulseDrawable createCircle() { |
+ PulseDrawable.Painter painter = new PulseDrawable.Painter() { |
+ @Override |
+ public void modifyDrawable(PulseDrawable drawable, float interpolation) { |
+ drawable.invalidateSelf(); |
+ } |
+ |
+ @Override |
+ public void draw( |
+ PulseDrawable drawable, Paint paint, Canvas canvas, float interpolation) { |
+ Rect bounds = drawable.getBounds(); |
+ float scale = MathUtils.interpolate(0.8f, 1.f, interpolation); |
+ float radius = Math.min(bounds.width(), bounds.height()) * scale / 2.f; |
+ canvas.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), radius, paint); |
+ } |
+ }; |
+ |
+ PulseDrawable drawable = |
+ new PulseDrawable(PathInterpolatorCompat.create(.8f, 0.f, .6f, 1.f), painter); |
+ drawable.setAlpha(76); |
+ return drawable; |
+ } |
+ |
+ private final Runnable mNextFrame = new Runnable() { |
+ @Override |
+ public void run() { |
+ stepPulse(); |
+ if (mRunning) scheduleSelf(mNextFrame, SystemClock.uptimeMillis() + 1000 / FRAME_RATE); |
+ } |
+ }; |
+ |
+ 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(); |
+ |
+ private PulseState mState; |
+ private boolean mMutated; |
+ private boolean mRunning; |
+ |
+ /** |
+ * Creates a new {@link PulseDrawable} instance. |
+ * @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. |
+ */ |
+ private PulseDrawable(Interpolator interpolator, Painter painter) { |
+ this(new PulseState(interpolator, painter)); |
+ setUseLightPulseColor(false); |
+ } |
+ |
+ private PulseDrawable(PulseState state) { |
+ mState = state; |
+ } |
+ |
+ /** Whether or not to use a light or dark color for the pulse. */ |
+ public void setUseLightPulseColor(boolean useLightPulseColor) { |
+ Resources resources = ContextUtils.getApplicationContext().getResources(); |
+ |
+ @ColorInt |
+ int color = ApiCompatibilityUtils.getColor( |
+ resources, useLightPulseColor ? R.color.google_grey_100 : R.color.google_blue_500); |
+ if (mState.color == color) return; |
+ |
+ 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); |
+ if (!mOriginalBounds.isEmpty()) setBounds(mOriginalBounds); |
+ } |
+ |
+ // 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 (will not include updates from calls to setAlpha()). */ |
+ 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(Interpolator interpolator, Painter painter) { |
+ 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; |
+ } |
+ } |
+} |