Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 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 | |
| 17 | package com.android.systemui.statusbar.stack; |
| 18 | |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 19 | import android.animation.Animator; |
| 20 | import android.animation.AnimatorListenerAdapter; |
| 21 | import android.animation.PropertyValuesHolder; |
| 22 | import android.animation.ValueAnimator; |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 23 | import android.view.View; |
| 24 | |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 25 | import com.android.systemui.Interpolators; |
| 26 | import com.android.systemui.R; |
| 27 | import com.android.systemui.statusbar.ExpandableNotificationRow; |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 28 | import com.android.systemui.statusbar.ExpandableView; |
| 29 | |
| 30 | /** |
| 31 | * A state of an expandable view |
| 32 | */ |
| 33 | public class ExpandableViewState extends ViewState { |
| 34 | |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 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; |
| 44 | |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 45 | // These are flags such that we can create masks for filtering. |
| 46 | |
| 47 | /** |
| 48 | * No known location. This is the default and should not be set after an invocation of the |
| 49 | * algorithm. |
| 50 | */ |
| 51 | public static final int LOCATION_UNKNOWN = 0x00; |
| 52 | |
| 53 | /** |
| 54 | * The location is the first heads up notification, so on the very top. |
| 55 | */ |
| 56 | public static final int LOCATION_FIRST_HUN = 0x01; |
| 57 | |
| 58 | /** |
| 59 | * The location is hidden / scrolled away on the top. |
| 60 | */ |
| 61 | public static final int LOCATION_HIDDEN_TOP = 0x02; |
| 62 | |
| 63 | /** |
| 64 | * The location is in the main area of the screen and visible. |
| 65 | */ |
| 66 | public static final int LOCATION_MAIN_AREA = 0x04; |
| 67 | |
| 68 | /** |
| 69 | * The location is in the bottom stack and it's peeking |
| 70 | */ |
| 71 | public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08; |
| 72 | |
| 73 | /** |
| 74 | * The location is in the bottom stack and it's hidden. |
| 75 | */ |
| 76 | public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10; |
| 77 | |
| 78 | /** |
Selim Cinek | 281c202 | 2016-10-13 19:14:43 -0700 | [diff] [blame] | 79 | * The view isn't laid out at all. |
| 80 | */ |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 81 | public static final int LOCATION_GONE = 0x40; |
| 82 | |
Selim Cinek | a7d4f82 | 2016-12-06 14:34:47 -0800 | [diff] [blame] | 83 | /** |
| 84 | * The visible locations of a view. |
| 85 | */ |
| 86 | public static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN |
| 87 | | ExpandableViewState.LOCATION_MAIN_AREA; |
| 88 | |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 89 | public int height; |
| 90 | public boolean dimmed; |
| 91 | public boolean dark; |
| 92 | public boolean hideSensitive; |
Selim Cinek | db16737 | 2016-11-17 15:41:17 -0800 | [diff] [blame] | 93 | public boolean belowSpeedBump; |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 94 | public float shadowAlpha; |
Selim Cinek | eccb5de | 2016-10-28 15:04:05 -0700 | [diff] [blame] | 95 | public boolean inShelf; |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 96 | |
| 97 | /** |
Selim Cinek | 9b9d6e1 | 2017-11-30 12:29:47 +0100 | [diff] [blame] | 98 | * A state indicating whether a headsup is currently fully visible, even when not scrolled. |
| 99 | * Only valid if the view is heads upped. |
| 100 | */ |
| 101 | public boolean headsUpIsVisible; |
| 102 | |
| 103 | /** |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 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. |
| 106 | */ |
| 107 | public int clipTopAmount; |
| 108 | |
| 109 | /** |
| 110 | * The index of the view, only accounting for views not equal to GONE |
| 111 | */ |
| 112 | public int notGoneIndex; |
| 113 | |
| 114 | /** |
| 115 | * The location this view is currently rendered at. |
| 116 | * |
| 117 | * <p>See <code>LOCATION_</code> flags.</p> |
| 118 | */ |
| 119 | public int location; |
| 120 | |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 121 | @Override |
| 122 | public void copyFrom(ViewState viewState) { |
| 123 | super.copyFrom(viewState); |
| 124 | if (viewState instanceof ExpandableViewState) { |
| 125 | ExpandableViewState svs = (ExpandableViewState) viewState; |
| 126 | height = svs.height; |
| 127 | dimmed = svs.dimmed; |
| 128 | shadowAlpha = svs.shadowAlpha; |
| 129 | dark = svs.dark; |
| 130 | hideSensitive = svs.hideSensitive; |
Selim Cinek | db16737 | 2016-11-17 15:41:17 -0800 | [diff] [blame] | 131 | belowSpeedBump = svs.belowSpeedBump; |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 132 | clipTopAmount = svs.clipTopAmount; |
| 133 | notGoneIndex = svs.notGoneIndex; |
| 134 | location = svs.location; |
Selim Cinek | 9b9d6e1 | 2017-11-30 12:29:47 +0100 | [diff] [blame] | 135 | headsUpIsVisible = svs.headsUpIsVisible; |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 136 | } |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * Applies a {@link ExpandableViewState} to a {@link ExpandableView}. |
| 141 | */ |
| 142 | @Override |
| 143 | public void applyToView(View view) { |
| 144 | super.applyToView(view); |
| 145 | if (view instanceof ExpandableView) { |
| 146 | ExpandableView expandableView = (ExpandableView) view; |
| 147 | |
| 148 | int height = expandableView.getActualHeight(); |
| 149 | int newHeight = this.height; |
| 150 | |
| 151 | // apply height |
| 152 | if (height != newHeight) { |
| 153 | expandableView.setActualHeight(newHeight, false /* notifyListeners */); |
| 154 | } |
| 155 | |
| 156 | float shadowAlpha = expandableView.getShadowAlpha(); |
| 157 | float newShadowAlpha = this.shadowAlpha; |
| 158 | |
| 159 | // apply shadowAlpha |
| 160 | if (shadowAlpha != newShadowAlpha) { |
| 161 | expandableView.setShadowAlpha(newShadowAlpha); |
| 162 | } |
| 163 | |
| 164 | // apply dimming |
| 165 | expandableView.setDimmed(this.dimmed, false /* animate */); |
| 166 | |
| 167 | // apply hiding sensitive |
| 168 | expandableView.setHideSensitive( |
| 169 | this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); |
| 170 | |
Selim Cinek | db16737 | 2016-11-17 15:41:17 -0800 | [diff] [blame] | 171 | // apply below shelf speed bump |
| 172 | expandableView.setBelowSpeedBump(this.belowSpeedBump); |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 173 | |
| 174 | // apply dark |
| 175 | expandableView.setDark(this.dark, false /* animate */, 0 /* delay */); |
| 176 | |
| 177 | // apply clipping |
| 178 | float oldClipTopAmount = expandableView.getClipTopAmount(); |
| 179 | if (oldClipTopAmount != this.clipTopAmount) { |
| 180 | expandableView.setClipTopAmount(this.clipTopAmount); |
| 181 | } |
Selim Cinek | eccb5de | 2016-10-28 15:04:05 -0700 | [diff] [blame] | 182 | |
| 183 | expandableView.setTransformingInShelf(false); |
| 184 | expandableView.setInShelf(inShelf); |
Selim Cinek | 9b9d6e1 | 2017-11-30 12:29:47 +0100 | [diff] [blame] | 185 | |
| 186 | if (headsUpIsVisible) { |
| 187 | expandableView.setHeadsUpIsVisible(); |
| 188 | } |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 189 | } |
| 190 | } |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 191 | |
| 192 | @Override |
| 193 | public void animateTo(View child, AnimationProperties properties) { |
| 194 | super.animateTo(child, properties); |
| 195 | if (!(child instanceof ExpandableView)) { |
| 196 | return; |
| 197 | } |
| 198 | ExpandableView expandableView = (ExpandableView) child; |
| 199 | AnimationFilter animationFilter = properties.getAnimationFilter(); |
| 200 | |
| 201 | // start height animation |
| 202 | if (this.height != expandableView.getActualHeight()) { |
| 203 | startHeightAnimation(expandableView, properties); |
| 204 | } else { |
| 205 | abortAnimation(child, TAG_ANIMATOR_HEIGHT); |
| 206 | } |
| 207 | |
| 208 | // start shadow alpha animation |
| 209 | if (this.shadowAlpha != expandableView.getShadowAlpha()) { |
| 210 | startShadowAlphaAnimation(expandableView, properties); |
| 211 | } else { |
| 212 | abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA); |
| 213 | } |
| 214 | |
| 215 | // start top inset animation |
| 216 | if (this.clipTopAmount != expandableView.getClipTopAmount()) { |
| 217 | startInsetAnimation(expandableView, properties); |
| 218 | } else { |
| 219 | abortAnimation(child, TAG_ANIMATOR_TOP_INSET); |
| 220 | } |
| 221 | |
| 222 | // start dimmed animation |
| 223 | expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed); |
| 224 | |
Selim Cinek | db16737 | 2016-11-17 15:41:17 -0800 | [diff] [blame] | 225 | // apply below the speed bump |
| 226 | expandableView.setBelowSpeedBump(this.belowSpeedBump); |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 227 | |
| 228 | // start hiding sensitive animation |
| 229 | expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive, |
| 230 | properties.delay, properties.duration); |
| 231 | |
| 232 | // start dark animation |
| 233 | expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay); |
| 234 | |
Selim Cinek | 5b5beb01 | 2016-11-08 18:11:58 -0800 | [diff] [blame] | 235 | if (properties.wasAdded(child) && !hidden) { |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 236 | expandableView.performAddAnimation(properties.delay, properties.duration); |
| 237 | } |
Selim Cinek | eccb5de | 2016-10-28 15:04:05 -0700 | [diff] [blame] | 238 | |
| 239 | if (!expandableView.isInShelf() && this.inShelf) { |
| 240 | expandableView.setTransformingInShelf(true); |
| 241 | } |
| 242 | expandableView.setInShelf(this.inShelf); |
Selim Cinek | 9b9d6e1 | 2017-11-30 12:29:47 +0100 | [diff] [blame] | 243 | |
| 244 | if (headsUpIsVisible) { |
| 245 | expandableView.setHeadsUpIsVisible(); |
| 246 | } |
Selim Cinek | 0cfbef4 | 2016-11-09 19:06:36 -0800 | [diff] [blame] | 247 | } |
| 248 | |
| 249 | private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) { |
| 250 | Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); |
| 251 | Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); |
| 252 | int newEndValue = this.height; |
| 253 | if (previousEndValue != null && previousEndValue == newEndValue) { |
| 254 | return; |
| 255 | } |
| 256 | ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); |
| 257 | AnimationFilter filter = properties.getAnimationFilter(); |
| 258 | if (!filter.animateHeight) { |
| 259 | // just a local update was performed |
| 260 | if (previousAnimator != null) { |
| 261 | // we need to increase all animation keyframes of the previous animator by the |
| 262 | // relative change to the end value |
| 263 | PropertyValuesHolder[] values = previousAnimator.getValues(); |
| 264 | int relativeDiff = newEndValue - previousEndValue; |
| 265 | int newStartValue = previousStartValue + relativeDiff; |
| 266 | values[0].setIntValues(newStartValue, newEndValue); |
| 267 | child.setTag(TAG_START_HEIGHT, newStartValue); |
| 268 | child.setTag(TAG_END_HEIGHT, newEndValue); |
| 269 | previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); |
| 270 | return; |
| 271 | } else { |
| 272 | // no new animation needed, let's just apply the value |
| 273 | child.setActualHeight(newEndValue, false); |
| 274 | return; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue); |
| 279 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 280 | @Override |
| 281 | public void onAnimationUpdate(ValueAnimator animation) { |
| 282 | child.setActualHeight((int) animation.getAnimatedValue(), |
| 283 | false /* notifyListeners */); |
| 284 | } |
| 285 | }); |
| 286 | animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); |
| 287 | long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); |
| 288 | animator.setDuration(newDuration); |
| 289 | if (properties.delay > 0 && (previousAnimator == null |
| 290 | || previousAnimator.getAnimatedFraction() == 0)) { |
| 291 | animator.setStartDelay(properties.delay); |
| 292 | } |
| 293 | AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); |
| 294 | if (listener != null) { |
| 295 | animator.addListener(listener); |
| 296 | } |
| 297 | // remove the tag when the animation is finished |
| 298 | animator.addListener(new AnimatorListenerAdapter() { |
| 299 | boolean mWasCancelled; |
| 300 | |
| 301 | @Override |
| 302 | public void onAnimationEnd(Animator animation) { |
| 303 | child.setTag(TAG_ANIMATOR_HEIGHT, null); |
| 304 | child.setTag(TAG_START_HEIGHT, null); |
| 305 | child.setTag(TAG_END_HEIGHT, null); |
| 306 | child.setActualHeightAnimating(false); |
| 307 | if (!mWasCancelled && child instanceof ExpandableNotificationRow) { |
| 308 | ((ExpandableNotificationRow) child).setGroupExpansionChanging( |
| 309 | false /* isExpansionChanging */); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | @Override |
| 314 | public void onAnimationStart(Animator animation) { |
| 315 | mWasCancelled = false; |
| 316 | } |
| 317 | |
| 318 | @Override |
| 319 | public void onAnimationCancel(Animator animation) { |
| 320 | mWasCancelled = true; |
| 321 | } |
| 322 | }); |
| 323 | startAnimator(animator, listener); |
| 324 | child.setTag(TAG_ANIMATOR_HEIGHT, animator); |
| 325 | child.setTag(TAG_START_HEIGHT, child.getActualHeight()); |
| 326 | child.setTag(TAG_END_HEIGHT, newEndValue); |
| 327 | child.setActualHeightAnimating(true); |
| 328 | } |
| 329 | |
| 330 | private void startShadowAlphaAnimation(final ExpandableView child, |
| 331 | AnimationProperties properties) { |
| 332 | Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA); |
| 333 | Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA); |
| 334 | float newEndValue = this.shadowAlpha; |
| 335 | if (previousEndValue != null && previousEndValue == newEndValue) { |
| 336 | return; |
| 337 | } |
| 338 | ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA); |
| 339 | AnimationFilter filter = properties.getAnimationFilter(); |
| 340 | if (!filter.animateShadowAlpha) { |
| 341 | // just a local update was performed |
| 342 | if (previousAnimator != null) { |
| 343 | // we need to increase all animation keyframes of the previous animator by the |
| 344 | // relative change to the end value |
| 345 | PropertyValuesHolder[] values = previousAnimator.getValues(); |
| 346 | float relativeDiff = newEndValue - previousEndValue; |
| 347 | float newStartValue = previousStartValue + relativeDiff; |
| 348 | values[0].setFloatValues(newStartValue, newEndValue); |
| 349 | child.setTag(TAG_START_SHADOW_ALPHA, newStartValue); |
| 350 | child.setTag(TAG_END_SHADOW_ALPHA, newEndValue); |
| 351 | previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); |
| 352 | return; |
| 353 | } else { |
| 354 | // no new animation needed, let's just apply the value |
| 355 | child.setShadowAlpha(newEndValue); |
| 356 | return; |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue); |
| 361 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 362 | @Override |
| 363 | public void onAnimationUpdate(ValueAnimator animation) { |
| 364 | child.setShadowAlpha((float) animation.getAnimatedValue()); |
| 365 | } |
| 366 | }); |
| 367 | animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); |
| 368 | long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); |
| 369 | animator.setDuration(newDuration); |
| 370 | if (properties.delay > 0 && (previousAnimator == null |
| 371 | || previousAnimator.getAnimatedFraction() == 0)) { |
| 372 | animator.setStartDelay(properties.delay); |
| 373 | } |
| 374 | AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); |
| 375 | if (listener != null) { |
| 376 | animator.addListener(listener); |
| 377 | } |
| 378 | // remove the tag when the animation is finished |
| 379 | animator.addListener(new AnimatorListenerAdapter() { |
| 380 | @Override |
| 381 | public void onAnimationEnd(Animator animation) { |
| 382 | child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null); |
| 383 | child.setTag(TAG_START_SHADOW_ALPHA, null); |
| 384 | child.setTag(TAG_END_SHADOW_ALPHA, null); |
| 385 | } |
| 386 | }); |
| 387 | startAnimator(animator, listener); |
| 388 | child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator); |
| 389 | child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha()); |
| 390 | child.setTag(TAG_END_SHADOW_ALPHA, newEndValue); |
| 391 | } |
| 392 | |
| 393 | private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) { |
| 394 | Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); |
| 395 | Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); |
| 396 | int newEndValue = this.clipTopAmount; |
| 397 | if (previousEndValue != null && previousEndValue == newEndValue) { |
| 398 | return; |
| 399 | } |
| 400 | ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); |
| 401 | AnimationFilter filter = properties.getAnimationFilter(); |
| 402 | if (!filter.animateTopInset) { |
| 403 | // just a local update was performed |
| 404 | if (previousAnimator != null) { |
| 405 | // we need to increase all animation keyframes of the previous animator by the |
| 406 | // relative change to the end value |
| 407 | PropertyValuesHolder[] values = previousAnimator.getValues(); |
| 408 | int relativeDiff = newEndValue - previousEndValue; |
| 409 | int newStartValue = previousStartValue + relativeDiff; |
| 410 | values[0].setIntValues(newStartValue, newEndValue); |
| 411 | child.setTag(TAG_START_TOP_INSET, newStartValue); |
| 412 | child.setTag(TAG_END_TOP_INSET, newEndValue); |
| 413 | previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); |
| 414 | return; |
| 415 | } else { |
| 416 | // no new animation needed, let's just apply the value |
| 417 | child.setClipTopAmount(newEndValue); |
| 418 | return; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); |
| 423 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 424 | @Override |
| 425 | public void onAnimationUpdate(ValueAnimator animation) { |
| 426 | child.setClipTopAmount((int) animation.getAnimatedValue()); |
| 427 | } |
| 428 | }); |
| 429 | animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); |
| 430 | long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); |
| 431 | animator.setDuration(newDuration); |
| 432 | if (properties.delay > 0 && (previousAnimator == null |
| 433 | || previousAnimator.getAnimatedFraction() == 0)) { |
| 434 | animator.setStartDelay(properties.delay); |
| 435 | } |
| 436 | AnimatorListenerAdapter listener = properties.getAnimationFinishListener(); |
| 437 | if (listener != null) { |
| 438 | animator.addListener(listener); |
| 439 | } |
| 440 | // remove the tag when the animation is finished |
| 441 | animator.addListener(new AnimatorListenerAdapter() { |
| 442 | @Override |
| 443 | public void onAnimationEnd(Animator animation) { |
| 444 | child.setTag(TAG_ANIMATOR_TOP_INSET, null); |
| 445 | child.setTag(TAG_START_TOP_INSET, null); |
| 446 | child.setTag(TAG_END_TOP_INSET, null); |
| 447 | } |
| 448 | }); |
| 449 | startAnimator(animator, listener); |
| 450 | child.setTag(TAG_ANIMATOR_TOP_INSET, animator); |
| 451 | child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); |
| 452 | child.setTag(TAG_END_TOP_INSET, newEndValue); |
| 453 | } |
| 454 | |
| 455 | /** |
| 456 | * Get the end value of the height animation running on a view or the actualHeight |
| 457 | * if no animation is running. |
| 458 | */ |
| 459 | public static int getFinalActualHeight(ExpandableView view) { |
| 460 | if (view == null) { |
| 461 | return 0; |
| 462 | } |
| 463 | ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT); |
| 464 | if (heightAnimator == null) { |
| 465 | return view.getActualHeight(); |
| 466 | } else { |
| 467 | return getChildTag(view, TAG_END_HEIGHT); |
| 468 | } |
| 469 | } |
Selim Cinek | bbcebde | 2016-11-09 18:28:20 -0800 | [diff] [blame] | 470 | } |