StackStateAnimator.java revision 95ed59283bd25fb363d13c000a7408bcafb5e93e
1572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek/*
2572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * Copyright (C) 2014 The Android Open Source Project
3572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *
4572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * Licensed under the Apache License, Version 2.0 (the "License");
5572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * you may not use this file except in compliance with the License.
6572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * You may obtain a copy of the License at
7572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *
8572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *      http://www.apache.org/licenses/LICENSE-2.0
9572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *
10572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * Unless required by applicable law or agreed to in writing, software
11572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * distributed under the License is distributed on an "AS IS" BASIS,
12572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * See the License for the specific language governing permissions and
14572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * limitations under the License
15572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek */
16572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
17572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekpackage com.android.systemui.statusbar.stack;
18572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
19eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport android.animation.Animator;
20eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport android.animation.AnimatorListenerAdapter;
21eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport android.animation.ObjectAnimator;
22d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggiimport android.animation.PropertyValuesHolder;
23572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.animation.ValueAnimator;
24572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.view.View;
25572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.view.animation.AnimationUtils;
26572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.view.animation.Interpolator;
27eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
28eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport com.android.systemui.R;
29572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport com.android.systemui.statusbar.ExpandableView;
308b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggiimport com.android.systemui.statusbar.SpeedBumpView;
31572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
32572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport java.util.ArrayList;
33eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.HashSet;
34eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Set;
35eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Stack;
36572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
37572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek/**
38572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * An stack state animator which handles animations to new StackScrollStates
39572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek */
40572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekpublic class StackStateAnimator {
41572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
425aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_STANDARD = 360;
4360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
455aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
4860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
498efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
505aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi
51eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
52eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
53d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
54eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
55eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
56eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
57eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
58eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
59d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
60eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
61eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
62eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
69572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
70572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    private final Interpolator mFastOutSlowInInterpolator;
7160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private final int mGoToFullShadeAppearingTranslation;
72572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public NotificationStackScrollLayout mHostLayout;
73eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
74eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            new ArrayList<>();
758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private ArrayList<View> mNewAddChildren = new ArrayList<>();
7660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private Set<Animator> mAnimatorSet = new HashSet<>();
7760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
78d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private AnimationFilter mAnimationFilter = new AnimationFilter();
795aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    private long mCurrentLength;
80dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi    private long mCurrentAdditionalDelay;
81572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
8260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    /** The current index for the last child which was not added in this event set. */
8360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private int mCurrentLastNotAddedIndex;
8460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
858d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mTopOverScrollAnimator;
868d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mBottomOverScrollAnimator;
878d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
88572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
89572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mHostLayout = hostLayout;
90572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
91eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                android.R.interpolator.fast_out_slow_in);
9260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        mGoToFullShadeAppearingTranslation =
9360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                hostLayout.getContext().getResources().getDimensionPixelSize(
9460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                        R.dimen.go_to_full_shade_appearing_translation);
95572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
96572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
97572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public boolean isRunning() {
98eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return !mAnimatorSet.isEmpty();
99572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
100572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
101572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public void startAnimationForEvents(
1020dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
103dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi            StackScrollState finalState, long additionalDelay) {
104eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
105eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        processAnimationEvents(mAnimationEvents, finalState);
106eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
107572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        int childCount = mHostLayout.getChildCount();
108d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        mAnimationFilter.applyCombination(mNewEvents);
109dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi        mCurrentAdditionalDelay = additionalDelay;
1105aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
11160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
112572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        for (int i = 0; i < childCount; i++) {
113572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
1148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
115572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
1168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (viewState == null || child.getVisibility() == View.GONE) {
117572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek                continue;
118572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
119572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
120572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            child.setClipBounds(null);
1218b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi            startAnimations(child, viewState, finalState, i);
122572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
123eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (!isRunning()) {
124eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            // no child has preformed any animation, lets finish
125eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            onAnimationFinished();
126eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewEvents.clear();
1288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewAddChildren.clear();
129572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
130572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
13160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private int findLastNotAddedIndex(StackScrollState finalState) {
13260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        int childCount = mHostLayout.getChildCount();
13360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        for (int i = childCount - 1; i >= 0; i--) {
13460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
13560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
13660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
13760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            if (viewState == null || child.getVisibility() == View.GONE) {
13860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                continue;
13960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            }
14060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            if (!mNewAddChildren.contains(child)) {
14160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                return viewState.notGoneIndex;
14260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            }
14360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
14460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        return -1;
14560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    }
14660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
147eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
148eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Start an animation to the given viewState
149eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
1508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
1518b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi            StackScrollState finalState, int i) {
152eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        int childVisibility = child.getVisibility();
153eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        boolean wasVisible = childVisibility == View.VISIBLE;
154eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        final float alpha = viewState.alpha;
155eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (!wasVisible && alpha != 0 && !viewState.gone) {
156eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            child.setVisibility(View.VISIBLE);
157eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1588efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
1598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
1608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
1618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean scaleChanging = child.getScaleX() != viewState.scale;
1628efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean alphaChanging = alpha != child.getAlpha();
1638efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean heightChanging = viewState.height != child.getActualHeight();
164708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
1658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean wasAdded = mNewAddChildren.contains(child);
1668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean hasDelays = mAnimationFilter.hasDelays;
1678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
168708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                alphaChanging || heightChanging || topInsetChanging;
1698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long delay = 0;
17060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long duration = mCurrentLength;
1718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (hasDelays && isDelayRelevant || wasAdded) {
172dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi            delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
1738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
1748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
17560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
17660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
17760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            yTranslationChanging = true;
17860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
17960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
18060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
18160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                    (long) (100 * longerDurationFactor);
18260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
18360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
184eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start translationY animation
1858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (yTranslationChanging) {
18660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            startYTranslationAnimation(child, viewState, duration, delay);
187eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
189eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start translationZ animation
1908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (zTranslationChanging) {
19160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            startZTranslationAnimation(child, viewState, duration, delay);
192d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
1938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
194d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // start scale animation
1958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (scaleChanging) {
19660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            startScaleAnimation(child, viewState, duration);
197eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
199eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start alpha animation
200eceda3d83814e20cabddc4f0755d475fa2f3d8ffDan Sandler        if (alphaChanging && child.getTranslationX() == 0) {
20160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            startAlphaAnimation(child, viewState, duration, delay);
202eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
2038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
204eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start height animation
2058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (heightChanging) {
20660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            startHeightAnimation(child, viewState, duration, delay);
20759b5a356b828fe60ea2874b0680a1bf7c84809a1Jorim Jaggi        }
2088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
209708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // start top inset animation
210708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (topInsetChanging) {
21160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            startInsetAnimation(child, viewState, duration, delay);
212708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
213708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
214d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // start dimmed animation
215fa129db84a2985f81eac4c51204180b00122b632Selim Cinek        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded);
2168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
217bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        // start dark animation
218bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        child.setDark(viewState.dark, mAnimationFilter.animateDark);
219bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock
2203d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        // apply speed bump state
2213d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        child.setBelowSpeedBump(viewState.belowSpeedBump);
2223d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek
223ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi        // start hiding sensitive animation
224ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi        child.setHideSensitive(viewState.hideSensitive,
225ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi                mAnimationFilter.animateHideSensitive && !wasAdded, delay, duration);
226ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi
227f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek        // apply scrimming
228f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek        child.setScrimAmount(viewState.scrimAmount);
229f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek
2308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (wasAdded) {
23160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            child.performAddAnimation(delay, mCurrentLength);
2328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2338b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi        if (child instanceof SpeedBumpView) {
2348b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi            finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
2358b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi                    delay + duration);
2368b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi        }
2378efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    }
2388efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
2398efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
2408efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
24160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        if (mAnimationFilter.hasGoToFullShadeEvent) {
24260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            return calculateDelayGoToFullShade(viewState);
24360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
2448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long minDelay = 0;
2458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
2468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
2478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            switch (event.animationType) {
2488efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
2498efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int changingIndex = finalState
2518efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(event.changingView).notGoneIndex;
2528efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - changingIndex);
2538efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2548efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2558efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
2568efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2588efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
2608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
2618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
2628efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2638efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    boolean noNextView = event.viewAfterChangingView == null;
2648efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    View viewAfterChangingView = noNextView
2658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            ? mHostLayout.getLastChildNotGone()
2668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            : event.viewAfterChangingView;
2678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
2688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int nextIndex = finalState
2698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
2708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    if (ownIndex >= nextIndex) {
2718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // we only have the view afterwards
2728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        ownIndex++;
2738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
2748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - nextIndex);
2758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = difference * delayPerElement;
2788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                default:
2828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            }
2848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        return minDelay;
286572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
287572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
28860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) {
28960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        float index = viewState.notGoneIndex;
29060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        index = (float) Math.pow(index, 0.7f);
29160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
29260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    }
29360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
294eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startHeightAnimation(final ExpandableView child,
29560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            StackScrollState.ViewState viewState, long duration, long delay) {
2968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
297d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
2988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        int newEndValue = viewState.height;
2998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
300eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
301eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
302eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
3038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateHeight) {
3048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
3058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
3068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
3078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
3088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
3098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int relativeDiff = newEndValue - previousEndValue;
3108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int newStartValue = previousStartValue + relativeDiff;
3118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setIntValues(newStartValue, newEndValue);
3128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, newStartValue);
3138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_HEIGHT, newEndValue);
3148df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
3158df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
3168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
3178df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
3188df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setActualHeight(newEndValue, false);
3198df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
320eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
321eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
322eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
3238df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
324eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
325572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            @Override
326572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
327d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setActualHeight((int) animation.getAnimatedValue(),
328d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                        false /* notifyListeners */);
329572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
330572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        });
331eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
33260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
333eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setDuration(newDuration);
3348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
3358efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
3368efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
337eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
338eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
339eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
340eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
341eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
342eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_HEIGHT, null);
3438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, null);
344eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_HEIGHT, null);
345eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
346eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
3478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
348eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
3498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
3508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_HEIGHT, newEndValue);
351eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
352eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
353708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void startInsetAnimation(final ExpandableView child,
35460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            StackScrollState.ViewState viewState, long duration, long delay) {
355708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
356708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
357708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int newEndValue = viewState.clipTopAmount;
358708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
359708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            return;
360708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
361708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
362708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (!mAnimationFilter.animateTopInset) {
363708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // just a local update was performed
364708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (previousAnimator != null) {
365708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // we need to increase all animation keyframes of the previous animator by the
366708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // relative change to the end value
367708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
368708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int relativeDiff = newEndValue - previousEndValue;
369708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int newStartValue = previousStartValue + relativeDiff;
370708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                values[0].setIntValues(newStartValue, newEndValue);
371708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, newStartValue);
372708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, newEndValue);
373708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
374708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
375708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            } else {
376708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // no new animation needed, let's just apply the value
377708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount(newEndValue);
378708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
379708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
380708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
381708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
382708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
383708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
384708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
385708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
386708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount((int) animation.getAnimatedValue());
387708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
388708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
389708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
39060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
391708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setDuration(newDuration);
392708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
393708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            animator.setStartDelay(delay);
394708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
395708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
396708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // remove the tag when the animation is finished
397708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
398708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
399708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationEnd(Animator animation) {
400708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
401708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, null);
402708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, null);
403708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
404708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
405708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        startAnimator(animator);
406708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
407708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
408708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_END_TOP_INSET, newEndValue);
409708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
410708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
411eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startAlphaAnimation(final ExpandableView child,
41260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            final StackScrollState.ViewState viewState, long duration, long delay) {
4138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
414eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
4158df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        final float newEndValue = viewState.alpha;
4168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
417eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
418eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
419eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
4208df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateAlpha) {
4218df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4228df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4238df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4248df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4258df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4268df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
4278df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
4288df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
4298df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, newStartValue);
4308df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_ALPHA, newEndValue);
4318df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4328df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
4338df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
4348df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
4358df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setAlpha(newEndValue);
4368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0) {
4378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    child.setVisibility(View.INVISIBLE);
4388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                }
439eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
440eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
441eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
442eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
4438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getAlpha(), newEndValue);
444eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
445eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // Handle layer type
446eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
447eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
448eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public boolean mWasCancelled;
449eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
450eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
451eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
4521efb240c1a0aeca9492cf8891794712adfdb1fa7Selim Cinek                child.setLayerType(View.LAYER_TYPE_NONE, null);
4538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0 && !mWasCancelled) {
454eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    child.setVisibility(View.INVISIBLE);
455eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
456eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_ALPHA, null);
4578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, null);
458eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_ALPHA, null);
459eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
460eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
461eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
462eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationCancel(Animator animation) {
463eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = true;
464eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
465eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
466eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
467eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationStart(Animator animation) {
468eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = false;
469eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
470eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
47160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
4723af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
476eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
477eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
478eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
479eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
480eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
481eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
482eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
483eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
485eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_ALPHA, animator);
4868df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_ALPHA, child.getAlpha());
4878df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_ALPHA, newEndValue);
488572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
489572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
490eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startZTranslationAnimation(final ExpandableView child,
49160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            final StackScrollState.ViewState viewState, long duration, long delay) {
4928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
493eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
4948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.zTranslation;
4958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
496eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
497eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
498eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
4998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateZ) {
5008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
5098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
5108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5148df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationZ(newEndValue);
515eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
516eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
517eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
518eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
5198df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationZ(), newEndValue);
520eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
52160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
5223af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
526eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
527eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
528eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
529eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
530eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
531eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
5328df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, null);
533eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Z, null);
534eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
535eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
5368efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
537eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
5388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
5398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
540eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
541eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
542eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startYTranslationAnimation(final ExpandableView child,
54360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            StackScrollState.ViewState viewState, long duration, long delay) {
5448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
545eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
5468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.yTranslation;
5478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
548eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
549eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
550eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
5518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateY) {
5528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
5618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
5628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationY(newEndValue);
5678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
568eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
569eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
570eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
571eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
5728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationY(), newEndValue);
573eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
57460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
5753af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
579eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
580eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
581eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
582eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
583eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
584eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
5858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, null);
586eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Y, null);
587eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
588eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
5898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
590eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
5918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
5928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
593eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
594eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
595d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private void startScaleAnimation(final ExpandableView child,
59660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            StackScrollState.ViewState viewState, long duration) {
5978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
598d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
5998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.scale;
6008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
601d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            return;
602d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
603d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
6048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateScale) {
6058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
6068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
6078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
6088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
6098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
6108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
6118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
6128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
6138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[1].setFloatValues(newStartValue, newEndValue);
6148df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, newStartValue);
6158df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_SCALE, newEndValue);
6168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
6178df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
6188df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
6198df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
6208df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleX(newEndValue);
6218df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleY(newEndValue);
622d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
623d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
624d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
625d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderX =
6268df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
627d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderY =
6288df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
629d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
630d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setInterpolator(mFastOutSlowInInterpolator);
63160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
632d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setDuration(newDuration);
633d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(getGlobalAnimationFinishedListener());
634d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // remove the tag when the animation is finished
635d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(new AnimatorListenerAdapter() {
636d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            @Override
637d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            public void onAnimationEnd(Animator animation) {
638d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_ANIMATOR_SCALE, null);
6398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, null);
640d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_END_SCALE, null);
641d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
642d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        });
6438efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
644d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setTag(TAG_ANIMATOR_SCALE, animator);
6458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_SCALE, child.getScaleX());
6468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_SCALE, newEndValue);
647d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
648d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
6498efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimator(ValueAnimator animator) {
6508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mAnimatorSet.add(animator);
6513af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.start();
6523af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
6533af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6543af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    /**
6553af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     * @return an adapter which ensures that onAnimationFinished is called once no animation is
6563af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     *         running anymore
6573af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     */
6583af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
6593af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        if (!mAnimationListenerPool.empty()) {
6603af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            return mAnimationListenerPool.pop();
6613af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        }
6623af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6633af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        // We need to create a new one, no reusable ones found
6643af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return new AnimatorListenerAdapter() {
6653af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            private boolean mWasCancelled;
6663af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6673af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6683af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationEnd(Animator animation) {
6693af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimatorSet.remove(animation);
6703af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
6713af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                    onAnimationFinished();
6723af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                }
6733af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimationListenerPool.push(this);
6743af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6753af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6763af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6773af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationCancel(Animator animation) {
6783af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = true;
6793af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6803af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6813af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6823af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationStart(Animator animation) {
6833af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = false;
6843af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6853af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        };
6863af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
6873af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
688eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private <T> T getChildTag(View child, int tag) {
689eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return (T) child.getTag(tag);
690eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
691eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
692eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
693eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Cancel the previous animator and get the duration of the new animation.
69439610545f0c2714a3526bc935effe57b421542d1Selim Cinek     *
69560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi     * @param duration the new duration
696eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param previousAnimator the animator which was running before
697eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @return the new duration
698eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
69960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
70060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = duration;
701eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (previousAnimator != null) {
7028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // We take either the desired length of the new animation or the remaining time of
7038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // the previous animator, whichever is longer.
7048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            newDuration = Math.max(previousAnimator.getDuration()
7058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    - previousAnimator.getCurrentPlayTime(), newDuration);
7063af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            previousAnimator.cancel();
707eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
7083af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return newDuration;
709eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
710eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
711eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void onAnimationFinished() {
712eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        mHostLayout.onChildAnimationFinished();
713eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
714eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
715eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
716eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Process the animationEvents for a new animation
717eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     *
718eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param animationEvents the animation events for the animation to perform
71939610545f0c2714a3526bc935effe57b421542d1Selim Cinek     * @param finalState the final state to animate to
72039610545f0c2714a3526bc935effe57b421542d1Selim Cinek     */
721eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void processAnimationEvents(
7220dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
723572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
7248d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
7258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final ExpandableView changingView = (ExpandableView) event.changingView;
7268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (event.animationType ==
7278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
7288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // This item is added, initialize it's properties.
7308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
7318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(changingView);
7328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState == null) {
7338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The position for this child was never generated, let's continue.
7348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
7358efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7368efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
7378efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The view was set to gone but the state never removed
7388efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    finalState.removeViewStateForView(changingView);
7398efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
74039610545f0c2714a3526bc935effe57b421542d1Selim Cinek                }
7418efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setAlpha(viewState.alpha);
7428efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationY(viewState.yTranslation);
7438efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationZ(viewState.zTranslation);
7448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setActualHeight(viewState.height, false);
7458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                mNewAddChildren.add(changingView);
7468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            } else if (event.animationType ==
7488efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
7498efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
75095ed59283bd25fb363d13c000a7408bcafb5e93eSelim Cinek                    mHostLayout.getOverlay().remove(changingView);
7518efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
7528efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7538efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7548efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // Find the amount to translate up. This is needed in order to understand the
7558efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // direction of the remove animation (either downwards or upwards)
7568efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
7578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(event.viewAfterChangingView);
7588efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                int actualHeight = changingView.getActualHeight();
7598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // upwards by default
7608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                float translationDirection = -1.0f;
7618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState != null) {
7628efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // there was a view after this one, Approximate the distance the next child
7638efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // travelled
7648efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = ((viewState.yTranslation
7658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
7668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            actualHeight);
7678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
7688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
77060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
77160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                        translationDirection, new Runnable() {
7728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    @Override
7738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    public void run() {
7748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // remove the temporary overlay
7758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        mHostLayout.getOverlay().remove(changingView);
7768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
7778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                });
778572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
7798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            mNewEvents.add(event);
780572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
781572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
7828d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
783475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi    public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
784475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi            final boolean isRubberbanded) {
7858d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
7869012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        if (targetAmount == startOverScrollAmount) {
7879012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi            return;
7889012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        }
7898d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        cancelOverScrollAnimators(onTop);
7908d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
7918d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                targetAmount);
7928d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
7938d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
7948d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            @Override
7958d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
7968d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                float currentOverScroll = (float) animation.getAnimatedValue();
79747c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                mHostLayout.setOverScrollAmount(
798475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
799475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        isRubberbanded);
8008d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            }
8018d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        });
8028d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
80347c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
80447c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            @Override
80547c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            public void onAnimationEnd(Animator animation) {
80647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                if (onTop) {
80747c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mTopOverScrollAnimator = null;
80847c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                } else {
80947c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mBottomOverScrollAnimator = null;
81047c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                }
81147c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            }
81247c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        });
8138d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.start();
8148d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (onTop) {
8158d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mTopOverScrollAnimator = overScrollAnimator;
8168d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        } else {
8178d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mBottomOverScrollAnimator = overScrollAnimator;
8188d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
8198d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
8208d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
8218d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void cancelOverScrollAnimators(boolean onTop) {
8228d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
8238d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (currentAnimator != null) {
8248d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            currentAnimator.cancel();
8258d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
8268d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
827572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek}
828