OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.widget; |
| 6 |
| 7 import android.content.res.Resources; |
| 8 import android.graphics.Canvas; |
| 9 import android.graphics.ColorFilter; |
| 10 import android.graphics.Paint; |
| 11 import android.graphics.PixelFormat; |
| 12 import android.graphics.Rect; |
| 13 import android.graphics.drawable.Animatable; |
| 14 import android.graphics.drawable.Drawable; |
| 15 import android.os.SystemClock; |
| 16 import android.support.annotation.ColorInt; |
| 17 import android.support.v4.view.animation.FastOutSlowInInterpolator; |
| 18 import android.support.v4.view.animation.PathInterpolatorCompat; |
| 19 import android.view.animation.Interpolator; |
| 20 |
| 21 import org.chromium.base.ApiCompatibilityUtils; |
| 22 import org.chromium.base.ContextUtils; |
| 23 import org.chromium.chrome.R; |
| 24 import org.chromium.chrome.browser.util.MathUtils; |
| 25 |
| 26 /** |
| 27 * A custom {@link Drawable} that will animate a pulse using the {@link PulseInt
erpolator}. Meant |
| 28 * to be created with a {@link PulseDrawable#Painter} that does the actual drawi
ng work based on |
| 29 * the pulse interpolation value. |
| 30 */ |
| 31 public class PulseDrawable extends Drawable implements Animatable { |
| 32 private static final long PULSE_DURATION_MS = 2500; |
| 33 private static final long FRAME_RATE = 60; |
| 34 |
| 35 /** |
| 36 * An interface that does the actual drawing work for this {@link Drawable}.
Not meant to be |
| 37 * stateful, as this could be shared across multiple instances of this drawa
ble if it gets |
| 38 * copied or mutated. |
| 39 */ |
| 40 private interface Painter { |
| 41 /** |
| 42 * Called when this drawable updates it's pulse interpolation. Should m
utate the drawable |
| 43 * as necessary. This is responsible for invalidating this {@link Drawa
ble} if something |
| 44 * needs to be redrawn. |
| 45 * |
| 46 * @param drawable The {@link PulseDrawable} that is updated. |
| 47 * @param interpolation The current progress of whatever is being pulsed
. |
| 48 */ |
| 49 void modifyDrawable(PulseDrawable drawable, float interpolation); |
| 50 |
| 51 /** |
| 52 * Called when this {@link PulseDrawable} needs to draw. Should perform
any draw operation |
| 53 * for the specific type of pulse. |
| 54 * @param drawable The calling {@link PulseDrawable}. |
| 55 * @param paint A {@link Paint} object to use. This will automa
tically have the |
| 56 * color set. |
| 57 * @param canvas The {@link Canvas} to draw to. |
| 58 * @param interpolation The current progress of whatever is being pulsed
. |
| 59 */ |
| 60 void draw(PulseDrawable drawable, Paint paint, Canvas canvas, float inte
rpolation); |
| 61 } |
| 62 |
| 63 /** |
| 64 * Creates a {@link PulseDrawable} that will fill the bounds with a pulsing
color. |
| 65 * @return A new {@link PulseDrawable} instance. |
| 66 */ |
| 67 public static PulseDrawable createHighlight() { |
| 68 PulseDrawable.Painter painter = new PulseDrawable.Painter() { |
| 69 @Override |
| 70 public void modifyDrawable(PulseDrawable drawable, float interpolati
on) { |
| 71 drawable.setAlpha((int) MathUtils.interpolate(12, 75, interpolat
ion)); |
| 72 } |
| 73 |
| 74 @Override |
| 75 public void draw( |
| 76 PulseDrawable drawable, Paint paint, Canvas canvas, float in
terpolation) { |
| 77 canvas.drawRect(drawable.getBounds(), paint); |
| 78 } |
| 79 }; |
| 80 |
| 81 return new PulseDrawable(new FastOutSlowInInterpolator(), painter); |
| 82 } |
| 83 |
| 84 /** |
| 85 * Creates a {@link PulseDrawable} that will draw a pulsing circle inside th
e bounds. |
| 86 * @return A new {@link PulseDrawable} instance. |
| 87 */ |
| 88 public static PulseDrawable createCircle() { |
| 89 PulseDrawable.Painter painter = new PulseDrawable.Painter() { |
| 90 @Override |
| 91 public void modifyDrawable(PulseDrawable drawable, float interpolati
on) { |
| 92 drawable.invalidateSelf(); |
| 93 } |
| 94 |
| 95 @Override |
| 96 public void draw( |
| 97 PulseDrawable drawable, Paint paint, Canvas canvas, float in
terpolation) { |
| 98 Rect bounds = drawable.getBounds(); |
| 99 float scale = MathUtils.interpolate(0.8f, 1.f, interpolation); |
| 100 float radius = Math.min(bounds.width(), bounds.height()) * scale
/ 2.f; |
| 101 canvas.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(),
radius, paint); |
| 102 } |
| 103 }; |
| 104 |
| 105 PulseDrawable drawable = |
| 106 new PulseDrawable(PathInterpolatorCompat.create(.8f, 0.f, .6f, 1
.f), painter); |
| 107 drawable.setAlpha(76); |
| 108 return drawable; |
| 109 } |
| 110 |
| 111 private final Runnable mNextFrame = new Runnable() { |
| 112 @Override |
| 113 public void run() { |
| 114 stepPulse(); |
| 115 if (mRunning) scheduleSelf(mNextFrame, SystemClock.uptimeMillis() +
1000 / FRAME_RATE); |
| 116 } |
| 117 }; |
| 118 |
| 119 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| 120 private final Rect mInset = new Rect(); |
| 121 private final Rect mOriginalBounds = new Rect(); |
| 122 private final Rect mInsetBounds = new Rect(); |
| 123 |
| 124 private PulseState mState; |
| 125 private boolean mMutated; |
| 126 private boolean mRunning; |
| 127 |
| 128 /** |
| 129 * Creates a new {@link PulseDrawable} instance. |
| 130 * @param interpolator An {@link Interpolator} that defines how the pulse wi
ll fade in and out. |
| 131 * @param painter The {@link Painter} that will be responsible for draw
ing the pulse. |
| 132 */ |
| 133 private PulseDrawable(Interpolator interpolator, Painter painter) { |
| 134 this(new PulseState(interpolator, painter)); |
| 135 setUseLightPulseColor(false); |
| 136 } |
| 137 |
| 138 private PulseDrawable(PulseState state) { |
| 139 mState = state; |
| 140 } |
| 141 |
| 142 /** Whether or not to use a light or dark color for the pulse. */ |
| 143 public void setUseLightPulseColor(boolean useLightPulseColor) { |
| 144 Resources resources = ContextUtils.getApplicationContext().getResources(
); |
| 145 |
| 146 @ColorInt |
| 147 int color = ApiCompatibilityUtils.getColor( |
| 148 resources, useLightPulseColor ? R.color.google_grey_100 : R.colo
r.google_blue_500); |
| 149 if (mState.color == color) return; |
| 150 |
| 151 int alpha = getAlpha(); |
| 152 mState.color = mState.drawColor = color; |
| 153 setAlpha(alpha); |
| 154 invalidateSelf(); |
| 155 } |
| 156 |
| 157 /** How much to inset the bounds of this {@link Drawable} by. */ |
| 158 public void setInset(int left, int top, int right, int bottom) { |
| 159 mInset.set(left, top, right, bottom); |
| 160 if (!mOriginalBounds.isEmpty()) setBounds(mOriginalBounds); |
| 161 } |
| 162 |
| 163 // Animatable implementation. |
| 164 @Override |
| 165 public void start() { |
| 166 if (mRunning) { |
| 167 unscheduleSelf(mNextFrame); |
| 168 scheduleSelf(mNextFrame, SystemClock.uptimeMillis() + 1000 / FRAME_R
ATE); |
| 169 } else { |
| 170 mRunning = true; |
| 171 if (mState.startTime == 0) mState.startTime = SystemClock.uptimeMill
is(); |
| 172 mNextFrame.run(); |
| 173 } |
| 174 } |
| 175 |
| 176 @Override |
| 177 public void stop() { |
| 178 mRunning = false; |
| 179 mState.startTime = 0; |
| 180 unscheduleSelf(mNextFrame); |
| 181 } |
| 182 |
| 183 @Override |
| 184 public boolean isRunning() { |
| 185 return mRunning; |
| 186 } |
| 187 |
| 188 // Drawable implementation. |
| 189 // Overriding only this method because {@link Drawable#setBounds(Rect)} call
s into this. |
| 190 @Override |
| 191 public void setBounds(int left, int top, int right, int bottom) { |
| 192 mOriginalBounds.set(left, top, right, bottom); |
| 193 mInsetBounds.set( |
| 194 left + mInset.left, top + mInset.top, right - mInset.right, bott
om - mInset.bottom); |
| 195 super.setBounds( |
| 196 mInsetBounds.left, mInsetBounds.top, mInsetBounds.right, mInsetB
ounds.bottom); |
| 197 } |
| 198 |
| 199 @Override |
| 200 public void draw(Canvas canvas) { |
| 201 mPaint.setColor(mState.drawColor); |
| 202 mState.painter.draw(this, mPaint, canvas, mState.progress); |
| 203 } |
| 204 |
| 205 @Override |
| 206 public void setAlpha(int alpha) { |
| 207 // Encode the alpha into the color. |
| 208 alpha += alpha >> 7; // make it 0..256 |
| 209 final int baseAlpha = mState.color >>> 24; |
| 210 final int useAlpha = baseAlpha * alpha >> 8; |
| 211 final int useColor = (mState.color << 8 >>> 8) | (useAlpha << 24); |
| 212 if (mState.drawColor != useColor) { |
| 213 mState.drawColor = useColor; |
| 214 invalidateSelf(); |
| 215 } |
| 216 } |
| 217 |
| 218 @Override |
| 219 public int getAlpha() { |
| 220 return mState.drawColor >>> 24; |
| 221 } |
| 222 |
| 223 @Override |
| 224 public void setColorFilter(ColorFilter colorFilter) { |
| 225 mPaint.setColorFilter(colorFilter); |
| 226 } |
| 227 |
| 228 @Override |
| 229 public int getOpacity() { |
| 230 return PixelFormat.TRANSLUCENT; |
| 231 } |
| 232 |
| 233 @Override |
| 234 public boolean setVisible(boolean visible, boolean restart) { |
| 235 final boolean changed = super.setVisible(visible, restart); |
| 236 if (visible) { |
| 237 if (changed || restart) start(); |
| 238 } else { |
| 239 stop(); |
| 240 } |
| 241 return changed; |
| 242 } |
| 243 |
| 244 @Override |
| 245 public Drawable mutate() { |
| 246 if (!mMutated && super.mutate() == this) { |
| 247 mState = new PulseState(mState); |
| 248 mMutated = true; |
| 249 } |
| 250 return this; |
| 251 } |
| 252 |
| 253 @Override |
| 254 public ConstantState getConstantState() { |
| 255 return mState; |
| 256 } |
| 257 |
| 258 private void stepPulse() { |
| 259 long curTime = SystemClock.uptimeMillis(); |
| 260 long msIntoAnim = (curTime - mState.startTime) % PULSE_DURATION_MS; |
| 261 float progress = ((float) msIntoAnim) / ((float) PULSE_DURATION_MS); |
| 262 mState.progress = mState.interpolator.getInterpolation(progress); |
| 263 mState.painter.modifyDrawable(PulseDrawable.this, mState.progress); |
| 264 } |
| 265 |
| 266 /** |
| 267 * The {@link ConstantState} subclass for this {@link PulseDrawable}. |
| 268 */ |
| 269 private static final class PulseState extends ConstantState { |
| 270 // Current Paint State. |
| 271 /** The current color, including alpha, to draw. */ |
| 272 public int drawColor; |
| 273 |
| 274 /** The original color to draw (will not include updates from calls to s
etAlpha()). */ |
| 275 public int color; |
| 276 |
| 277 // Current Animation State |
| 278 /** The time from {@link SystemClock#updateMillis()} that this animation
started at. */ |
| 279 public long startTime; |
| 280 |
| 281 /** The current progress from 0 to 1 of the pulse. */ |
| 282 public float progress; |
| 283 |
| 284 /** The {@link Interpolator} that makes the pulse and generates the prog
ress. */ |
| 285 public Interpolator interpolator; |
| 286 |
| 287 /** |
| 288 * The {@link Painter} object that is responsible for modifying and draw
ing this |
| 289 * {@link PulseDrawable}. |
| 290 */ |
| 291 public Painter painter; |
| 292 |
| 293 PulseState(Interpolator interpolator, Painter painter) { |
| 294 this.interpolator = new PulseInterpolator(interpolator); |
| 295 this.painter = painter; |
| 296 } |
| 297 |
| 298 PulseState(PulseState other) { |
| 299 drawColor = other.drawColor; |
| 300 color = other.color; |
| 301 |
| 302 startTime = other.startTime; |
| 303 |
| 304 interpolator = other.interpolator; |
| 305 painter = other.painter; |
| 306 } |
| 307 |
| 308 @Override |
| 309 public Drawable newDrawable() { |
| 310 return new PulseDrawable(this); |
| 311 } |
| 312 |
| 313 @Override |
| 314 public int getChangingConfigurations() { |
| 315 return 0; |
| 316 } |
| 317 } |
| 318 } |
OLD | NEW |