FileDocCategorySizeDatePackage
StackStateAnimator.javaAPI DocAndroid 5.1 API41116Thu Mar 12 22:22:42 GMT 2015com.android.systemui.statusbar.stack

StackStateAnimator

public class StackStateAnimator extends Object
An stack state animator which handles animations to new StackScrollStates

Fields Summary
public static final int
ANIMATION_DURATION_STANDARD
public static final int
ANIMATION_DURATION_GO_TO_FULL_SHADE
public static final int
ANIMATION_DURATION_APPEAR_DISAPPEAR
public static final int
ANIMATION_DURATION_DIMMED_ACTIVATED
public static final int
ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING
public static final int
ANIMATION_DELAY_PER_ELEMENT_MANUAL
public static final int
ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE
public static final int
ANIMATION_DELAY_PER_ELEMENT_DARK
private static final int
DELAY_EFFECT_MAX_INDEX_DIFFERENCE
private static final int
TAG_ANIMATOR_TRANSLATION_Y
private static final int
TAG_ANIMATOR_TRANSLATION_Z
private static final int
TAG_ANIMATOR_SCALE
private static final int
TAG_ANIMATOR_ALPHA
private static final int
TAG_ANIMATOR_HEIGHT
private static final int
TAG_ANIMATOR_TOP_INSET
private static final int
TAG_END_TRANSLATION_Y
private static final int
TAG_END_TRANSLATION_Z
private static final int
TAG_END_SCALE
private static final int
TAG_END_ALPHA
private static final int
TAG_END_HEIGHT
private static final int
TAG_END_TOP_INSET
private static final int
TAG_START_TRANSLATION_Y
private static final int
TAG_START_TRANSLATION_Z
private static final int
TAG_START_SCALE
private static final int
TAG_START_ALPHA
private static final int
TAG_START_HEIGHT
private static final int
TAG_START_TOP_INSET
private final android.view.animation.Interpolator
mFastOutSlowInInterpolator
private final int
mGoToFullShadeAppearingTranslation
public NotificationStackScrollLayout
mHostLayout
private ArrayList
mNewEvents
private ArrayList
mNewAddChildren
private Set
mAnimatorSet
private Stack
mAnimationListenerPool
private AnimationFilter
mAnimationFilter
private long
mCurrentLength
private long
mCurrentAdditionalDelay
private int
mCurrentLastNotAddedIndex
The current index for the last child which was not added in this event set.
private android.animation.ValueAnimator
mTopOverScrollAnimator
private android.animation.ValueAnimator
mBottomOverScrollAnimator
Constructors Summary
public StackStateAnimator(NotificationStackScrollLayout hostLayout)


       
        mHostLayout = hostLayout;
        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
                android.R.interpolator.fast_out_slow_in);
        mGoToFullShadeAppearingTranslation =
                hostLayout.getContext().getResources().getDimensionPixelSize(
                        R.dimen.go_to_full_shade_appearing_translation);
    
Methods Summary
public voidanimateOverScrollToAmount(float targetAmount, boolean onTop, boolean isRubberbanded)

        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
        if (targetAmount == startOverScrollAmount) {
            return;
        }
        cancelOverScrollAnimators(onTop);
        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
                targetAmount);
        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currentOverScroll = (float) animation.getAnimatedValue();
                mHostLayout.setOverScrollAmount(
                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
                        isRubberbanded);
            }
        });
        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (onTop) {
                    mTopOverScrollAnimator = null;
                } else {
                    mBottomOverScrollAnimator = null;
                }
            }
        });
        overScrollAnimator.start();
        if (onTop) {
            mTopOverScrollAnimator = overScrollAnimator;
        } else {
            mBottomOverScrollAnimator = overScrollAnimator;
        }
    
