OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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.compositor.bottombar.contextualsearch; |
| 6 |
| 7 import static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Ani
matableAnimation.createAnimation; |
| 8 |
| 9 import android.content.Context; |
| 10 import android.view.animation.Interpolator; |
| 11 |
| 12 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanel.PanelState; |
| 13 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanel.StateChangeReason; |
| 14 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation; |
| 15 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable
; |
| 16 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animation; |
| 17 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; |
| 18 import org.chromium.chrome.browser.util.MathUtils; |
| 19 |
| 20 /** |
| 21 * Base abstract class for animating the Contextual Search Panel. |
| 22 */ |
| 23 abstract class ContextualSearchPanelAnimation extends ContextualSearchPanelBase |
| 24 implements Animatable<ContextualSearchPanelAnimation.Property> { |
| 25 |
| 26 /** |
| 27 * Animation properties. |
| 28 */ |
| 29 protected enum Property { |
| 30 PANEL_HEIGHT, |
| 31 FIRST_RUN_PANEL_HEIGHT, |
| 32 } |
| 33 |
| 34 /** |
| 35 * The base duration of animations in milliseconds. This value is based on |
| 36 * the Kennedy specification for slow animations. |
| 37 */ |
| 38 private static final long BASE_ANIMATION_DURATION_MS = 218; |
| 39 |
| 40 /** |
| 41 * The maximum animation duration in milliseconds. |
| 42 */ |
| 43 private static final long MAXIMUM_ANIMATION_DURATION_MS = 350; |
| 44 |
| 45 /** |
| 46 * The minimum animation duration in milliseconds. |
| 47 */ |
| 48 private static final long MINIMUM_ANIMATION_DURATION_MS = Math.round(7 * 100
0 / 60); |
| 49 |
| 50 /** |
| 51 * Average animation velocity in dps per second. |
| 52 */ |
| 53 private static final float INITIAL_ANIMATION_VELOCITY_DP_PER_SECOND = 1750f; |
| 54 |
| 55 /** |
| 56 * The PanelState to which the Panel is being animated. |
| 57 */ |
| 58 private PanelState mAnimatingState; |
| 59 |
| 60 /** |
| 61 * The StateChangeReason for which the Panel is being animated. |
| 62 */ |
| 63 private StateChangeReason mAnimatingStateReason; |
| 64 |
| 65 /** |
| 66 * The animation set. |
| 67 */ |
| 68 private ChromeAnimation<Animatable<?>> mLayoutAnimations; |
| 69 |
| 70 /** |
| 71 * The {@link LayoutUpdateHost} used to request a new frame to be updated an
d rendered. |
| 72 */ |
| 73 private final LayoutUpdateHost mUpdateHost; |
| 74 |
| 75 // =========================================================================
=================== |
| 76 // Constructor |
| 77 // =========================================================================
=================== |
| 78 |
| 79 /** |
| 80 * @param context The current Android {@link Context}. |
| 81 * @param updateHost The {@link LayoutUpdateHost} used to request updates in
the Layout. |
| 82 */ |
| 83 public ContextualSearchPanelAnimation(Context context, LayoutUpdateHost upda
teHost) { |
| 84 super(context); |
| 85 mUpdateHost = updateHost; |
| 86 } |
| 87 |
| 88 // =========================================================================
=================== |
| 89 // Animation API |
| 90 // =========================================================================
=================== |
| 91 |
| 92 /** |
| 93 * Animates the Contextual Search Panel to its maximized state. |
| 94 * |
| 95 * @param reason The reason for the change of panel state. |
| 96 */ |
| 97 protected void maximizePanel(StateChangeReason reason) { |
| 98 animatePanelToState(PanelState.MAXIMIZED, reason); |
| 99 } |
| 100 |
| 101 /** |
| 102 * Animates the Contextual Search Panel to its intermediary state. |
| 103 * |
| 104 * @param reason The reason for the change of panel state. |
| 105 */ |
| 106 protected void expandPanel(StateChangeReason reason) { |
| 107 animatePanelToState(getIntermediaryState(), reason); |
| 108 } |
| 109 |
| 110 /** |
| 111 * Animates the Contextual Search Panel to its peeked state. |
| 112 * |
| 113 * @param reason The reason for the change of panel state. |
| 114 */ |
| 115 protected void peekPanel(StateChangeReason reason) { |
| 116 // Indicate to the Compositor that for now on the Panel should be |
| 117 // rendered, until it's closed. |
| 118 startShowing(); |
| 119 |
| 120 // TODO(pedrosimonetti): Implement custom animation with the following v
alues. |
| 121 // int SEARCH_BAR_ANIMATION_DURATION_MS = 218; |
| 122 // float SEARCH_BAR_SLIDE_OFFSET_DP = 40; |
| 123 // float mSearchBarHeightDp; |
| 124 // setTranslationY(mIsShowingFirstRunFlow |
| 125 // ? mSearchBarHeightDp : SEARCH_BAR_SLIDE_OFFSET_DP); |
| 126 // setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE); |
| 127 animatePanelToState(PanelState.PEEKED, reason); |
| 128 } |
| 129 |
| 130 /** |
| 131 * Animates the Contextual Search Panel to its closed state. |
| 132 * |
| 133 * @param reason The reason for the change of panel state. |
| 134 */ |
| 135 protected void closePanel(StateChangeReason reason, boolean animate) { |
| 136 if (animate) { |
| 137 animatePanelToState(PanelState.CLOSED, reason); |
| 138 } else { |
| 139 resizePanelToState(PanelState.CLOSED, reason); |
| 140 } |
| 141 } |
| 142 |
| 143 /** |
| 144 * Animates the Contextual Search Panel to a given |state| with a default du
ration. |
| 145 * |
| 146 * @param state The state to animate to. |
| 147 * @param reason The reason for the change of panel state. |
| 148 */ |
| 149 private void animatePanelToState(PanelState state, StateChangeReason reason)
{ |
| 150 animatePanelToState(state, reason, BASE_ANIMATION_DURATION_MS); |
| 151 } |
| 152 |
| 153 /** |
| 154 * Animates the Contextual Search Panel to a given |state| with a custom |du
ration|. |
| 155 * |
| 156 * @param state The state to animate to. |
| 157 * @param reason The reason for the change of panel state. |
| 158 * @param duration The animation duration in milliseconds. |
| 159 */ |
| 160 protected void animatePanelToState(PanelState state, StateChangeReason reaso
n, long duration) { |
| 161 mAnimatingState = state; |
| 162 mAnimatingStateReason = reason; |
| 163 |
| 164 final float height = getPanelHeightFromState(state); |
| 165 animatePanelTo(height, duration); |
| 166 } |
| 167 |
| 168 /** |
| 169 * Resizes the Contextual Search Panel to a given |state|. |
| 170 * |
| 171 * @param state The state to resize to. |
| 172 * @param reason The reason for the change of panel state. |
| 173 */ |
| 174 private void resizePanelToState(PanelState state, StateChangeReason reason)
{ |
| 175 final float height = getPanelHeightFromState(state); |
| 176 setPanelHeight(height); |
| 177 setPanelState(state, reason); |
| 178 requestUpdate(); |
| 179 } |
| 180 |
| 181 // =========================================================================
=================== |
| 182 // Animation Helpers |
| 183 // =========================================================================
=================== |
| 184 |
| 185 /** |
| 186 * Animates the Contextual Search panel after first-run success. |
| 187 */ |
| 188 protected void animateAfterFirstRunSuccess() { |
| 189 final PanelState desiredState = PanelState.EXPANDED; |
| 190 mAnimatingState = desiredState; |
| 191 mAnimatingStateReason = StateChangeReason.OPTIN; |
| 192 |
| 193 final float desiredHeight = getPanelHeightFromState(desiredState); |
| 194 animateProperty(Property.FIRST_RUN_PANEL_HEIGHT, getHeight(), desiredHei
ght, |
| 195 BASE_ANIMATION_DURATION_MS); |
| 196 } |
| 197 |
| 198 /** |
| 199 * Animates the Panel to its nearest state. |
| 200 */ |
| 201 protected void animateToNearestState() { |
| 202 // Calculate the nearest state from the current position, and then calcu
late the duration |
| 203 // of the animation that will start with a desired initial velocity and
move the desired |
| 204 // amount of dps (displacement). |
| 205 final PanelState nearestState = findNearestPanelStateFromHeight(getHeigh
t()); |
| 206 final float displacement = getPanelHeightFromState(nearestState) - getHe
ight(); |
| 207 final long duration = calculateAnimationDuration( |
| 208 INITIAL_ANIMATION_VELOCITY_DP_PER_SECOND, displacement); |
| 209 |
| 210 animatePanelToState(nearestState, StateChangeReason.SWIPE, duration); |
| 211 } |
| 212 |
| 213 /** |
| 214 * Animates the Panel to its projected state, given a particular vertical |v
elocity|. |
| 215 * |
| 216 * @param velocity The velocity of the gesture in dps per second. |
| 217 */ |
| 218 protected void animateToProjectedState(float velocity) { |
| 219 final float kickY = calculateAnimationDisplacement(velocity, BASE_ANIMAT
ION_DURATION_MS); |
| 220 final float projectedHeight = getHeight() - kickY; |
| 221 |
| 222 // Calculate the projected state the Panel will be at the end of the fli
ng movement and the |
| 223 // duration of the animation given the current velocity and the projecte
d displacement. |
| 224 final PanelState projectedState = findNearestPanelStateFromHeight(projec
tedHeight); |
| 225 final float displacement = getPanelHeightFromState(projectedState) - get
Height(); |
| 226 final long duration = calculateAnimationDuration(velocity, displacement)
; |
| 227 |
| 228 animatePanelToState(projectedState, StateChangeReason.FLING, duration); |
| 229 } |
| 230 |
| 231 /** |
| 232 * Calculates the animation displacement given the |initialVelocity| and a |
| 233 * desired |duration|. |
| 234 * |
| 235 * @param initialVelocity The initial velocity of the animation in dps per s
econd. |
| 236 * @param duration The desired duration of the animation in milliseconds. |
| 237 * @return The animation displacement in dps. |
| 238 */ |
| 239 protected float calculateAnimationDisplacement(float initialVelocity, float
duration) { |
| 240 // NOTE(pedrosimonetti): This formula assumes the deceleration curve is |
| 241 // quadratic (t^2), |
| 242 // hence the displacement formula should be: |
| 243 // displacement = initialVelocity * duration / 2 |
| 244 // |
| 245 // We are also converting the duration from milliseconds to seconds, |
| 246 // which explains why |
| 247 // we are dividing by 2000 (2 * 1000) instead of 2. |
| 248 return initialVelocity * duration / 2000; |
| 249 } |
| 250 |
| 251 /** |
| 252 * Calculates the animation duration given the |initialVelocity| and a |
| 253 * desired |displacement|. |
| 254 * |
| 255 * @param initialVelocity The initial velocity of the animation in dps per s
econd. |
| 256 * @param displacement The displacement of the animation in dps. |
| 257 * @return The animation duration in milliseconds. |
| 258 */ |
| 259 private long calculateAnimationDuration(float initialVelocity, float displac
ement) { |
| 260 // NOTE(pedrosimonetti): This formula assumes the deceleration curve is |
| 261 // quadratic (t^2), |
| 262 // hence the duration formula should be: |
| 263 // duration = 2 * displacement / initialVelocity |
| 264 // |
| 265 // We are also converting the duration from seconds to milliseconds, |
| 266 // which explains why |
| 267 // we are multiplying by 2000 (2 * 1000) instead of 2. |
| 268 return MathUtils.clamp(Math.round(Math.abs(2000 * displacement / initial
Velocity)), |
| 269 MINIMUM_ANIMATION_DURATION_MS, MAXIMUM_ANIMATION_DURATION_MS); |
| 270 } |
| 271 |
| 272 // =========================================================================
=================== |
| 273 // Layout Integration |
| 274 // =========================================================================
=================== |
| 275 |
| 276 /** |
| 277 * Requests a new frame to be updated and rendered. |
| 278 */ |
| 279 protected void requestUpdate() { |
| 280 // NOTE(pedrosimonetti): mUpdateHost will be null in the ContextualSearc
hEventFilterTest, |
| 281 // so we always need to check if it's null before calling requestUpdate. |
| 282 if (mUpdateHost != null) { |
| 283 mUpdateHost.requestUpdate(); |
| 284 } |
| 285 } |
| 286 |
| 287 // =========================================================================
=================== |
| 288 // Animation Framework |
| 289 // =========================================================================
=================== |
| 290 |
| 291 /** |
| 292 * Animates the Contextual Search Panel to a given |height| with a custom |d
uration|. |
| 293 * |
| 294 * @param height The height to animate to. |
| 295 * @param duration The animation duration in milliseconds. |
| 296 */ |
| 297 private void animatePanelTo(float height, long duration) { |
| 298 animateProperty(Property.PANEL_HEIGHT, getHeight(), height, duration); |
| 299 } |
| 300 |
| 301 /** |
| 302 * Animates the Contextual Search Panel. |
| 303 * |
| 304 * @param property The property which will be animated. |
| 305 * @param start The initial value. |
| 306 * @param end The final value. |
| 307 * @param duration The animation duration in milliseconds. |
| 308 */ |
| 309 protected void animateProperty(Property property, float start, float end, lo
ng duration) { |
| 310 if (duration > 0) { |
| 311 if (animationIsRunning()) { |
| 312 cancelAnimation(this, property); |
| 313 } |
| 314 addToAnimation(this, property, start, end, duration, 0); |
| 315 } |
| 316 } |
| 317 |
| 318 /** |
| 319 * Sets a property for an animation. |
| 320 * |
| 321 * @param prop The property to update. |
| 322 * @param value New value of the property. |
| 323 */ |
| 324 @Override |
| 325 public void setProperty(Property prop, float value) { |
| 326 if (prop == Property.PANEL_HEIGHT) { |
| 327 setPanelHeight(value); |
| 328 } else if (prop == Property.FIRST_RUN_PANEL_HEIGHT) { |
| 329 setPanelHeightForPromoOptInAnimation(value); |
| 330 } |
| 331 } |
| 332 |
| 333 /** |
| 334 * Steps the animation forward and updates all the animated values. |
| 335 * @param time The current time of the app in ms. |
| 336 * @param jumpToEnd Whether to finish the animation. |
| 337 * @return Whether the animation was finished. |
| 338 */ |
| 339 public boolean onUpdateAnimation(long time, boolean jumpToEnd) { |
| 340 boolean finished = true; |
| 341 if (mLayoutAnimations != null) { |
| 342 if (jumpToEnd) { |
| 343 finished = mLayoutAnimations.finished(); |
| 344 mLayoutAnimations.updateAndFinish(); |
| 345 } else { |
| 346 finished = mLayoutAnimations.update(time); |
| 347 } |
| 348 |
| 349 if (finished || jumpToEnd) { |
| 350 mLayoutAnimations = null; |
| 351 onAnimationFinished(); |
| 352 } |
| 353 requestUpdate(); |
| 354 } |
| 355 return finished; |
| 356 } |
| 357 |
| 358 /** |
| 359 * Called when layout-specific actions are needed after the animation finish
es. |
| 360 */ |
| 361 protected void onAnimationStarted() { |
| 362 } |
| 363 |
| 364 /** |
| 365 * Called when layout-specific actions are needed after the animation finish
es. |
| 366 */ |
| 367 protected void onAnimationFinished() { |
| 368 // If animating to a particular PanelState, and after completing |
| 369 // resizing the Panel to its desired state, then the Panel's state |
| 370 // should be updated. This method also is called when an animation |
| 371 // is cancelled (which can happen by a subsequent gesture while |
| 372 // an animation is happening). That's why the actual height should |
| 373 // be checked. |
| 374 if (mAnimatingState != PanelState.UNDEFINED |
| 375 && getHeight() == getPanelHeightFromState(mAnimatingState)) { |
| 376 setPanelState(mAnimatingState, mAnimatingStateReason); |
| 377 } |
| 378 |
| 379 mAnimatingState = PanelState.UNDEFINED; |
| 380 mAnimatingStateReason = StateChangeReason.UNKNOWN; |
| 381 } |
| 382 |
| 383 /** |
| 384 * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAn
imation.Animatable} |
| 385 * and adds it to the animation. |
| 386 * Automatically sets the start value at the beginning of the animation. |
| 387 */ |
| 388 protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T pr
op, float start, |
| 389 float end, long duration, long startTime) { |
| 390 addToAnimation(object, prop, start, end, duration, startTime, false); |
| 391 } |
| 392 |
| 393 /** |
| 394 * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAn
imation.Animatable} |
| 395 * and adds it to the animation. Uses a deceleration interpolator by default
. |
| 396 */ |
| 397 protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T pr
op, float start, |
| 398 float end, long duration, long startTime, boolean setStartValueAfter
Delay) { |
| 399 addToAnimation(object, prop, start, end, duration, startTime, setStartVa
lueAfterDelay, |
| 400 ChromeAnimation.getDecelerateInterpolator()); |
| 401 } |
| 402 |
| 403 /** |
| 404 * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAn
imation.Animatable} |
| 405 * and adds it to the animation. |
| 406 * |
| 407 * @param <T> The Enum type of the Property being used |
| 408 * @param object The object being animated |
| 409 * @param prop The property being animated |
| 410 * @param start The starting value of the animation |
| 411 * @param end The ending value of the animation |
| 412 * @param duration The duration of the animation in ms |
| 413 * @param startTime The start time in ms |
| 414 * @param setStartValueAfterDelay See {@link Animation#setStartValueAfterSta
rtDelay(boolean)} |
| 415 * @param interpolator The interpolator to use for the animation |
| 416 */ |
| 417 protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T pr
op, float start, |
| 418 float end, long duration, long startTime, boolean setStartValueAfter
Delay, |
| 419 Interpolator interpolator) { |
| 420 ChromeAnimation.Animation<Animatable<?>> component = createAnimation(obj
ect, prop, start, |
| 421 end, duration, startTime, setStartValueAfterDelay, interpolator)
; |
| 422 addToAnimation(component); |
| 423 } |
| 424 |
| 425 /** |
| 426 * Appends an Animation to the current animation set and starts it immediate
ly. If the set is |
| 427 * already finished or doesn't exist, the animation set is also started. |
| 428 */ |
| 429 protected void addToAnimation(ChromeAnimation.Animation<Animatable<?>> compo
nent) { |
| 430 if (mLayoutAnimations == null || mLayoutAnimations.finished()) { |
| 431 onAnimationStarted(); |
| 432 mLayoutAnimations = new ChromeAnimation<Animatable<?>>(); |
| 433 mLayoutAnimations.start(); |
| 434 } |
| 435 component.start(); |
| 436 mLayoutAnimations.add(component); |
| 437 requestUpdate(); |
| 438 } |
| 439 |
| 440 /** |
| 441 * @return whether or not the animation is currently being run. |
| 442 */ |
| 443 protected boolean animationIsRunning() { |
| 444 return mLayoutAnimations != null && !mLayoutAnimations.finished(); |
| 445 } |
| 446 |
| 447 /** |
| 448 * Cancels any animation for the given object and property. |
| 449 * @param object The object being animated. |
| 450 * @param prop The property to search for. |
| 451 */ |
| 452 protected <T extends Enum<?>> void cancelAnimation(Animatable<T> object, T p
rop) { |
| 453 if (mLayoutAnimations != null) { |
| 454 mLayoutAnimations.cancel(object, prop); |
| 455 } |
| 456 } |
| 457 } |
OLD | NEW |