StackStateAnimator.java revision 475b21dfe517ec04f435f6b02f4a53083d040db4
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
177d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
1788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
179bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        // start dark animation
180bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        child.setDark(viewState.dark, mAnimationFilter.animateDark);
181bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock
182f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek        // apply scrimming
183f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek        child.setScrimAmount(viewState.scrimAmount);
184f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek
1858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (wasAdded) {
1868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            child.performAddAnimation(delay);
1878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
1888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    }
1898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
1908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
1918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
1928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long minDelay = 0;
1938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
1948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
1958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            switch (event.animationType) {
1968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
1978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
1988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int changingIndex = finalState
1998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(event.changingView).notGoneIndex;
2008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - changingIndex);
2018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
2048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
2088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
2098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
2108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    boolean noNextView = event.viewAfterChangingView == null;
2128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    View viewAfterChangingView = noNextView
2138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            ? mHostLayout.getLastChildNotGone()
2148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            : event.viewAfterChangingView;
2158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
2168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int nextIndex = finalState
2178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
2188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    if (ownIndex >= nextIndex) {
2198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // we only have the view afterwards
2208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        ownIndex++;
2218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
2228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - nextIndex);
2238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = difference * delayPerElement;
2268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                default:
2308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            }
2328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        return minDelay;
234572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
235572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
236eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startHeightAnimation(final ExpandableView child,
2378efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState.ViewState viewState, long delay) {
2388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
239d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
2408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        int newEndValue = viewState.height;
2418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
242eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
243eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
244eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
2458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateHeight) {
2468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
2478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
2488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
2498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
2508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
2518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int relativeDiff = newEndValue - previousEndValue;
2528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int newStartValue = previousStartValue + relativeDiff;
2538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setIntValues(newStartValue, newEndValue);
2548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, newStartValue);
2558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_HEIGHT, newEndValue);
2568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
2588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
2598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
2608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setActualHeight(newEndValue, false);
2618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
262eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
263eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
264eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
2658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
266eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
267572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            @Override
268572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
269d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setActualHeight((int) animation.getAnimatedValue(),
270d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                        false /* notifyListeners */);
271572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
272572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        });
273eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
2748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
275eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setDuration(newDuration);
2768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
2778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
2788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
279eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
280eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
281eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
282eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
283eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
284eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_HEIGHT, null);
2858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, null);
286eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_HEIGHT, null);
287eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
288eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
2898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
290eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
2918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
2928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_HEIGHT, newEndValue);
293eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
294eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
295708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void startInsetAnimation(final ExpandableView child,
296708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            StackScrollState.ViewState viewState, long delay) {
297708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
298708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
299708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int newEndValue = viewState.clipTopAmount;
300708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
301708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            return;
302708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
303708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
304708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (!mAnimationFilter.animateTopInset) {
305708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // just a local update was performed
306708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (previousAnimator != null) {
307708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // we need to increase all animation keyframes of the previous animator by the
308708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // relative change to the end value
309708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
310708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int relativeDiff = newEndValue - previousEndValue;
311708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int newStartValue = previousStartValue + relativeDiff;
312708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                values[0].setIntValues(newStartValue, newEndValue);
313708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, newStartValue);
314708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, newEndValue);
315708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
316708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
317708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            } else {
318708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // no new animation needed, let's just apply the value
319708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount(newEndValue);
320708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
321708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
322708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
323708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
324708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
325708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
326708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
327708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
328708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount((int) animation.getAnimatedValue());
329708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
330708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
331708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
332708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
333708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setDuration(newDuration);
334708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
335708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            animator.setStartDelay(delay);
336708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
337708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
338708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // remove the tag when the animation is finished
339708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
340708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
341708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationEnd(Animator animation) {
342708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
343708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, null);
344708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, null);
345708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
346708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
347708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        startAnimator(animator);
348708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
349708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
350708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_END_TOP_INSET, newEndValue);
351708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
352708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
353eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startAlphaAnimation(final ExpandableView child,
3548efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final StackScrollState.ViewState viewState, long delay) {
3558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
356eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
3578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        final float newEndValue = viewState.alpha;
3588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
359eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
360eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
361eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
3628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateAlpha) {
3638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
3648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
3658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
3668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
3678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
3688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
3698df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
3708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
3718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, newStartValue);
3728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_ALPHA, newEndValue);
3738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
3748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
3758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
3768df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
3778df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setAlpha(newEndValue);
3788df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0) {
3798df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    child.setVisibility(View.INVISIBLE);
3808df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                }
381eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
382eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
383eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
384eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
3858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getAlpha(), newEndValue);
386eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
387eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // Handle layer type
388eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
389eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
390eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public boolean mWasCancelled;
391eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
392eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
393eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
3941efb240c1a0aeca9492cf8891794712adfdb1fa7Selim Cinek                child.setLayerType(View.LAYER_TYPE_NONE, null);
3958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0 && !mWasCancelled) {
396eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    child.setVisibility(View.INVISIBLE);
397eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
398eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_ALPHA, null);
3998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, null);
400eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_ALPHA, null);
401eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
402eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
403eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
404eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationCancel(Animator animation) {
405eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = true;
406eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
407eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
408eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
409eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationStart(Animator animation) {
410eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = false;
411eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
412eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
4143af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
418eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
419eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
420eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
421eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
422eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
423eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
424eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
425eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
427eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_ALPHA, animator);
4288df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_ALPHA, child.getAlpha());
4298df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_ALPHA, newEndValue);
430572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
431572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
432eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startZTranslationAnimation(final ExpandableView child,
4338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final StackScrollState.ViewState viewState, long delay) {
4348df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
435eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
4368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.zTranslation;
4378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
438eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
439eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
440eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
4418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateZ) {
4428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
4488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
4498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
4508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
4518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
4528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
4548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
4558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
4568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationZ(newEndValue);
457eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
458eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
459eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
460eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
4618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationZ(), newEndValue);
462eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
4638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
4643af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
468eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
469eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
470eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
471eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
472eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
473eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
4748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, null);
475eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Z, null);
476eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
477eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
479eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
4808df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
4818df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
482eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
483eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
484eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startYTranslationAnimation(final ExpandableView child,
4858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState.ViewState viewState, long delay) {
4868df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
487eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
4888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.yTranslation;
4898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
490eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
491eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
492eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
4938df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateY) {
4948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
5038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
5048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationY(newEndValue);
5098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
510eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
511eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
512eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
513eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
5148df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationY(), newEndValue);
515eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
5168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
5173af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
521eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
522eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
523eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
524eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
525eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
526eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
5278df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, null);
528eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Y, null);
529eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
530eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
5318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
532eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
5338df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
5348df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
535eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
536eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
537d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private void startScaleAnimation(final ExpandableView child,
538d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            StackScrollState.ViewState viewState) {
5398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
540d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
5418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.scale;
5428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
543d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            return;
544d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
545d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
5468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateScale) {
5478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[1].setFloatValues(newStartValue, newEndValue);
5568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, newStartValue);
5578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_SCALE, newEndValue);
5588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleX(newEndValue);
5638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleY(newEndValue);
564d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
565d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
566d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
567d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderX =
5688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
569d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderY =
5708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
571d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
572d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setInterpolator(mFastOutSlowInInterpolator);
5738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
574d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setDuration(newDuration);
575d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(getGlobalAnimationFinishedListener());
576d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // remove the tag when the animation is finished
577d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(new AnimatorListenerAdapter() {
578d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            @Override
579d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            public void onAnimationEnd(Animator animation) {
580d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_ANIMATOR_SCALE, null);
5818df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, null);
582d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_END_SCALE, null);
583d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
584d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        });
5858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
586d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setTag(TAG_ANIMATOR_SCALE, animator);
5878df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_SCALE, child.getScaleX());
5888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_SCALE, newEndValue);
589d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
590d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
5918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimator(ValueAnimator animator) {
5928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mAnimatorSet.add(animator);
5933af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.start();
5943af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
5953af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
5963af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    /**
5973af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     * @return an adapter which ensures that onAnimationFinished is called once no animation is
5983af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     *         running anymore
5993af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     */
6003af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
6013af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        if (!mAnimationListenerPool.empty()) {
6023af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            return mAnimationListenerPool.pop();
6033af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        }
6043af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6053af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        // We need to create a new one, no reusable ones found
6063af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return new AnimatorListenerAdapter() {
6073af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            private boolean mWasCancelled;
6083af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6093af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6103af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationEnd(Animator animation) {
6113af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimatorSet.remove(animation);
6123af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
6133af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                    onAnimationFinished();
6143af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                }
6153af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimationListenerPool.push(this);
6163af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6173af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6183af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6193af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationCancel(Animator animation) {
6203af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = true;
6213af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6223af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6233af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6243af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationStart(Animator animation) {
6253af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = false;
6263af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6273af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        };
6283af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
6293af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
630eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private <T> T getChildTag(View child, int tag) {
631eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return (T) child.getTag(tag);
632eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
633eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
634eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
635eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Cancel the previous animator and get the duration of the new animation.
63639610545f0c2714a3526bc935effe57b421542d1Selim Cinek     *
637eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param previousAnimator the animator which was running before
638eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @return the new duration
639eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
6408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) {
6415aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        long newDuration = mCurrentLength;
642eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (previousAnimator != null) {
6438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // We take either the desired length of the new animation or the remaining time of
6448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // the previous animator, whichever is longer.
6458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            newDuration = Math.max(previousAnimator.getDuration()
6468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    - previousAnimator.getCurrentPlayTime(), newDuration);
6473af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            previousAnimator.cancel();
648eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
6493af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return newDuration;
650eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
651eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
652eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void onAnimationFinished() {
653eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        mHostLayout.onChildAnimationFinished();
654eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
655eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
656eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
657eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Process the animationEvents for a new animation
658eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     *
659eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param animationEvents the animation events for the animation to perform
66039610545f0c2714a3526bc935effe57b421542d1Selim Cinek     * @param finalState the final state to animate to
66139610545f0c2714a3526bc935effe57b421542d1Selim Cinek     */
662eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void processAnimationEvents(
6630dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
664572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
6658d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
6668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final ExpandableView changingView = (ExpandableView) event.changingView;
6678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (event.animationType ==
6688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
6698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // This item is added, initialize it's properties.
6718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
6728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(changingView);
6738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState == null) {
6748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The position for this child was never generated, let's continue.
6758efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
6768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
6778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
6788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The view was set to gone but the state never removed
6798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    finalState.removeViewStateForView(changingView);
6808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
68139610545f0c2714a3526bc935effe57b421542d1Selim Cinek                }
6828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setAlpha(viewState.alpha);
6838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationY(viewState.yTranslation);
6848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationZ(viewState.zTranslation);
6858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setActualHeight(viewState.height, false);
6868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                mNewAddChildren.add(changingView);
6878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            } else if (event.animationType ==
6898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
6908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
6918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
6928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
6938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // Find the amount to translate up. This is needed in order to understand the
6958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // direction of the remove animation (either downwards or upwards)
6968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
6978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(event.viewAfterChangingView);
6988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                int actualHeight = changingView.getActualHeight();
6998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // upwards by default
7008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                float translationDirection = -1.0f;
7018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState != null) {
7028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // there was a view after this one, Approximate the distance the next child
7038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // travelled
7048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = ((viewState.yTranslation
7058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
7068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            actualHeight);
7078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
7088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.performRemoveAnimation(translationDirection, new Runnable() {
7118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    @Override
7128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    public void run() {
7138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // remove the temporary overlay
7148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        mHostLayout.getOverlay().remove(changingView);
7158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
7168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                });
717572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
7188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            mNewEvents.add(event);
719572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
720572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
7218d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
722475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi    public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
723475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi            final boolean isRubberbanded) {
7248d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
7259012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        if (targetAmount == startOverScrollAmount) {
7269012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi            return;
7279012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        }
7288d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        cancelOverScrollAnimators(onTop);
7298d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
7308d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                targetAmount);
7318d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
7328d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
7338d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            @Override
7348d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
7358d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                float currentOverScroll = (float) animation.getAnimatedValue();
73647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                mHostLayout.setOverScrollAmount(
737475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
738475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        isRubberbanded);
7398d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            }
7408d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        });
7418d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
74247c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
74347c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            @Override
74447c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            public void onAnimationEnd(Animator animation) {
74547c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                if (onTop) {
74647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mTopOverScrollAnimator = null;
74747c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                } else {
74847c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mBottomOverScrollAnimator = null;
74947c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                }
75047c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            }
75147c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        });
7528d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.start();
7538d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (onTop) {
7548d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mTopOverScrollAnimator = overScrollAnimator;
7558d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        } else {
7568d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mBottomOverScrollAnimator = overScrollAnimator;
7578d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
7588d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
7598d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
7608d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void cancelOverScrollAnimators(boolean onTop) {
7618d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
7628d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (currentAnimator != null) {
7638d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            currentAnimator.cancel();
7648d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
7658d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
766572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek}
767