private longcalculateChildAnimationDelay(StackScrollState.ViewState viewState, StackScrollState finalState)

        if (mAnimationFilter.hasDarkEvent) {
            return calculateDelayDark(viewState);
        }
        if (mAnimationFilter.hasGoToFullShadeEvent) {
            return calculateDelayGoToFullShade(viewState);
        }
        long minDelay = 0;
        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
            switch (event.animationType) {
                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
                    int ownIndex = viewState.notGoneIndex;
                    int changingIndex = finalState
                            .getViewStateForView(event.changingView).notGoneIndex;
                    int difference = Math.abs(ownIndex - changingIndex);
                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
                            difference - 1));
                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
                    minDelay = Math.max(delay, minDelay);
                    break;
                }
                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
                    int ownIndex = viewState.notGoneIndex;
                    boolean noNextView = event.viewAfterChangingView == null;
                    View viewAfterChangingView = noNextView
                            ? mHostLayout.getLastChildNotGone()
                            : event.viewAfterChangingView;

                    int nextIndex = finalState
                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
                    if (ownIndex >= nextIndex) {
                        // we only have the view afterwards
                        ownIndex++;
                    }
                    int difference = Math.abs(ownIndex - nextIndex);
                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
                            difference - 1));
                    long delay = difference * delayPerElement;
                    minDelay = Math.max(delay, minDelay);
                    break;
                }
                default:
                    break;
            }
        }
        return minDelay;
    
private longcalculateDelayDark(StackScrollState.ViewState viewState)

        int referenceIndex;
        if (mAnimationFilter.darkAnimationOriginIndex ==
                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
            referenceIndex = 0;
        } else if (mAnimationFilter.darkAnimationOriginIndex ==
                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
            referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
        } else {
            referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
        }
        return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
    
private longcalculateDelayGoToFullShade(StackScrollState.ViewState viewState)

        float index = viewState.notGoneIndex;
        index = (float) Math.pow(index, 0.7f);
        return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
    
private longcancelAnimatorAndGetNewDuration(long duration, android.animation.ValueAnimator previousAnimator)
Cancel the previous animator and get the duration of the new animation.

param
duration the new duration
param
previousAnimator the animator which was running before
return
the new duration

        long newDuration = duration;
        if (previousAnimator != null) {
            // We take either the desired length of the new animation or the remaining time of
            // the previous animator, whichever is longer.
            newDuration = Math.max(previousAnimator.getDuration()
                    - previousAnimator.getCurrentPlayTime(), newDuration);
            previousAnimator.cancel();
        }
        return newDuration;
    
public voidcancelOverScrollAnimators(boolean onTop)

        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
        if (currentAnimator != null) {
            currentAnimator.cancel();
        }
    
private intfindLastNotAddedIndex(StackScrollState finalState)

        int childCount = mHostLayout.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);

            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
            if (viewState == null || child.getVisibility() == View.GONE) {
                continue;
            }
            if (!mNewAddChildren.contains(child)) {
                return viewState.notGoneIndex;
            }
        }
        return -1;
    
private static TgetChildTag(android.view.View child, int tag)

        return (T) child.getTag(tag);
    
public static intgetFinalActualHeight(com.android.systemui.statusbar.ExpandableView view)
Get the end value of the height animation running on a view or the actualHeight if no animation is running.

        if (view == null) {
            return 0;
        }
        ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
        if (heightAnimator == null) {
            return view.getActualHeight();
        } else {
            return getChildTag(view, TAG_END_HEIGHT);
        }
    
private android.animation.AnimatorListenerAdaptergetGlobalAnimationFinishedListener()

return
an adapter which ensures that onAnimationFinished is called once no animation is running anymore

        if (!mAnimationListenerPool.empty()) {
            return mAnimationListenerPool.pop();
        }

        // We need to create a new one, no reusable ones found
        return new AnimatorListenerAdapter() {
            private boolean mWasCancelled;

            @Override
            public void onAnimationEnd(Animator animation) {
                mAnimatorSet.remove(animation);
                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
                    onAnimationFinished();
                }
                mAnimationListenerPool.push(this);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mWasCancelled = true;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                mWasCancelled = false;
            }
        };
    
public booleanisRunning()

        return !mAnimatorSet.isEmpty();
    
private voidonAnimationFinished()

        mHostLayout.onChildAnimationFinished();
    
private voidprocessAnimationEvents(java.util.ArrayList animationEvents, StackScrollState finalState)
Process the animationEvents for a new animation

