StackStateAnimator.java revision 3d2b94bf8e32640e57573ebb17911b1db9440231
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;
30572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
31572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport java.util.ArrayList;
32eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.HashSet;
33eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Set;
34eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Stack;
35572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
36572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek/**
37572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * An stack state animator which handles animations to new StackScrollStates
38572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek */
39572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekpublic class StackStateAnimator {
40572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
415aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_STANDARD = 360;
428efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
435aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
475aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi
48eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
49eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
50d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
51eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
52eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
53eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
54eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
55eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
56d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
57eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
58eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
59eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
66572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
67572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    private final Interpolator mFastOutSlowInInterpolator;
68572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public NotificationStackScrollLayout mHostLayout;
69eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
70eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            new ArrayList<>();
718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private ArrayList<View> mNewAddChildren = new ArrayList<>();
72eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private Set<Animator> mAnimatorSet = new HashSet<Animator>();
73eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private Stack<AnimatorListenerAdapter> mAnimationListenerPool
74eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            = new Stack<AnimatorListenerAdapter>();
75d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private AnimationFilter mAnimationFilter = new AnimationFilter();
765aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    private long mCurrentLength;
77572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
788d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mTopOverScrollAnimator;
798d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mBottomOverScrollAnimator;
808d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
81572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
82572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mHostLayout = hostLayout;
83572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
84eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                android.R.interpolator.fast_out_slow_in);
85572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
86572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
87572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public boolean isRunning() {
88eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return !mAnimatorSet.isEmpty();
89572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
90572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
91572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public void startAnimationForEvents(
920dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
93572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
94eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
95eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        processAnimationEvents(mAnimationEvents, finalState);
96eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
97572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        int childCount = mHostLayout.getChildCount();
98d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        mAnimationFilter.applyCombination(mNewEvents);
995aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
100572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        for (int i = 0; i < childCount; i++) {
101572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
1028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
103572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
1048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (viewState == null || child.getVisibility() == View.GONE) {
105572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek                continue;
106572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
107572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
108572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            child.setClipBounds(null);
1098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            startAnimations(child, viewState, finalState);
110572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
111eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (!isRunning()) {
112eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            // no child has preformed any animation, lets finish
113eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            onAnimationFinished();
114eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewEvents.clear();
1168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewAddChildren.clear();
117572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
118572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
119eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
120eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Start an animation to the given viewState
121eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
1228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
1238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
124eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        int childVisibility = child.getVisibility();
125eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        boolean wasVisible = childVisibility == View.VISIBLE;
126eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        final float alpha = viewState.alpha;
127eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (!wasVisible && alpha != 0 && !viewState.gone) {
128eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            child.setVisibility(View.VISIBLE);
129eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
1318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
1328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
1338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean scaleChanging = child.getScaleX() != viewState.scale;
1348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean alphaChanging = alpha != child.getAlpha();
1358efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean heightChanging = viewState.height != child.getActualHeight();
136708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
1378efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean wasAdded = mNewAddChildren.contains(child);
1388efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean hasDelays = mAnimationFilter.hasDelays;
1398efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
140708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                alphaChanging || heightChanging || topInsetChanging;
1418efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long delay = 0;
1428efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (hasDelays && isDelayRelevant || wasAdded) {
1438efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            delay = calculateChildAnimationDelay(viewState, finalState);
1448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
1458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
146eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start translationY animation
1478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (yTranslationChanging) {
1488efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            startYTranslationAnimation(child, viewState, delay);
149eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
151eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start translationZ animation
1528efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (zTranslationChanging) {
1538efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            startZTranslationAnimation(child, viewState, delay);
154d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
1558efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
156d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // start scale animation
1578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (scaleChanging) {
158d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            startScaleAnimation(child, viewState);
159eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
161eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start alpha animation
1628efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (alphaChanging) {
1638efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            startAlphaAnimation(child, viewState, delay);
164eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
1658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
166eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start height animation
1678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (heightChanging) {
1688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            startHeightAnimation(child, viewState, delay);
16959b5a356b828fe60ea2874b0680a1bf7c84809a1Jorim Jaggi        }
1708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
171708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // start top inset animation
172708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (topInsetChanging) {
173708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            startInsetAnimation(child, viewState, delay);
174708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
175708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
176d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // start dimmed animation
177fa129db84a2985f81eac4c51204180b00122b632Selim Cinek        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded);
1788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
179bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        // start dark animation
180bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        child.setDark(viewState.dark, mAnimationFilter.animateDark);
181bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock
1823d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        // apply speed bump state
1833d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        child.setBelowSpeedBump(viewState.belowSpeedBump);
1843d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek
185f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek        // apply scrimming
186f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek        child.setScrimAmount(viewState.scrimAmount);
187f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek
1888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (wasAdded) {
1898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            child.performAddAnimation(delay);
1908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
1918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    }
1928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
1938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
1948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
1958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long minDelay = 0;
1968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
1978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
1988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            switch (event.animationType) {
1998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
2008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int changingIndex = finalState
2028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(event.changingView).notGoneIndex;
2038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - changingIndex);
2048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
2078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
2118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
2128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
2138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    boolean noNextView = event.viewAfterChangingView == null;
2158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    View viewAfterChangingView = noNextView
2168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            ? mHostLayout.getLastChildNotGone()
2178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            : event.viewAfterChangingView;
2188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
2198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int nextIndex = finalState
2208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
2218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    if (ownIndex >= nextIndex) {
2228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // we only have the view afterwards
2238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        ownIndex++;
2248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
2258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - nextIndex);
2268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = difference * delayPerElement;
2298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                default:
2338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            }
2358efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2368efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        return minDelay;
237572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
238572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
239eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startHeightAnimation(final ExpandableView child,
2408efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState.ViewState viewState, long delay) {
2418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
242d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
2438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        int newEndValue = viewState.height;
2448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
245eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
246eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
247eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
2488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateHeight) {
2498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
2508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
2518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
2528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
2538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
2548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int relativeDiff = newEndValue - previousEndValue;
2558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int newStartValue = previousStartValue + relativeDiff;
2568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setIntValues(newStartValue, newEndValue);
2578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, newStartValue);
2588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_HEIGHT, newEndValue);
2598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
2618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
2628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
2638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setActualHeight(newEndValue, false);
2648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
265eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
266eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
267eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
2688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
269eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
270572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            @Override
271572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
272d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setActualHeight((int) animation.getAnimatedValue(),
273d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                        false /* notifyListeners */);
274572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
275572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        });
276eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
2778df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
278eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setDuration(newDuration);
2798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
2808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
2818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
282eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
283eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
284eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
285eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
286eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
287eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_HEIGHT, null);
2888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, null);
289eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_HEIGHT, null);
290eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
291eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
2928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
293eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
2948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
2958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_HEIGHT, newEndValue);
296eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
297eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
298708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void startInsetAnimation(final ExpandableView child,
299708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            StackScrollState.ViewState viewState, long delay) {
300708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
301708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
302708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int newEndValue = viewState.clipTopAmount;
303708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
304708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            return;
305708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
306708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
307708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (!mAnimationFilter.animateTopInset) {
308708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // just a local update was performed
309708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (previousAnimator != null) {
310708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // we need to increase all animation keyframes of the previous animator by the
311708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // relative change to the end value
312708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
313708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int relativeDiff = newEndValue - previousEndValue;
314708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int newStartValue = previousStartValue + relativeDiff;
315708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                values[0].setIntValues(newStartValue, newEndValue);
316708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, newStartValue);
317708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, newEndValue);
318708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
319708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
320708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            } else {
321708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // no new animation needed, let's just apply the value
322708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount(newEndValue);
323708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
324708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
325708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
326708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
327708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
328708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
329708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
330708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
331708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount((int) animation.getAnimatedValue());
332708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
333708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
334708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
335708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
336708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setDuration(newDuration);
337708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
338708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            animator.setStartDelay(delay);
339708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
340708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
341708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // remove the tag when the animation is finished
342708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
343708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
344708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationEnd(Animator animation) {
345708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
346708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, null);
347708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, null);
348708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
349708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
350708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        startAnimator(animator);
351708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
352708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
353708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_END_TOP_INSET, newEndValue);
354708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
355708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
356eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startAlphaAnimation(final ExpandableView child,
3578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final StackScrollState.ViewState viewState, long delay) {
3588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
359eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
3608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        final float newEndValue = viewState.alpha;
3618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
362eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
363eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
364eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
3658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateAlpha) {
3668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
3678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
3688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
3698df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
3708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
3718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
3728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
3738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
3748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, newStartValue);
3758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_ALPHA, newEndValue);
3768df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
3778df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
3788df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
3798df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
3808df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setAlpha(newEndValue);
3818df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0) {
3828df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    child.setVisibility(View.INVISIBLE);
3838df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                }
384eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
385eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
386eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
387eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
3888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getAlpha(), newEndValue);
389eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
390eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // Handle layer type
391eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
392eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
393eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public boolean mWasCancelled;
394eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
395eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
396eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
3971efb240c1a0aeca9492cf8891794712adfdb1fa7Selim Cinek                child.setLayerType(View.LAYER_TYPE_NONE, null);
3988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0 && !mWasCancelled) {
399eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    child.setVisibility(View.INVISIBLE);
400eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
401eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_ALPHA, null);
4028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, null);
403eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_ALPHA, null);
404eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
405eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
406eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
407eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationCancel(Animator animation) {
408eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = true;
409eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
410eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
411eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
412eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationStart(Animator animation) {
413eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = false;
414eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
415eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
4173af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
421eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
422eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
423eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
424eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
425eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
426eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
427eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
428eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
430eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_ALPHA, animator);
4318df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_ALPHA, child.getAlpha());
4328df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_ALPHA, newEndValue);
433572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
434572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
435eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startZTranslationAnimation(final ExpandableView child,
4368efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final StackScrollState.ViewState viewState, long delay) {
4378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
438eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
4398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.zTranslation;
4408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
441eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
442eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
443eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
4448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateZ) {
4458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
4518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
4528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
4538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
4548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
4558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
4578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
4588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
4598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationZ(newEndValue);
460eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
461eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
462eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
463eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
4648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationZ(), newEndValue);
465eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
4668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
4673af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
471eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
472eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
473eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
474eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
475eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
476eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
4778df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, null);
478eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Z, null);
479eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
480eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
482eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
4838df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
4848df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
485eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
486eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
487eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startYTranslationAnimation(final ExpandableView child,
4888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState.ViewState viewState, long delay) {
4898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
490eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
4918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.yTranslation;
4928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
493eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
494eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
495eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
4968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateY) {
4978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
5068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
5078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationY(newEndValue);
5128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
513eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
514eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
515eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
516eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
5178df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationY(), newEndValue);
518eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
5198df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
5203af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
524eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
525eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
526eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
527eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
528eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
529eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
5308df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, null);
531eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Y, null);
532eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
533eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
5348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
535eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
5368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
5378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
538eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
539eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
540d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private void startScaleAnimation(final ExpandableView child,
541d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            StackScrollState.ViewState viewState) {
5428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
543d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
5448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.scale;
5458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
546d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            return;
547d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
548d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
5498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateScale) {
5508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[1].setFloatValues(newStartValue, newEndValue);
5598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, newStartValue);
5608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_SCALE, newEndValue);
5618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleX(newEndValue);
5668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleY(newEndValue);
567d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
568d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
569d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
570d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderX =
5718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
572d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderY =
5738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
574d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
575d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setInterpolator(mFastOutSlowInInterpolator);
5768df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
577d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setDuration(newDuration);
578d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(getGlobalAnimationFinishedListener());
579d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // remove the tag when the animation is finished
580d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(new AnimatorListenerAdapter() {
581d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            @Override
582d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            public void onAnimationEnd(Animator animation) {
583d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_ANIMATOR_SCALE, null);
5848df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, null);
585d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_END_SCALE, null);
586d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
587d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        });
5888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
589d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setTag(TAG_ANIMATOR_SCALE, animator);
5908df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_SCALE, child.getScaleX());
5918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_SCALE, newEndValue);
592d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
593d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
5948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimator(ValueAnimator animator) {
5958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mAnimatorSet.add(animator);
5963af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.start();
5973af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
5983af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
5993af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    /**
6003af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     * @return an adapter which ensures that onAnimationFinished is called once no animation is
6013af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     *         running anymore
6023af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     */
6033af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
6043af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        if (!mAnimationListenerPool.empty()) {
6053af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            return mAnimationListenerPool.pop();
6063af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        }
6073af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6083af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        // We need to create a new one, no reusable ones found
6093af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return new AnimatorListenerAdapter() {
6103af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            private boolean mWasCancelled;
6113af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6123af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6133af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationEnd(Animator animation) {
6143af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimatorSet.remove(animation);
6153af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
6163af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                    onAnimationFinished();
6173af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                }
6183af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimationListenerPool.push(this);
6193af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6203af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6213af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6223af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationCancel(Animator animation) {
6233af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = true;
6243af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6253af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6263af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6273af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationStart(Animator animation) {
6283af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = false;
6293af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6303af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        };
6313af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
6323af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
633eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private <T> T getChildTag(View child, int tag) {
634eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return (T) child.getTag(tag);
635eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
636eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
637eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
638eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Cancel the previous animator and get the duration of the new animation.
63939610545f0c2714a3526bc935effe57b421542d1Selim Cinek     *
640eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param previousAnimator the animator which was running before
641eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @return the new duration
642eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
6438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) {
6445aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        long newDuration = mCurrentLength;
645eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (previousAnimator != null) {
6468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // We take either the desired length of the new animation or the remaining time of
6478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // the previous animator, whichever is longer.
6488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            newDuration = Math.max(previousAnimator.getDuration()
6498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    - previousAnimator.getCurrentPlayTime(), newDuration);
6503af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            previousAnimator.cancel();
651eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
6523af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return newDuration;
653eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
654eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
655eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void onAnimationFinished() {
656eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        mHostLayout.onChildAnimationFinished();
657eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
658eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
659eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
660eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Process the animationEvents for a new animation
661eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     *
662eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param animationEvents the animation events for the animation to perform
66339610545f0c2714a3526bc935effe57b421542d1Selim Cinek     * @param finalState the final state to animate to
66439610545f0c2714a3526bc935effe57b421542d1Selim Cinek     */
665eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void processAnimationEvents(
6660dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
667572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
6688d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
6698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final ExpandableView changingView = (ExpandableView) event.changingView;
6708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (event.animationType ==
6718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
6728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // This item is added, initialize it's properties.
6748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
6758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(changingView);
6768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState == null) {
6778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The position for this child was never generated, let's continue.
6788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
6798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
6808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
6818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The view was set to gone but the state never removed
6828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    finalState.removeViewStateForView(changingView);
6838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
68439610545f0c2714a3526bc935effe57b421542d1Selim Cinek                }
6858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setAlpha(viewState.alpha);
6868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationY(viewState.yTranslation);
6878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationZ(viewState.zTranslation);
6888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setActualHeight(viewState.height, false);
6898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                mNewAddChildren.add(changingView);
6908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            } else if (event.animationType ==
6928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
6938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
6948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
6958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
6968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // Find the amount to translate up. This is needed in order to understand the
6988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // direction of the remove animation (either downwards or upwards)
6998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
7008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(event.viewAfterChangingView);
7018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                int actualHeight = changingView.getActualHeight();
7028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // upwards by default
7038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                float translationDirection = -1.0f;
7048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState != null) {
7058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // there was a view after this one, Approximate the distance the next child
7068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // travelled
7078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = ((viewState.yTranslation
7088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
7098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            actualHeight);
7108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
7118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.performRemoveAnimation(translationDirection, new Runnable() {
7148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    @Override
7158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    public void run() {
7168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // remove the temporary overlay
7178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        mHostLayout.getOverlay().remove(changingView);
7188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
7198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                });
720572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
7218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            mNewEvents.add(event);
722572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
723572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
7248d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
725475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi    public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
726475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi            final boolean isRubberbanded) {
7278d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
7289012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        if (targetAmount == startOverScrollAmount) {
7299012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi            return;
7309012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        }
7318d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        cancelOverScrollAnimators(onTop);
7328d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
7338d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                targetAmount);
7348d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
7358d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
7368d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            @Override
7378d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
7388d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                float currentOverScroll = (float) animation.getAnimatedValue();
73947c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                mHostLayout.setOverScrollAmount(
740475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
741475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        isRubberbanded);
7428d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            }
7438d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        });
7448d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
74547c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
74647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            @Override
74747c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            public void onAnimationEnd(Animator animation) {
74847c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                if (onTop) {
74947c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mTopOverScrollAnimator = null;
75047c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                } else {
75147c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mBottomOverScrollAnimator = null;
75247c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                }
75347c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            }
75447c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        });
7558d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.start();
7568d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (onTop) {
7578d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mTopOverScrollAnimator = overScrollAnimator;
7588d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        } else {
7598d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mBottomOverScrollAnimator = overScrollAnimator;
7608d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
7618d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
7628d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
7638d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void cancelOverScrollAnimators(boolean onTop) {
7648d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
7658d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (currentAnimator != null) {
7668d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            currentAnimator.cancel();
7678d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
7688d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
769572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek}
770