StackStateAnimator.java revision b5605e58cb8080c8c887b1885336b707596c8094
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;
29b5605e58cb8080c8c887b1885336b707596c8094Selim Cinekimport com.android.systemui.statusbar.ExpandableNotificationRow;
30572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport com.android.systemui.statusbar.ExpandableView;
318b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggiimport com.android.systemui.statusbar.SpeedBumpView;
32572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
33572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport java.util.ArrayList;
34eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.HashSet;
35eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Set;
36eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Stack;
37572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
38572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek/**
39572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * An stack state animator which handles animations to new StackScrollStates
40572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek */
41572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekpublic class StackStateAnimator {
42572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
435aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_STANDARD = 360;
4460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
46b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
475aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
488efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
49b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
5160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
524e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi    public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
53b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
54b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
555aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi
56eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
57eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
58d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
59eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
60eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
61eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
62eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
63eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
64d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
65eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
66eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
67eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
698df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
74572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
75572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    private final Interpolator mFastOutSlowInInterpolator;
7660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private final int mGoToFullShadeAppearingTranslation;
77572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public NotificationStackScrollLayout mHostLayout;
78eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
79eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            new ArrayList<>();
808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private ArrayList<View> mNewAddChildren = new ArrayList<>();
8160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private Set<Animator> mAnimatorSet = new HashSet<>();
8260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
83d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private AnimationFilter mAnimationFilter = new AnimationFilter();
845aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    private long mCurrentLength;
85dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi    private long mCurrentAdditionalDelay;
86572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
8760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    /** The current index for the last child which was not added in this event set. */
8860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private int mCurrentLastNotAddedIndex;
8960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
908d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mTopOverScrollAnimator;
918d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mBottomOverScrollAnimator;
92b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    private ExpandableNotificationRow mChildExpandingView;
938d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
94572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
95572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mHostLayout = hostLayout;
96572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
97eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                android.R.interpolator.fast_out_slow_in);
9860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        mGoToFullShadeAppearingTranslation =
9960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                hostLayout.getContext().getResources().getDimensionPixelSize(
10060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                        R.dimen.go_to_full_shade_appearing_translation);
101572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
102572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
103572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public boolean isRunning() {
104eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return !mAnimatorSet.isEmpty();
105572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
106572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
107572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public void startAnimationForEvents(
1080dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
109dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi            StackScrollState finalState, long additionalDelay) {
110eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
111eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        processAnimationEvents(mAnimationEvents, finalState);
112eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
113572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        int childCount = mHostLayout.getChildCount();
114d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        mAnimationFilter.applyCombination(mNewEvents);
115dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi        mCurrentAdditionalDelay = additionalDelay;
1165aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
11760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
118572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        for (int i = 0; i < childCount; i++) {
119572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
1208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
121b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState = finalState.getViewStateForView(child);
1228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (viewState == null || child.getVisibility() == View.GONE) {
123572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek                continue;
124572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
125572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
126a272dfed9a4f31d8245099c0d99a73e79b90670cSelim Cinek            child.setClipTopOptimization(0);
127b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
128572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
129eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (!isRunning()) {
130eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            // no child has preformed any animation, lets finish
131eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            onAnimationFinished();
132eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewEvents.clear();
1348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewAddChildren.clear();
135b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek        mChildExpandingView = null;
136572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
137572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
13860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private int findLastNotAddedIndex(StackScrollState finalState) {
13960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        int childCount = mHostLayout.getChildCount();
14060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        for (int i = childCount - 1; i >= 0; i--) {
14160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
14260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
143b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState = finalState.getViewStateForView(child);
14460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            if (viewState == null || child.getVisibility() == View.GONE) {
14560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                continue;
14660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            }
14760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            if (!mNewAddChildren.contains(child)) {
14860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                return viewState.notGoneIndex;
14960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            }
15060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
15160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        return -1;
15260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    }
15360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
154b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
155eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
156b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * Start an animation to the given  {@link StackViewState}.
157b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     *
158b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param child the child to start the animation on
159b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param viewState the {@link StackViewState} of the view to animate to
160b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param finalState the final state after the animation
161b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param i the index of the view; only relevant if the view is the speed bump and is
162b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     *          ignored otherwise
163b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
164eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
165b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    public void startStackAnimations(final ExpandableView child, StackViewState viewState,
166b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackScrollState finalState, int i, long fixedDelay) {
167eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        final float alpha = viewState.alpha;
168b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean wasAdded = mNewAddChildren.contains(child);
169b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        long duration = mCurrentLength;
170b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
171b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
172b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
173b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
174b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
175b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                    (long) (100 * longerDurationFactor);
176eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
1788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
1798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean scaleChanging = child.getScaleX() != viewState.scale;
1808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean alphaChanging = alpha != child.getAlpha();
1818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean heightChanging = viewState.height != child.getActualHeight();
1824e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi        boolean darkChanging = viewState.dark != child.isDark();
183708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
1848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean hasDelays = mAnimationFilter.hasDelays;
1858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
1864e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi                alphaChanging || heightChanging || topInsetChanging || darkChanging;
1878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long delay = 0;
188b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (fixedDelay != -1) {
189b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            delay = fixedDelay;
190b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        } else if (hasDelays && isDelayRelevant || wasAdded) {
191dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi            delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
1928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
1938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
194b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        startViewAnimations(child, viewState, delay, duration);
1958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
196eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start height animation
197d2319fbe6a53ac4c38ca02e4d8e32da86ed0994bSelim Cinek        if (heightChanging && child.getActualHeight() != 0) {
198b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startHeightAnimation(child, viewState, duration, delay);
19959b5a356b828fe60ea2874b0680a1bf7c84809a1Jorim Jaggi        }
2008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
201708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // start top inset animation
202708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (topInsetChanging) {
203b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startInsetAnimation(child, viewState, duration, delay);
204708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
205708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
206d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // start dimmed animation
207b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
2088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
209bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        // start dark animation
210b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
211bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock
2123d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        // apply speed bump state
2133d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        child.setBelowSpeedBump(viewState.belowSpeedBump);
2143d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek
215ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi        // start hiding sensitive animation
216b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
217b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                delay, duration);
218ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi
2198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (wasAdded) {
22060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            child.performAddAnimation(delay, mCurrentLength);
2218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2228b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi        if (child instanceof SpeedBumpView) {
2238b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi            finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
2248b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi                    delay + duration);
225b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek        } else if (child instanceof ExpandableNotificationRow) {
226b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
227b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
228b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                    duration);
2298b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi        }
2308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    }
2318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
232b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    /**
233b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * Start an animation to a new {@link ViewState}.
234b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     *
235b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param child the child to start the animation on
236b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param viewState the  {@link StackViewState} of the view to animate to
237b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param delay a fixed delay
238b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param duration the duration of the animation
239b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     */
240b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
241b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean wasVisible = child.getVisibility() == View.VISIBLE;
242b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        final float alpha = viewState.alpha;
243b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (!wasVisible && alpha != 0 && !viewState.gone) {
244b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            child.setVisibility(View.VISIBLE);
245b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
246b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
247b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
248b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean scaleChanging = child.getScaleX() != viewState.scale;
249b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha();
250b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean alphaChanging = viewState.alpha != childAlpha;
251b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
252b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start translationY animation
253b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (yTranslationChanging) {
254b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startYTranslationAnimation(child, viewState, duration, delay);
255b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
256b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
257b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start translationZ animation
258b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (zTranslationChanging) {
259b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startZTranslationAnimation(child, viewState, duration, delay);
260b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
261b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
262b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start scale animation
263b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (scaleChanging) {
264b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startScaleAnimation(child, viewState, duration);
265b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
266b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
267b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start alpha animation
268b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (alphaChanging && child.getTranslationX() == 0) {
269b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startAlphaAnimation(child, viewState, duration, delay);
270b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
271b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    }
272b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
273b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private long calculateChildAnimationDelay(StackViewState viewState,
2748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
2754e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi        if (mAnimationFilter.hasDarkEvent) {
2764e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi            return calculateDelayDark(viewState);
2774e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi        }
27860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        if (mAnimationFilter.hasGoToFullShadeEvent) {
27960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            return calculateDelayGoToFullShade(viewState);
28060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
2818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long minDelay = 0;
2828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
2838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
2848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            switch (event.animationType) {
2858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
2868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int changingIndex = finalState
2888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(event.changingView).notGoneIndex;
2898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - changingIndex);
2908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
2938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
2978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
2988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
2998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
3008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    boolean noNextView = event.viewAfterChangingView == null;
3018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    View viewAfterChangingView = noNextView
3028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            ? mHostLayout.getLastChildNotGone()
3038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            : event.viewAfterChangingView;
3048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
3058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int nextIndex = finalState
3068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
3078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    if (ownIndex >= nextIndex) {
3088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // we only have the view afterwards
3098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        ownIndex++;
3108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
3118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - nextIndex);
3128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
3138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
3148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = difference * delayPerElement;
3158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
3168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
3178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
3188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                default:
3198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
3208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            }
3218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
3228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        return minDelay;
323572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
324572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
325b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private long calculateDelayDark(StackViewState viewState) {
3262a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        int referenceIndex;
3272a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        if (mAnimationFilter.darkAnimationOriginIndex ==
3282a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
3292a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi            referenceIndex = 0;
3302a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        } else if (mAnimationFilter.darkAnimationOriginIndex ==
3312a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
3322a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi            referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
3332a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        } else {
3342a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi            referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
3352a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        }
3362a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
3374e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi    }
3384e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi
339b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private long calculateDelayGoToFullShade(StackViewState viewState) {
34060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        float index = viewState.notGoneIndex;
34160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        index = (float) Math.pow(index, 0.7f);
34260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
34360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    }
34460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
345eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startHeightAnimation(final ExpandableView child,
346b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState, long duration, long delay) {
3478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
348d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
3498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        int newEndValue = viewState.height;
3508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
351eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
352eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
353eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
3548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateHeight) {
3558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
3568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
3578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
3588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
3598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
3608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int relativeDiff = newEndValue - previousEndValue;
3618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int newStartValue = previousStartValue + relativeDiff;
3628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setIntValues(newStartValue, newEndValue);
3638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, newStartValue);
3648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_HEIGHT, newEndValue);
3658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
3668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
3678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
3688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
3698df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setActualHeight(newEndValue, false);
3708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
371eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
372eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
373eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
3748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
375eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
376572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            @Override
377572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
378d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setActualHeight((int) animation.getAnimatedValue(),
379d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                        false /* notifyListeners */);
380572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
381572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        });
382eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
38360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
384eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setDuration(newDuration);
3858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
3868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
3878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
388eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
389eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
390eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
391eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
392eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
393eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_HEIGHT, null);
3948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, null);
395eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_HEIGHT, null);
396eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
397eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
3988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
399eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
4008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
4018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_HEIGHT, newEndValue);
402eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
403eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
404708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void startInsetAnimation(final ExpandableView child,
405b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState, long duration, long delay) {
406708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
407708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
408708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int newEndValue = viewState.clipTopAmount;
409708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
410708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            return;
411708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
412708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
413708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (!mAnimationFilter.animateTopInset) {
414708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // just a local update was performed
415708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (previousAnimator != null) {
416708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // we need to increase all animation keyframes of the previous animator by the
417708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // relative change to the end value
418708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
419708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int relativeDiff = newEndValue - previousEndValue;
420708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int newStartValue = previousStartValue + relativeDiff;
421708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                values[0].setIntValues(newStartValue, newEndValue);
422708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, newStartValue);
423708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, newEndValue);
424708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
425708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
426708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            } else {
427708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // no new animation needed, let's just apply the value
428708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount(newEndValue);
429708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
430708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
431708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
432708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
433708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
434708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
435708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
436708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
437708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount((int) animation.getAnimatedValue());
438708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
439708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
440708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
44160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
442708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setDuration(newDuration);
443708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
444708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            animator.setStartDelay(delay);
445708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
446708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
447708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // remove the tag when the animation is finished
448708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
449708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
450708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationEnd(Animator animation) {
451708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
452708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, null);
453708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, null);
454708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
455708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
456708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        startAnimator(animator);
457708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
458708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
459708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_END_TOP_INSET, newEndValue);
460708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
461708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
462b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startAlphaAnimation(final View child,
463b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            final ViewState viewState, long duration, long delay) {
4648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
465eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
4668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        final float newEndValue = viewState.alpha;
4678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
468eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
469eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
470eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
4718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateAlpha) {
4728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4768df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4778df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
4788df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
4798df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
4808df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, newStartValue);
4818df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_ALPHA, newEndValue);
4828df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4838df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
4848df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
4858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
4868df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setAlpha(newEndValue);
4878df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0) {
4888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    child.setVisibility(View.INVISIBLE);
4898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                }
490eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
491eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
492eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
493eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
4948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getAlpha(), newEndValue);
495eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
496eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // Handle layer type
497eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
498eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
499eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public boolean mWasCancelled;
500eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
501eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
502eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
5031efb240c1a0aeca9492cf8891794712adfdb1fa7Selim Cinek                child.setLayerType(View.LAYER_TYPE_NONE, null);
5048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0 && !mWasCancelled) {
505eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    child.setVisibility(View.INVISIBLE);
506eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
507c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                // remove the tag when the animation is finished
508eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_ALPHA, null);
5098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, null);
510eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_ALPHA, null);
511eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
512eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
513eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
514eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationCancel(Animator animation) {
515eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = true;
516eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
517eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
518eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
519eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationStart(Animator animation) {
520eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = false;
521eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
522eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
52360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
5243af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
528eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
529eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
5308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
531eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_ALPHA, animator);
5328df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_ALPHA, child.getAlpha());
5338df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_ALPHA, newEndValue);
534572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
535572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
536b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startZTranslationAnimation(final View child,
537b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            final ViewState viewState, long duration, long delay) {
5388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
539eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
5408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.zTranslation;
5418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
542eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
543eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
544eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
5458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateZ) {
5468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
5558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
5568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationZ(newEndValue);
561eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
562eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
563eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
564eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
5658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationZ(), newEndValue);
566eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
56760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
5683af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
572eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
573eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
574eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
575eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
576eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
577eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
5788df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, null);
579eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Z, null);
580eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
581eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
5828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
583eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
5848df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
5858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
586eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
587eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
588b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startYTranslationAnimation(final View child,
589b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            ViewState viewState, long duration, long delay) {
5908df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
591eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
5928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.yTranslation;
5938df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
594eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
595eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
596eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
5978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateY) {
5988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
6008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
6018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
6028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
6038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
6048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
6058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
6068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
6078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
6088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
6098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
6108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
6118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
6128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationY(newEndValue);
6138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
614eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
615eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
616eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
617eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
6188df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationY(), newEndValue);
619eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
62060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
6213af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
6228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
6238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
6248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
625eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
626eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
627eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
628eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
629eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
630eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
6318df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, null);
632eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Y, null);
633eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
634eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
6358efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
636eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
6378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
6388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
639eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
640eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
641b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startScaleAnimation(final View child,
642b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            ViewState viewState, long duration) {
6438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
644d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
6458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.scale;
6468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
647d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            return;
648d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
649d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
6508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateScale) {
6518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
6528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
6538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
6548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
6558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
6568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
6578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
6588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
6598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[1].setFloatValues(newStartValue, newEndValue);
6608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, newStartValue);
6618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_SCALE, newEndValue);
6628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
6638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
6648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
6658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
6668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleX(newEndValue);
6678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleY(newEndValue);
668d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
669d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
670d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
671d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderX =
6728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
673d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderY =
6748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
675d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
676d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setInterpolator(mFastOutSlowInInterpolator);
67760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
678d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setDuration(newDuration);
679d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(getGlobalAnimationFinishedListener());
680d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // remove the tag when the animation is finished
681d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(new AnimatorListenerAdapter() {
682d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            @Override
683d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            public void onAnimationEnd(Animator animation) {
684d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_ANIMATOR_SCALE, null);
6858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, null);
686d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_END_SCALE, null);
687d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
688d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        });
6898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
690d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setTag(TAG_ANIMATOR_SCALE, animator);
6918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_SCALE, child.getScaleX());
6928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_SCALE, newEndValue);
693d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
694d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
6958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimator(ValueAnimator animator) {
6968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mAnimatorSet.add(animator);
6973af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.start();
6983af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
6993af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7003af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    /**
7013af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     * @return an adapter which ensures that onAnimationFinished is called once no animation is
7023af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     *         running anymore
7033af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     */
7043af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
7053af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        if (!mAnimationListenerPool.empty()) {
7063af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            return mAnimationListenerPool.pop();
7073af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        }
7083af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7093af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        // We need to create a new one, no reusable ones found
7103af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return new AnimatorListenerAdapter() {
7113af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            private boolean mWasCancelled;
7123af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7133af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
7143af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationEnd(Animator animation) {
7153af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimatorSet.remove(animation);
7163af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
7173af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                    onAnimationFinished();
7183af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                }
7193af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimationListenerPool.push(this);
7203af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
7213af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7223af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
7233af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationCancel(Animator animation) {
7243af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = true;
7253af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
7263af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7273af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
7283af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationStart(Animator animation) {
7293af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = false;
7303af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
7313af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        };
7323af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
7333af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
73402af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    private static <T> T getChildTag(View child, int tag) {
735eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return (T) child.getTag(tag);
736eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
737eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
738eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
739eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Cancel the previous animator and get the duration of the new animation.
74039610545f0c2714a3526bc935effe57b421542d1Selim Cinek     *
74160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi     * @param duration the new duration
742eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param previousAnimator the animator which was running before
743eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @return the new duration
744eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
74560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
74660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = duration;
747eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (previousAnimator != null) {
7488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // We take either the desired length of the new animation or the remaining time of
7498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // the previous animator, whichever is longer.
7508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            newDuration = Math.max(previousAnimator.getDuration()
7518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    - previousAnimator.getCurrentPlayTime(), newDuration);
7523af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            previousAnimator.cancel();
753eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
7543af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return newDuration;
755eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
756eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
757eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void onAnimationFinished() {
758eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        mHostLayout.onChildAnimationFinished();
759eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
760eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
761eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
762eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Process the animationEvents for a new animation
763eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     *
764eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param animationEvents the animation events for the animation to perform
76539610545f0c2714a3526bc935effe57b421542d1Selim Cinek     * @param finalState the final state to animate to
76639610545f0c2714a3526bc935effe57b421542d1Selim Cinek     */
767eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void processAnimationEvents(
7680dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
769572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
7708d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
7718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final ExpandableView changingView = (ExpandableView) event.changingView;
7728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (event.animationType ==
7738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
7748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // This item is added, initialize it's properties.
776b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                StackViewState viewState = finalState
7778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(changingView);
7788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState == null) {
7798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The position for this child was never generated, let's continue.
7808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
7818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
7838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The view was set to gone but the state never removed
7848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    finalState.removeViewStateForView(changingView);
7858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
78639610545f0c2714a3526bc935effe57b421542d1Selim Cinek                }
787b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                finalState.applyState(changingView, viewState);
7888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                mNewAddChildren.add(changingView);
7898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            } else if (event.animationType ==
7918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
7928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
79395ed59283bd25fb363d13c000a7408bcafb5e93eSelim Cinek                    mHostLayout.getOverlay().remove(changingView);
7948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
7958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // Find the amount to translate up. This is needed in order to understand the
7988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // direction of the remove animation (either downwards or upwards)
799b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                StackViewState viewState = finalState
8008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(event.viewAfterChangingView);
8018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                int actualHeight = changingView.getActualHeight();
8028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // upwards by default
8038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                float translationDirection = -1.0f;
8048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState != null) {
8058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // there was a view after this one, Approximate the distance the next child
8068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // travelled
8078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = ((viewState.yTranslation
8088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
8098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            actualHeight);
8108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
8118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
8128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
81360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
81460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                        translationDirection, new Runnable() {
8158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    @Override
8168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    public void run() {
8178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // remove the temporary overlay
8188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        mHostLayout.getOverlay().remove(changingView);
8198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
8208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                });
821b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            } else if (event.animationType ==
822f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
823f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                // A race condition can trigger the view to be added to the overlay even though
824f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                // it is swiped out. So let's remove it
825f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                mHostLayout.getOverlay().remove(changingView);
826b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            } else if (event.animationType == NotificationStackScrollLayout
827b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                    .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
828b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
829b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                row.prepareExpansionChanged(finalState);
830b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                mChildExpandingView = row;
831572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
8328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            mNewEvents.add(event);
833572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
834572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
8358d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
836475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi    public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
837475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi            final boolean isRubberbanded) {
8388d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
8399012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        if (targetAmount == startOverScrollAmount) {
8409012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi            return;
8419012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        }
8428d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        cancelOverScrollAnimators(onTop);
8438d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
8448d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                targetAmount);
8458d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
8468d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
8478d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            @Override
8488d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
8498d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                float currentOverScroll = (float) animation.getAnimatedValue();
85047c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                mHostLayout.setOverScrollAmount(
851475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
852475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        isRubberbanded);
8538d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            }
8548d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        });
8558d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
85647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
85747c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            @Override
85847c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            public void onAnimationEnd(Animator animation) {
85947c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                if (onTop) {
86047c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mTopOverScrollAnimator = null;
86147c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                } else {
86247c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mBottomOverScrollAnimator = null;
86347c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                }
86447c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            }
86547c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        });
8668d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.start();
8678d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (onTop) {
8688d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mTopOverScrollAnimator = overScrollAnimator;
8698d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        } else {
8708d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mBottomOverScrollAnimator = overScrollAnimator;
8718d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
8728d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
8738d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
8748d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void cancelOverScrollAnimators(boolean onTop) {
8758d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
8768d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (currentAnimator != null) {
8778d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            currentAnimator.cancel();
8788d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
8798d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
88002af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek
88102af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    /**
88202af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek     * Get the end value of the height animation running on a view or the actualHeight
88302af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek     * if no animation is running.
88402af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek     */
88502af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    public static int getFinalActualHeight(ExpandableView view) {
88602af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        if (view == null) {
88702af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            return 0;
88802af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        }
88902af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
89002af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        if (heightAnimator == null) {
89102af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            return view.getActualHeight();
89202af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        } else {
89302af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            return getChildTag(view, TAG_END_HEIGHT);
89402af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        }
89502af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    }
896572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek}
897