param
animationEvents the animation events for the animation to perform
param
finalState the final state to animate to

        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
            final ExpandableView changingView = (ExpandableView) event.changingView;
            if (event.animationType ==
                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {

                // This item is added, initialize it's properties.
                StackScrollState.ViewState viewState = finalState
                        .getViewStateForView(changingView);
                if (viewState == null) {
                    // The position for this child was never generated, let's continue.
                    continue;
                }
                if (changingView.getVisibility() == View.GONE) {
                    // The view was set to gone but the state never removed
                    finalState.removeViewStateForView(changingView);
                    continue;
                }
                changingView.setAlpha(viewState.alpha);
                changingView.setTranslationY(viewState.yTranslation);
                changingView.setTranslationZ(viewState.zTranslation);
                changingView.setActualHeight(viewState.height, false);
                mNewAddChildren.add(changingView);

            } else if (event.animationType ==
                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
                if (changingView.getVisibility() == View.GONE) {
                    mHostLayout.getOverlay().remove(changingView);
                    continue;
                }

                // Find the amount to translate up. This is needed in order to understand the
                // direction of the remove animation (either downwards or upwards)
                StackScrollState.ViewState viewState = finalState
                        .getViewStateForView(event.viewAfterChangingView);
                int actualHeight = changingView.getActualHeight();
                // upwards by default
                float translationDirection = -1.0f;
                if (viewState != null) {
                    // there was a view after this one, Approximate the distance the next child
                    // travelled
                    translationDirection = ((viewState.yTranslation
                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
                            actualHeight);
                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);

                }
                changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                        translationDirection, new Runnable() {
                    @Override
                    public void run() {
                        // remove the temporary overlay
                        mHostLayout.getOverlay().remove(changingView);
                    }
                });
            }  else if (event.animationType ==
                NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                // A race condition can trigger the view to be added to the overlay even though
                // it is swiped out. So let's remove it
                mHostLayout.getOverlay().remove(changingView);
            }
            mNewEvents.add(event);
        }
    
private voidstartAlphaAnimation(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)

        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
        final float newEndValue = viewState.alpha;
        if (previousEndValue != null && previousEndValue == newEndValue) {
            return;
        }
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
        if (!mAnimationFilter.animateAlpha) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                float relativeDiff = newEndValue - previousEndValue;
                float newStartValue = previousStartValue + relativeDiff;
                values[0].setFloatValues(newStartValue, newEndValue);
                child.setTag(TAG_START_ALPHA, newStartValue);
                child.setTag(TAG_END_ALPHA, newEndValue);
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                child.setAlpha(newEndValue);
                if (newEndValue == 0) {
                    child.setVisibility(View.INVISIBLE);
                }
            }
        }

        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
                child.getAlpha(), newEndValue);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        // Handle layer type
        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        animator.addListener(new AnimatorListenerAdapter() {
            public boolean mWasCancelled;

            @Override
            public void onAnimationEnd(Animator animation) {
                child.setLayerType(View.LAYER_TYPE_NONE, null);
                if (newEndValue == 0 && !mWasCancelled) {
                    child.setVisibility(View.INVISIBLE);
                }
                // remove the tag when the animation is finished
                child.setTag(TAG_ANIMATOR_ALPHA, null);
                child.setTag(TAG_START_ALPHA, null);
                child.setTag(TAG_END_ALPHA, null);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mWasCancelled = true;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                mWasCancelled = false;
            }
        });
        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
        animator.setDuration(newDuration);
        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
            animator.setStartDelay(delay);
        }
        animator.addListener(getGlobalAnimationFinishedListener());

        startAnimator(animator);
        child.setTag(TAG_ANIMATOR_ALPHA, animator);
        child.setTag(TAG_START_ALPHA, child.getAlpha());
        child.setTag(TAG_END_ALPHA, newEndValue);
    
public voidstartAnimationForEvents(java.util.ArrayList mAnimationEvents, StackScrollState finalState, long additionalDelay)


        processAnimationEvents(mAnimationEvents, finalState);

        int childCount = mHostLayout.getChildCount();
        mAnimationFilter.applyCombination(mNewEvents);
        mCurrentAdditionalDelay = additionalDelay;
        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
        mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
        for (int i = 0; i < childCount; i++) {
            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);

            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
            if (viewState == null || child.getVisibility() == View.GONE) {
                continue;
            }

            child.setClipBounds(null);
            startAnimations(child, viewState, finalState, i);
        }
        if (!isRunning()) {
            // no child has preformed any animation, lets finish
            onAnimationFinished();
        }
        mNewEvents.clear();
        mNewAddChildren.clear();
    
