2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar.notification.stack;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.PropertyValuesHolder;
22 import android.animation.ValueAnimator;
23 import android.view.View;
25 import com.android.systemui.Interpolators;
26 import com.android.systemui.R;
27 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
28 import com.android.systemui.statusbar.notification.row.ExpandableView;
31 * A state of an expandable view
33 public class ExpandableViewState extends ViewState {
35 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
36 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
37 private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
38 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
39 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
40 private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
41 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
42 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
43 private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
45 // These are flags such that we can create masks for filtering.
48 * No known location. This is the default and should not be set after an invocation of the
51 public static final int LOCATION_UNKNOWN = 0x00;
54 * The location is the first heads up notification, so on the very top.
56 public static final int LOCATION_FIRST_HUN = 0x01;
59 * The location is hidden / scrolled away on the top.
61 public static final int LOCATION_HIDDEN_TOP = 0x02;
64 * The location is in the main area of the screen and visible.
66 public static final int LOCATION_MAIN_AREA = 0x04;
69 * The location is in the bottom stack and it's peeking
71 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
74 * The location is in the bottom stack and it's hidden.
76 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
79 * The view isn't laid out at all.
81 public static final int LOCATION_GONE = 0x40;
84 * The visible locations of a view.
86 public static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN
87 | ExpandableViewState.LOCATION_MAIN_AREA;
90 public boolean dimmed;
92 public boolean hideSensitive;
93 public boolean belowSpeedBump;
94 public float shadowAlpha;
95 public boolean inShelf;
98 * A state indicating whether a headsup is currently fully visible, even when not scrolled.
99 * Only valid if the view is heads upped.
101 public boolean headsUpIsVisible;
104 * How much the child overlaps with the previous child on top. This is used to
105 * show the background properly when the child on top is translating away.
107 public int clipTopAmount;
110 * The index of the view, only accounting for views not equal to GONE
112 public int notGoneIndex;
115 * The location this view is currently rendered at.
117 * <p>See <code>LOCATION_</code> flags.</p>
122 public void copyFrom(ViewState viewState) {
123 super.copyFrom(viewState);
124 if (viewState instanceof ExpandableViewState) {
125 ExpandableViewState svs = (ExpandableViewState) viewState;
128 shadowAlpha = svs.shadowAlpha;
130 hideSensitive = svs.hideSensitive;
131 belowSpeedBump = svs.belowSpeedBump;
132 clipTopAmount = svs.clipTopAmount;
133 notGoneIndex = svs.notGoneIndex;
134 location = svs.location;
135 headsUpIsVisible = svs.headsUpIsVisible;
140 * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
143 public void applyToView(View view) {
144 super.applyToView(view);
145 if (view instanceof ExpandableView) {
146 ExpandableView expandableView = (ExpandableView) view;
148 int height = expandableView.getActualHeight();
149 int newHeight = this.height;
152 if (height != newHeight) {
153 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
156 float shadowAlpha = expandableView.getShadowAlpha();
157 float newShadowAlpha = this.shadowAlpha;
160 if (shadowAlpha != newShadowAlpha) {
161 expandableView.setShadowAlpha(newShadowAlpha);
165 expandableView.setDimmed(this.dimmed, false /* animate */);
167 // apply hiding sensitive
168 expandableView.setHideSensitive(
169 this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
171 // apply below shelf speed bump
172 expandableView.setBelowSpeedBump(this.belowSpeedBump);
175 expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
178 float oldClipTopAmount = expandableView.getClipTopAmount();
179 if (oldClipTopAmount != this.clipTopAmount) {
180 expandableView.setClipTopAmount(this.clipTopAmount);
183 expandableView.setTransformingInShelf(false);
184 expandableView.setInShelf(inShelf);
186 if (headsUpIsVisible) {
187 expandableView.setHeadsUpIsVisible();
193 public void animateTo(View child, AnimationProperties properties) {
194 super.animateTo(child, properties);
195 if (!(child instanceof ExpandableView)) {
198 ExpandableView expandableView = (ExpandableView) child;
199 AnimationFilter animationFilter = properties.getAnimationFilter();
201 // start height animation
202 if (this.height != expandableView.getActualHeight()) {
203 startHeightAnimation(expandableView, properties);
205 abortAnimation(child, TAG_ANIMATOR_HEIGHT);
208 // start shadow alpha animation
209 if (this.shadowAlpha != expandableView.getShadowAlpha()) {
210 startShadowAlphaAnimation(expandableView, properties);
212 abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
215 // start top inset animation
216 if (this.clipTopAmount != expandableView.getClipTopAmount()) {
217 startInsetAnimation(expandableView, properties);
219 abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
222 // start dimmed animation
223 expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
225 // apply below the speed bump
226 expandableView.setBelowSpeedBump(this.belowSpeedBump);
228 // start hiding sensitive animation
229 expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
230 properties.delay, properties.duration);
232 // start dark animation
233 expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
235 if (properties.wasAdded(child) && !hidden) {
236 expandableView.performAddAnimation(properties.delay, properties.duration,
237 false /* isHeadsUpAppear */);
240 if (!expandableView.isInShelf() && this.inShelf) {
241 expandableView.setTransformingInShelf(true);
243 expandableView.setInShelf(this.inShelf);
245 if (headsUpIsVisible) {
246 expandableView.setHeadsUpIsVisible();
250 private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
251 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
252 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
253 int newEndValue = this.height;
254 if (previousEndValue != null && previousEndValue == newEndValue) {
257 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
258 AnimationFilter filter = properties.getAnimationFilter();
259 if (!filter.animateHeight) {
260 // just a local update was performed
261 if (previousAnimator != null) {
262 // we need to increase all animation keyframes of the previous animator by the
263 // relative change to the end value
264 PropertyValuesHolder[] values = previousAnimator.getValues();
265 int relativeDiff = newEndValue - previousEndValue;
266 int newStartValue = previousStartValue + relativeDiff;
267 values[0].setIntValues(newStartValue, newEndValue);
268 child.setTag(TAG_START_HEIGHT, newStartValue);
269 child.setTag(TAG_END_HEIGHT, newEndValue);
270 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
273 // no new animation needed, let's just apply the value
274 child.setActualHeight(newEndValue, false);
279 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
280 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
282 public void onAnimationUpdate(ValueAnimator animation) {
283 child.setActualHeight((int) animation.getAnimatedValue(),
284 false /* notifyListeners */);
287 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
288 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
289 animator.setDuration(newDuration);
290 if (properties.delay > 0 && (previousAnimator == null
291 || previousAnimator.getAnimatedFraction() == 0)) {
292 animator.setStartDelay(properties.delay);
294 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
295 if (listener != null) {
296 animator.addListener(listener);
298 // remove the tag when the animation is finished
299 animator.addListener(new AnimatorListenerAdapter() {
300 boolean mWasCancelled;
303 public void onAnimationEnd(Animator animation) {
304 child.setTag(TAG_ANIMATOR_HEIGHT, null);
305 child.setTag(TAG_START_HEIGHT, null);
306 child.setTag(TAG_END_HEIGHT, null);
307 child.setActualHeightAnimating(false);
308 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
309 ((ExpandableNotificationRow) child).setGroupExpansionChanging(
310 false /* isExpansionChanging */);
315 public void onAnimationStart(Animator animation) {
316 mWasCancelled = false;
320 public void onAnimationCancel(Animator animation) {
321 mWasCancelled = true;
324 startAnimator(animator, listener);
325 child.setTag(TAG_ANIMATOR_HEIGHT, animator);
326 child.setTag(TAG_START_HEIGHT, child.getActualHeight());
327 child.setTag(TAG_END_HEIGHT, newEndValue);
328 child.setActualHeightAnimating(true);
331 private void startShadowAlphaAnimation(final ExpandableView child,
332 AnimationProperties properties) {
333 Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
334 Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
335 float newEndValue = this.shadowAlpha;
336 if (previousEndValue != null && previousEndValue == newEndValue) {
339 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
340 AnimationFilter filter = properties.getAnimationFilter();
341 if (!filter.animateShadowAlpha) {
342 // just a local update was performed
343 if (previousAnimator != null) {
344 // we need to increase all animation keyframes of the previous animator by the
345 // relative change to the end value
346 PropertyValuesHolder[] values = previousAnimator.getValues();
347 float relativeDiff = newEndValue - previousEndValue;
348 float newStartValue = previousStartValue + relativeDiff;
349 values[0].setFloatValues(newStartValue, newEndValue);
350 child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
351 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
352 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
355 // no new animation needed, let's just apply the value
356 child.setShadowAlpha(newEndValue);
361 ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
362 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
364 public void onAnimationUpdate(ValueAnimator animation) {
365 child.setShadowAlpha((float) animation.getAnimatedValue());
368 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
369 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
370 animator.setDuration(newDuration);
371 if (properties.delay > 0 && (previousAnimator == null
372 || previousAnimator.getAnimatedFraction() == 0)) {
373 animator.setStartDelay(properties.delay);
375 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
376 if (listener != null) {
377 animator.addListener(listener);
379 // remove the tag when the animation is finished
380 animator.addListener(new AnimatorListenerAdapter() {
382 public void onAnimationEnd(Animator animation) {
383 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
384 child.setTag(TAG_START_SHADOW_ALPHA, null);
385 child.setTag(TAG_END_SHADOW_ALPHA, null);
388 startAnimator(animator, listener);
389 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
390 child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
391 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
394 private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
395 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
396 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
397 int newEndValue = this.clipTopAmount;
398 if (previousEndValue != null && previousEndValue == newEndValue) {
401 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
402 AnimationFilter filter = properties.getAnimationFilter();
403 if (!filter.animateTopInset) {
404 // just a local update was performed
405 if (previousAnimator != null) {
406 // we need to increase all animation keyframes of the previous animator by the
407 // relative change to the end value
408 PropertyValuesHolder[] values = previousAnimator.getValues();
409 int relativeDiff = newEndValue - previousEndValue;
410 int newStartValue = previousStartValue + relativeDiff;
411 values[0].setIntValues(newStartValue, newEndValue);
412 child.setTag(TAG_START_TOP_INSET, newStartValue);
413 child.setTag(TAG_END_TOP_INSET, newEndValue);
414 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
417 // no new animation needed, let's just apply the value
418 child.setClipTopAmount(newEndValue);
423 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
424 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
426 public void onAnimationUpdate(ValueAnimator animation) {
427 child.setClipTopAmount((int) animation.getAnimatedValue());
430 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
431 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
432 animator.setDuration(newDuration);
433 if (properties.delay > 0 && (previousAnimator == null
434 || previousAnimator.getAnimatedFraction() == 0)) {
435 animator.setStartDelay(properties.delay);
437 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
438 if (listener != null) {
439 animator.addListener(listener);
441 // remove the tag when the animation is finished
442 animator.addListener(new AnimatorListenerAdapter() {
444 public void onAnimationEnd(Animator animation) {
445 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
446 child.setTag(TAG_START_TOP_INSET, null);
447 child.setTag(TAG_END_TOP_INSET, null);
450 startAnimator(animator, listener);
451 child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
452 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
453 child.setTag(TAG_END_TOP_INSET, newEndValue);
457 * Get the end value of the height animation running on a view or the actualHeight
458 * if no animation is running.
460 public static int getFinalActualHeight(ExpandableView view) {
464 ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
465 if (heightAnimator == null) {
466 return view.getActualHeight();
468 return getChildTag(view, TAG_END_HEIGHT);
473 public void cancelAnimations(View view) {
474 super.cancelAnimations(view);
475 Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
476 if (animator != null) {
479 animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA);
480 if (animator != null) {
483 animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
484 if (animator != null) {