StackStateAnimator.java revision 8df56452cb696ebdee82df6fb255892eabf3febc
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.systemui.statusbar.stack; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.PropertyValuesHolder; 24import android.animation.ValueAnimator; 25import android.view.View; 26import android.view.animation.AnimationUtils; 27import android.view.animation.Interpolator; 28 29import com.android.systemui.R; 30import com.android.systemui.statusbar.ExpandableView; 31 32import java.util.ArrayList; 33import java.util.HashSet; 34import java.util.Set; 35import java.util.Stack; 36 37/** 38 * An stack state animator which handles animations to new StackScrollStates 39 */ 40public class StackStateAnimator { 41 42 public static final int ANIMATION_DURATION_STANDARD = 360; 43 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; 44 45 private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; 46 private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; 47 private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag; 48 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; 49 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; 50 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; 51 private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; 52 private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; 53 private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag; 54 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; 55 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; 56 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; 57 private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; 58 private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; 59 private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag; 60 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; 61 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag; 62 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; 63 64 private final Interpolator mFastOutSlowInInterpolator; 65 public NotificationStackScrollLayout mHostLayout; 66 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mHandledEvents = 67 new ArrayList<>(); 68 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = 69 new ArrayList<>(); 70 private Set<Animator> mAnimatorSet = new HashSet<Animator>(); 71 private Stack<AnimatorListenerAdapter> mAnimationListenerPool 72 = new Stack<AnimatorListenerAdapter>(); 73 private AnimationFilter mAnimationFilter = new AnimationFilter(); 74 private long mCurrentLength; 75 76 public StackStateAnimator(NotificationStackScrollLayout hostLayout) { 77 mHostLayout = hostLayout; 78 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), 79 android.R.interpolator.fast_out_slow_in); 80 } 81 82 public boolean isRunning() { 83 return !mAnimatorSet.isEmpty(); 84 } 85 86 public void startAnimationForEvents( 87 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, 88 StackScrollState finalState) { 89 90 processAnimationEvents(mAnimationEvents, finalState); 91 92 int childCount = mHostLayout.getChildCount(); 93 mAnimationFilter.applyCombination(mNewEvents); 94 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents); 95 for (int i = 0; i < childCount; i++) { 96 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); 97 StackScrollState.ViewState viewState = finalState.getViewStateForView(child); 98 if (viewState == null) { 99 continue; 100 } 101 102 startAnimations(child, viewState); 103 104 child.setClipBounds(null); 105 } 106 if (!isRunning()) { 107 // no child has preformed any animation, lets finish 108 onAnimationFinished(); 109 } 110 } 111 112 /** 113 * Start an animation to the given viewState 114 */ 115 private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) { 116 int childVisibility = child.getVisibility(); 117 boolean wasVisible = childVisibility == View.VISIBLE; 118 final float alpha = viewState.alpha; 119 if (!wasVisible && alpha != 0 && !viewState.gone) { 120 child.setVisibility(View.VISIBLE); 121 } 122 // start translationY animation 123 if (child.getTranslationY() != viewState.yTranslation) { 124 startYTranslationAnimation(child, viewState); 125 } 126 // start translationZ animation 127 if (child.getTranslationZ() != viewState.zTranslation) { 128 startZTranslationAnimation(child, viewState); 129 } 130 // start scale animation 131 if (child.getScaleX() != viewState.scale) { 132 startScaleAnimation(child, viewState); 133 } 134 // start alpha animation 135 if (alpha != child.getAlpha()) { 136 startAlphaAnimation(child, viewState); 137 } 138 // start height animation 139 if (viewState.height != child.getActualHeight()) { 140 startHeightAnimation(child, viewState); 141 } 142 // start dimmed animation 143 child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); 144 } 145 146 private void startHeightAnimation(final ExpandableView child, 147 StackScrollState.ViewState viewState) { 148 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); 149 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); 150 int newEndValue = viewState.height; 151 if (previousEndValue != null && previousEndValue == newEndValue) { 152 return; 153 } 154 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); 155 if (!mAnimationFilter.animateHeight) { 156 // just a local update was performed 157 if (previousAnimator != null) { 158 // we need to increase all animation keyframes of the previous animator by the 159 // relative change to the end value 160 PropertyValuesHolder[] values = previousAnimator.getValues(); 161 int relativeDiff = newEndValue - previousEndValue; 162 int newStartValue = previousStartValue + relativeDiff; 163 values[0].setIntValues(newStartValue, newEndValue); 164 child.setTag(TAG_START_HEIGHT, newStartValue); 165 child.setTag(TAG_END_HEIGHT, newEndValue); 166 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 167 return; 168 } else { 169 // no new animation needed, let's just apply the value 170 child.setActualHeight(newEndValue, false); 171 return; 172 } 173 } 174 175 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); 176 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 177 @Override 178 public void onAnimationUpdate(ValueAnimator animation) { 179 child.setActualHeight((int) animation.getAnimatedValue(), 180 false /* notifyListeners */); 181 } 182 }); 183 animator.setInterpolator(mFastOutSlowInInterpolator); 184 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 185 animator.setDuration(newDuration); 186 animator.addListener(getGlobalAnimationFinishedListener()); 187 // remove the tag when the animation is finished 188 animator.addListener(new AnimatorListenerAdapter() { 189 @Override 190 public void onAnimationEnd(Animator animation) { 191 child.setTag(TAG_ANIMATOR_HEIGHT, null); 192 child.setTag(TAG_START_HEIGHT, null); 193 child.setTag(TAG_END_HEIGHT, null); 194 } 195 }); 196 startInstantly(animator); 197 child.setTag(TAG_ANIMATOR_HEIGHT, animator); 198 child.setTag(TAG_START_HEIGHT, child.getActualHeight()); 199 child.setTag(TAG_END_HEIGHT, newEndValue); 200 } 201 202 private void startAlphaAnimation(final ExpandableView child, 203 final StackScrollState.ViewState viewState) { 204 Float previousStartValue = getChildTag(child,TAG_START_ALPHA); 205 Float previousEndValue = getChildTag(child,TAG_END_ALPHA); 206 final float newEndValue = viewState.alpha; 207 if (previousEndValue != null && previousEndValue == newEndValue) { 208 return; 209 } 210 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); 211 if (!mAnimationFilter.animateAlpha) { 212 // just a local update was performed 213 if (previousAnimator != null) { 214 // we need to increase all animation keyframes of the previous animator by the 215 // relative change to the end value 216 PropertyValuesHolder[] values = previousAnimator.getValues(); 217 float relativeDiff = newEndValue - previousEndValue; 218 float newStartValue = previousStartValue + relativeDiff; 219 values[0].setFloatValues(newStartValue, newEndValue); 220 child.setTag(TAG_START_ALPHA, newStartValue); 221 child.setTag(TAG_END_ALPHA, newEndValue); 222 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 223 return; 224 } else { 225 // no new animation needed, let's just apply the value 226 child.setAlpha(newEndValue); 227 if (newEndValue == 0) { 228 child.setVisibility(View.INVISIBLE); 229 } 230 } 231 } 232 233 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, 234 child.getAlpha(), newEndValue); 235 animator.setInterpolator(mFastOutSlowInInterpolator); 236 // Handle layer type 237 final int currentLayerType = child.getLayerType(); 238 child.setLayerType(View.LAYER_TYPE_HARDWARE, null); 239 animator.addListener(new AnimatorListenerAdapter() { 240 public boolean mWasCancelled; 241 242 @Override 243 public void onAnimationEnd(Animator animation) { 244 child.setLayerType(currentLayerType, null); 245 if (newEndValue == 0 && !mWasCancelled) { 246 child.setVisibility(View.INVISIBLE); 247 } 248 child.setTag(TAG_ANIMATOR_ALPHA, null); 249 child.setTag(TAG_START_ALPHA, null); 250 child.setTag(TAG_END_ALPHA, null); 251 } 252 253 @Override 254 public void onAnimationCancel(Animator animation) { 255 mWasCancelled = true; 256 } 257 258 @Override 259 public void onAnimationStart(Animator animation) { 260 mWasCancelled = false; 261 } 262 }); 263 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 264 animator.setDuration(newDuration); 265 animator.addListener(getGlobalAnimationFinishedListener()); 266 // remove the tag when the animation is finished 267 animator.addListener(new AnimatorListenerAdapter() { 268 @Override 269 public void onAnimationEnd(Animator animation) { 270 271 } 272 }); 273 startInstantly(animator); 274 child.setTag(TAG_ANIMATOR_ALPHA, animator); 275 child.setTag(TAG_START_ALPHA, child.getAlpha()); 276 child.setTag(TAG_END_ALPHA, newEndValue); 277 } 278 279 private void startZTranslationAnimation(final ExpandableView child, 280 final StackScrollState.ViewState viewState) { 281 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); 282 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); 283 float newEndValue = viewState.zTranslation; 284 if (previousEndValue != null && previousEndValue == newEndValue) { 285 return; 286 } 287 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); 288 if (!mAnimationFilter.animateZ) { 289 // just a local update was performed 290 if (previousAnimator != null) { 291 // we need to increase all animation keyframes of the previous animator by the 292 // relative change to the end value 293 PropertyValuesHolder[] values = previousAnimator.getValues(); 294 float relativeDiff = newEndValue - previousEndValue; 295 float newStartValue = previousStartValue + relativeDiff; 296 values[0].setFloatValues(newStartValue, newEndValue); 297 child.setTag(TAG_START_TRANSLATION_Z, newStartValue); 298 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 299 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 300 return; 301 } else { 302 // no new animation needed, let's just apply the value 303 child.setTranslationZ(newEndValue); 304 } 305 } 306 307 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, 308 child.getTranslationZ(), newEndValue); 309 animator.setInterpolator(mFastOutSlowInInterpolator); 310 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 311 animator.setDuration(newDuration); 312 animator.addListener(getGlobalAnimationFinishedListener()); 313 // remove the tag when the animation is finished 314 animator.addListener(new AnimatorListenerAdapter() { 315 @Override 316 public void onAnimationEnd(Animator animation) { 317 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); 318 child.setTag(TAG_START_TRANSLATION_Z, null); 319 child.setTag(TAG_END_TRANSLATION_Z, null); 320 } 321 }); 322 startInstantly(animator); 323 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); 324 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); 325 child.setTag(TAG_END_TRANSLATION_Z, newEndValue); 326 } 327 328 private void startYTranslationAnimation(final ExpandableView child, 329 StackScrollState.ViewState viewState) { 330 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); 331 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); 332 float newEndValue = viewState.yTranslation; 333 if (previousEndValue != null && previousEndValue == newEndValue) { 334 return; 335 } 336 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); 337 if (!mAnimationFilter.animateY) { 338 // just a local update was performed 339 if (previousAnimator != null) { 340 // we need to increase all animation keyframes of the previous animator by the 341 // relative change to the end value 342 PropertyValuesHolder[] values = previousAnimator.getValues(); 343 float relativeDiff = newEndValue - previousEndValue; 344 float newStartValue = previousStartValue + relativeDiff; 345 values[0].setFloatValues(newStartValue, newEndValue); 346 child.setTag(TAG_START_TRANSLATION_Y, newStartValue); 347 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 348 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 349 return; 350 } else { 351 // no new animation needed, let's just apply the value 352 child.setTranslationY(newEndValue); 353 return; 354 } 355 } 356 357 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 358 child.getTranslationY(), newEndValue); 359 animator.setInterpolator(mFastOutSlowInInterpolator); 360 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 361 animator.setDuration(newDuration); 362 animator.addListener(getGlobalAnimationFinishedListener()); 363 // remove the tag when the animation is finished 364 animator.addListener(new AnimatorListenerAdapter() { 365 @Override 366 public void onAnimationEnd(Animator animation) { 367 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); 368 child.setTag(TAG_START_TRANSLATION_Y, null); 369 child.setTag(TAG_END_TRANSLATION_Y, null); 370 } 371 }); 372 startInstantly(animator); 373 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); 374 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); 375 child.setTag(TAG_END_TRANSLATION_Y, newEndValue); 376 } 377 378 private void startScaleAnimation(final ExpandableView child, 379 StackScrollState.ViewState viewState) { 380 Float previousStartValue = getChildTag(child, TAG_START_SCALE); 381 Float previousEndValue = getChildTag(child, TAG_END_SCALE); 382 float newEndValue = viewState.scale; 383 if (previousEndValue != null && previousEndValue == newEndValue) { 384 return; 385 } 386 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE); 387 if (!mAnimationFilter.animateScale) { 388 // just a local update was performed 389 if (previousAnimator != null) { 390 // we need to increase all animation keyframes of the previous animator by the 391 // relative change to the end value 392 PropertyValuesHolder[] values = previousAnimator.getValues(); 393 float relativeDiff = newEndValue - previousEndValue; 394 float newStartValue = previousStartValue + relativeDiff; 395 values[0].setFloatValues(newStartValue, newEndValue); 396 values[1].setFloatValues(newStartValue, newEndValue); 397 child.setTag(TAG_START_SCALE, newStartValue); 398 child.setTag(TAG_END_SCALE, newEndValue); 399 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 400 return; 401 } else { 402 // no new animation needed, let's just apply the value 403 child.setScaleX(newEndValue); 404 child.setScaleY(newEndValue); 405 } 406 } 407 408 PropertyValuesHolder holderX = 409 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue); 410 PropertyValuesHolder holderY = 411 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue); 412 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY); 413 animator.setInterpolator(mFastOutSlowInInterpolator); 414 long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); 415 animator.setDuration(newDuration); 416 animator.addListener(getGlobalAnimationFinishedListener()); 417 // remove the tag when the animation is finished 418 animator.addListener(new AnimatorListenerAdapter() { 419 @Override 420 public void onAnimationEnd(Animator animation) { 421 child.setTag(TAG_ANIMATOR_SCALE, null); 422 child.setTag(TAG_START_SCALE, null); 423 child.setTag(TAG_END_SCALE, null); 424 } 425 }); 426 startInstantly(animator); 427 child.setTag(TAG_ANIMATOR_SCALE, animator); 428 child.setTag(TAG_START_SCALE, child.getScaleX()); 429 child.setTag(TAG_END_SCALE, newEndValue); 430 } 431 432 /** 433 * Start an animator instantly instead of waiting on the next synchronization frame 434 */ 435 private void startInstantly(ValueAnimator animator) { 436 animator.start(); 437 animator.setCurrentPlayTime(0); 438 } 439 440 /** 441 * @return an adapter which ensures that onAnimationFinished is called once no animation is 442 * running anymore 443 */ 444 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() { 445 if (!mAnimationListenerPool.empty()) { 446 return mAnimationListenerPool.pop(); 447 } 448 449 // We need to create a new one, no reusable ones found 450 return new AnimatorListenerAdapter() { 451 private boolean mWasCancelled; 452 453 @Override 454 public void onAnimationEnd(Animator animation) { 455 mAnimatorSet.remove(animation); 456 if (mAnimatorSet.isEmpty() && !mWasCancelled) { 457 onAnimationFinished(); 458 } 459 mAnimationListenerPool.push(this); 460 } 461 462 @Override 463 public void onAnimationCancel(Animator animation) { 464 mWasCancelled = true; 465 } 466 467 @Override 468 public void onAnimationStart(Animator animation) { 469 mAnimatorSet.add(animation); 470 mWasCancelled = false; 471 } 472 }; 473 } 474 475 private <T> T getChildTag(View child, int tag) { 476 return (T) child.getTag(tag); 477 } 478 479 /** 480 * Cancel the previous animator and get the duration of the new animation. 481 * 482 * @param previousAnimator the animator which was running before 483 * @return the new duration 484 */ 485 private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) { 486 long newDuration = mCurrentLength; 487 if (previousAnimator != null) { 488 // We take either the desired length of the new animation or the remaining time of 489 // the previous animator, whichever is longer. 490 newDuration = Math.max(previousAnimator.getDuration() 491 - previousAnimator.getCurrentPlayTime(), newDuration); 492 previousAnimator.cancel(); 493 } 494 return newDuration; 495 } 496 497 private void onAnimationFinished() { 498 mHandledEvents.clear(); 499 mNewEvents.clear(); 500 mHostLayout.onChildAnimationFinished(); 501 } 502 503 /** 504 * Process the animationEvents for a new animation 505 * 506 * @param animationEvents the animation events for the animation to perform 507 * @param finalState the final state to animate to 508 */ 509 private void processAnimationEvents( 510 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, 511 StackScrollState finalState) { 512 mNewEvents.clear(); 513 for (NotificationStackScrollLayout.AnimationEvent event: animationEvents) { 514 View changingView = event.changingView; 515 if (!mHandledEvents.contains(event)) { 516 if (event.animationType == NotificationStackScrollLayout.AnimationEvent 517 .ANIMATION_TYPE_ADD) { 518 519 // This item is added, initialize it's properties. 520 StackScrollState.ViewState viewState = finalState 521 .getViewStateForView(changingView); 522 if (viewState == null) { 523 // The position for this child was never generated, let's continue. 524 continue; 525 } 526 changingView.setAlpha(0); 527 changingView.setTranslationY(viewState.yTranslation); 528 changingView.setTranslationZ(viewState.zTranslation); 529 } 530 mHandledEvents.add(event); 531 mNewEvents.add(event); 532 } 533 } 534 } 535} 536