private voidstartAnimations(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, StackScrollState finalState, int i)
Start an animation to the given viewState

        int childVisibility = child.getVisibility();
        boolean wasVisible = childVisibility == View.VISIBLE;
        final float alpha = viewState.alpha;
        if (!wasVisible && alpha != 0 && !viewState.gone) {
            child.setVisibility(View.VISIBLE);
        }

        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
        boolean scaleChanging = child.getScaleX() != viewState.scale;
        boolean alphaChanging = alpha != child.getAlpha();
        boolean heightChanging = viewState.height != child.getActualHeight();
        boolean darkChanging = viewState.dark != child.isDark();
        boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
        boolean wasAdded = mNewAddChildren.contains(child);
        boolean hasDelays = mAnimationFilter.hasDelays;
        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
                alphaChanging || heightChanging || topInsetChanging || darkChanging;
        boolean noAnimation = wasAdded;
        long delay = 0;
        long duration = mCurrentLength;
        if (hasDelays && isDelayRelevant || wasAdded) {
            delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
        }

        if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
            child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
            yTranslationChanging = true;
            float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
            longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
            duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
                    (long) (100 * longerDurationFactor);
        }

        // start translationY animation
        if (yTranslationChanging) {
            if (noAnimation && !mAnimationFilter.hasGoToFullShadeEvent) {
                child.setTranslationY(viewState.yTranslation);
            } else {
                startYTranslationAnimation(child, viewState, duration, delay);
            }
        }

        // start translationZ animation
        if (zTranslationChanging) {
            if (noAnimation) {
                child.setTranslationZ(viewState.zTranslation);
            } else {
                startZTranslationAnimation(child, viewState, duration, delay);
            }
        }

        // start scale animation
        if (scaleChanging) {
            if (noAnimation) {
                child.setScaleX(viewState.scale);
                child.setScaleY(viewState.scale);
            } else {
                startScaleAnimation(child, viewState, duration);
            }
        }

        // start alpha animation
        if (alphaChanging && child.getTranslationX() == 0) {
            if (noAnimation) {
                child.setAlpha(viewState.alpha);
            } else {
                startAlphaAnimation(child, viewState, duration, delay);
            }
        }

        // start height animation
        if (heightChanging && child.getActualHeight() != 0) {
            if (noAnimation) {
                child.setActualHeight(viewState.height, false);
            } else {
                startHeightAnimation(child, viewState, duration, delay);
            }
        }

        // start top inset animation
        if (topInsetChanging) {
            if (noAnimation) {
                child.setClipTopAmount(viewState.clipTopAmount);
            } else {
                startInsetAnimation(child, viewState, duration, delay);
            }
        }

        // start dimmed animation
        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded
                && !noAnimation);

        // start dark animation
        child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay);

        // apply speed bump state
        child.setBelowSpeedBump(viewState.belowSpeedBump);

        // start hiding sensitive animation
        child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive &&
                !wasAdded && !noAnimation, delay, duration);

        if (wasAdded) {
            child.performAddAnimation(delay, mCurrentLength);
        }
        if (child instanceof SpeedBumpView) {
            finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
                    delay + duration);
        }
    
private voidstartAnimator(android.animation.ValueAnimator animator)

        mAnimatorSet.add(animator);
        animator.start();
    
private voidstartHeightAnimation(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)

        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
        int newEndValue = viewState.height;
        if (previousEndValue != null && previousEndValue == newEndValue) {
            return;
        }
        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
        if (!mAnimationFilter.animateHeight) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                int relativeDiff = newEndValue - previousEndValue;
                int newStartValue = previousStartValue + relativeDiff;
                values[0].setIntValues(newStartValue, newEndValue);
                child.setTag(TAG_START_HEIGHT, newStartValue);
                child.setTag(TAG_END_HEIGHT, newEndValue);
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                child.setActualHeight(newEndValue, false);
                return;
            }
        }

        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                child.setActualHeight((int) animation.getAnimatedValue(),
                        false /* notifyListeners */);
            }
        });
        animator.setInterpolator(mFastOutSlowInInterpolator);
        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
        animator.setDuration(newDuration);
        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
            animator.setStartDelay(delay);
        }
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                child.setTag(TAG_ANIMATOR_HEIGHT, null);
                child.setTag(TAG_START_HEIGHT, null);
                child.setTag(TAG_END_HEIGHT, null);
            }
        });
        startAnimator(animator);
        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
        child.setTag(TAG_END_HEIGHT, newEndValue);
    
private voidstartInsetAnimation(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)

        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
        int newEndValue = viewState.clipTopAmount;
        if (previousEndValue != null && previousEndValue == newEndValue) {
            return;
        }
        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
        if (!mAnimationFilter.animateTopInset) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                int relativeDiff = newEndValue - previousEndValue;
                int newStartValue = previousStartValue + relativeDiff;
                values[0].setIntValues(newStartValue, newEndValue);
                child.setTag(TAG_START_TOP_INSET, newStartValue);
                child.setTag(TAG_END_TOP_INSET, newEndValue);
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                child.setClipTopAmount(newEndValue);
                return;
            }
        }

        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                child.setClipTopAmount((int) animation.getAnimatedValue());
            }
        });
        animator.setInterpolator(mFastOutSlowInInterpolator);
        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
        animator.setDuration(newDuration);
        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
            animator.setStartDelay(delay);
        }
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
                child.setTag(TAG_START_TOP_INSET, null);
                child.setTag(TAG_END_TOP_INSET, null);
            }
        });
        startAnimator(animator);
        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
        child.setTag(TAG_END_TOP_INSET, newEndValue);
    
private voidstartScaleAnimation(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, long duration)

        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
        float newEndValue = viewState.scale;
        if (previousEndValue != null && previousEndValue == newEndValue) {
            return;
        }
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
        if (!mAnimationFilter.animateScale) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                float relativeDiff = newEndValue - previousEndValue;
                float newStartValue = previousStartValue + relativeDiff;
                values[0].setFloatValues(newStartValue, newEndValue);
                values[1].setFloatValues(newStartValue, newEndValue);
                child.setTag(TAG_START_SCALE, newStartValue);
                child.setTag(TAG_END_SCALE, newEndValue);
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                child.setScaleX(newEndValue);
                child.setScaleY(newEndValue);
            }
        }

        PropertyValuesHolder holderX =
                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
        PropertyValuesHolder holderY =
                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
        animator.setDuration(newDuration);
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                child.setTag(TAG_ANIMATOR_SCALE, null);
                child.setTag(TAG_START_SCALE, null);
                child.setTag(TAG_END_SCALE, null);
            }
        });
        startAnimator(animator);
        child.setTag(TAG_ANIMATOR_SCALE, animator);
        child.setTag(TAG_START_SCALE, child.getScaleX());
        child.setTag(TAG_END_SCALE, newEndValue);
    
private voidstartYTranslationAnimation(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)

        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
        float newEndValue = viewState.yTranslation;
        if (previousEndValue != null && previousEndValue == newEndValue) {
            return;
        }
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
        if (!mAnimationFilter.animateY) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                float relativeDiff = newEndValue - previousEndValue;
                float newStartValue = previousStartValue + relativeDiff;
                values[0].setFloatValues(newStartValue, newEndValue);
                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                child.setTranslationY(newEndValue);
                return;
            }
        }

        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
                child.getTranslationY(), newEndValue);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
        animator.setDuration(newDuration);
        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
            animator.setStartDelay(delay);
        }
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
                child.setTag(TAG_START_TRANSLATION_Y, null);
                child.setTag(TAG_END_TRANSLATION_Y, null);
            }
        });
        startAnimator(animator);
        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
    
private voidstartZTranslationAnimation(com.android.systemui.statusbar.ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)

        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
        float newEndValue = viewState.zTranslation;
        if (previousEndValue != null && previousEndValue == newEndValue) {
            return;
        }
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
        if (!mAnimationFilter.animateZ) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                float relativeDiff = newEndValue - previousEndValue;
                float newStartValue = previousStartValue + relativeDiff;
                values[0].setFloatValues(newStartValue, newEndValue);
                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                child.setTranslationZ(newEndValue);
            }
        }

        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
                child.getTranslationZ(), newEndValue);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
        animator.setDuration(newDuration);
        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
            animator.setStartDelay(delay);
        }
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
                child.setTag(TAG_START_TRANSLATION_Z, null);
                child.setTag(TAG_END_TRANSLATION_Z, null);
            }
        });
        startAnimator(animator);
        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);