StackStateAnimator.java revision 9012958742c7a66b37ba5f2196f9086bb1980e6b
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
1798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (wasAdded) {
1808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            child.performAddAnimation(delay);
1818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
1828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    }
1838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
1848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
1858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
1868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long minDelay = 0;
1878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
1888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
1898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            switch (event.animationType) {
1908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
1918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
1928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int changingIndex = finalState
1938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(event.changingView).notGoneIndex;
1948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - changingIndex);
1958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
1968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
1978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
1988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
1998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
2028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
2038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
2048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
2058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    boolean noNextView = event.viewAfterChangingView == null;
2068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    View viewAfterChangingView = noNextView
2078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            ? mHostLayout.getLastChildNotGone()
2088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            : event.viewAfterChangingView;
2098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
2108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int nextIndex = finalState
2118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
2128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    if (ownIndex >= nextIndex) {
2138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // we only have the view afterwards
2148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        ownIndex++;
2158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
2168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - nextIndex);
2178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
2188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
2198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = difference * delayPerElement;
2208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
2218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
2238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                default:
2248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
2258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            }
2268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        return minDelay;
228572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
229572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
230eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startHeightAnimation(final ExpandableView child,
2318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState.ViewState viewState, long delay) {
2328df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
233d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
2348df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        int newEndValue = viewState.height;
2358df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
236eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
237eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
238eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
2398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateHeight) {
2408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
2418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
2428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
2438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
2448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
2458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int relativeDiff = newEndValue - previousEndValue;
2468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int newStartValue = previousStartValue + relativeDiff;
2478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setIntValues(newStartValue, newEndValue);
2488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, newStartValue);
2498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_HEIGHT, newEndValue);
2508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
2528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
2538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
2548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setActualHeight(newEndValue, false);
2558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
256eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
257eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
258eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
2598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
260eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
261572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            @Override
262572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
263d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setActualHeight((int) animation.getAnimatedValue(),
264d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                        false /* notifyListeners */);
265572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
266572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        });
267eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
2688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
269eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setDuration(newDuration);
2708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
2718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
2728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
273eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
274eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
275eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
276eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
277eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
278eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_HEIGHT, null);
2798df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, null);
280eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_HEIGHT, null);
281eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
282eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
2838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
284eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
2858df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
2868df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_HEIGHT, newEndValue);
287eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
288eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
289708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void startInsetAnimation(final ExpandableView child,
290708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            StackScrollState.ViewState viewState, long delay) {
291708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
292708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
293708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int newEndValue = viewState.clipTopAmount;
294708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
295708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            return;
296708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
297708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
298708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (!mAnimationFilter.animateTopInset) {
299708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // just a local update was performed
300708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (previousAnimator != null) {
301708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // we need to increase all animation keyframes of the previous animator by the
302708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // relative change to the end value
303708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
304708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int relativeDiff = newEndValue - previousEndValue;
305708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int newStartValue = previousStartValue + relativeDiff;
306708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                values[0].setIntValues(newStartValue, newEndValue);
307708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, newStartValue);
308708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, newEndValue);
309708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
310708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
311708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            } else {
312708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // no new animation needed, let's just apply the value
313708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount(newEndValue);
314708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
315708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
316708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
317708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
318708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
319708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
320708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
321708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
322708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount((int) animation.getAnimatedValue());
323708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
324708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
325708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
326708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
327708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setDuration(newDuration);
328708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
329708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            animator.setStartDelay(delay);
330708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
331708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
332708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // remove the tag when the animation is finished
333708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
334708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
335708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationEnd(Animator animation) {
336708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
337708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, null);
338708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, null);
339708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
340708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
341708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        startAnimator(animator);
342708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
343708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
344708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_END_TOP_INSET, newEndValue);
345708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
346708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
347eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startAlphaAnimation(final ExpandableView child,
3488efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final StackScrollState.ViewState viewState, long delay) {
3498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
350eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
3518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        final float newEndValue = viewState.alpha;
3528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
353eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
354eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
355eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
3568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateAlpha) {
3578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
3588df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
3598df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
3608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
3618df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
3628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
3638df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
3648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
3658df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, newStartValue);
3668df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_ALPHA, newEndValue);
3678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
3688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
3698df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
3708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
3718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setAlpha(newEndValue);
3728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0) {
3738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    child.setVisibility(View.INVISIBLE);
3748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                }
375eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
376eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
377eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
378eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
3798df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getAlpha(), newEndValue);
380eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
381eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // Handle layer type
382eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
383eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
384eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public boolean mWasCancelled;
385eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
386eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
387eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
3881efb240c1a0aeca9492cf8891794712adfdb1fa7Selim Cinek                child.setLayerType(View.LAYER_TYPE_NONE, null);
3898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0 && !mWasCancelled) {
390eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    child.setVisibility(View.INVISIBLE);
391eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
392eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_ALPHA, null);
3938df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, null);
394eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_ALPHA, null);
395eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
396eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
397eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
398eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationCancel(Animator animation) {
399eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = true;
400eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
401eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
402eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
403eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationStart(Animator animation) {
404eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = false;
405eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
406eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
4083af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
412eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
413eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
414eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
415eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
416eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
417eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
418eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
419eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
421eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_ALPHA, animator);
4228df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_ALPHA, child.getAlpha());
4238df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_ALPHA, newEndValue);
424572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
425572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
426eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startZTranslationAnimation(final ExpandableView child,
4278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final StackScrollState.ViewState viewState, long delay) {
4288df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
429eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
4308df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.zTranslation;
4318df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
432eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
433eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
434eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
4358df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateZ) {
4368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
4428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
4438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
4448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
4458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
4468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
4488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
4498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
4508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationZ(newEndValue);
451eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
452eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
453eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
454eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
4558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationZ(), newEndValue);
456eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
4578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
4583af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
4598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
462eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
463eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
464eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
465eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
466eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
467eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
4688df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, null);
469eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Z, null);
470eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
471eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
473eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
4748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
4758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
476eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
477eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
478eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startYTranslationAnimation(final ExpandableView child,
4798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState.ViewState viewState, long delay) {
4808df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
481eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
4828df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.yTranslation;
4838df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
484eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
485eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
486eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
4878df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateY) {
4888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
4898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
4908df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4938df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
4948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
4958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
4968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
4978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
4988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationY(newEndValue);
5038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
504eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
505eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
506eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
507eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
5088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationY(), newEndValue);
509eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
5108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
5113af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5148efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
515eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
516eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
517eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
518eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
519eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
520eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
5218df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, null);
522eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Y, null);
523eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
524eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
5258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
526eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
5278df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
5288df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
529eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
530eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
531d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private void startScaleAnimation(final ExpandableView child,
532d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            StackScrollState.ViewState viewState) {
5338df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
534d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
5358df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.scale;
5368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
537d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            return;
538d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
539d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
5408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateScale) {
5418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[1].setFloatValues(newStartValue, newEndValue);
5508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, newStartValue);
5518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_SCALE, newEndValue);
5528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5568df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleX(newEndValue);
5578df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleY(newEndValue);
558d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
559d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
560d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
561d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderX =
5628df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
563d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderY =
5648df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
565d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
566d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setInterpolator(mFastOutSlowInInterpolator);
5678df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
568d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setDuration(newDuration);
569d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(getGlobalAnimationFinishedListener());
570d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // remove the tag when the animation is finished
571d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(new AnimatorListenerAdapter() {
572d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            @Override
573d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            public void onAnimationEnd(Animator animation) {
574d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_ANIMATOR_SCALE, null);
5758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, null);
576d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_END_SCALE, null);
577d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
578d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        });
5798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
580d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setTag(TAG_ANIMATOR_SCALE, animator);
5818df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_SCALE, child.getScaleX());
5828df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_SCALE, newEndValue);
583d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
584d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
5858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimator(ValueAnimator animator) {
5868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mAnimatorSet.add(animator);
5873af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.start();
5883af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
5893af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
5903af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    /**
5913af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     * @return an adapter which ensures that onAnimationFinished is called once no animation is
5923af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     *         running anymore
5933af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     */
5943af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
5953af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        if (!mAnimationListenerPool.empty()) {
5963af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            return mAnimationListenerPool.pop();
5973af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        }
5983af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
5993af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        // We need to create a new one, no reusable ones found
6003af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return new AnimatorListenerAdapter() {
6013af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            private boolean mWasCancelled;
6023af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6033af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6043af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationEnd(Animator animation) {
6053af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimatorSet.remove(animation);
6063af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
6073af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                    onAnimationFinished();
6083af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                }
6093af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimationListenerPool.push(this);
6103af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6113af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6123af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6133af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationCancel(Animator animation) {
6143af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = true;
6153af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6163af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
6173af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
6183af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationStart(Animator animation) {
6193af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = false;
6203af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
6213af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        };
6223af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
6233af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
624eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private <T> T getChildTag(View child, int tag) {
625eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return (T) child.getTag(tag);
626eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
627eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
628eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
629eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Cancel the previous animator and get the duration of the new animation.
63039610545f0c2714a3526bc935effe57b421542d1Selim Cinek     *
631eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param previousAnimator the animator which was running before
632eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @return the new duration
633eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
6348df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) {
6355aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        long newDuration = mCurrentLength;
636eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (previousAnimator != null) {
6378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // We take either the desired length of the new animation or the remaining time of
6388df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // the previous animator, whichever is longer.
6398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            newDuration = Math.max(previousAnimator.getDuration()
6408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    - previousAnimator.getCurrentPlayTime(), newDuration);
6413af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            previousAnimator.cancel();
642eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
6433af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return newDuration;
644eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
645eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
646eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void onAnimationFinished() {
647eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        mHostLayout.onChildAnimationFinished();
648eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
649eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
650eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
651eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Process the animationEvents for a new animation
652eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     *
653eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param animationEvents the animation events for the animation to perform
65439610545f0c2714a3526bc935effe57b421542d1Selim Cinek     * @param finalState the final state to animate to
65539610545f0c2714a3526bc935effe57b421542d1Selim Cinek     */
656eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void processAnimationEvents(
6570dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
658572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
6598d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
6608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final ExpandableView changingView = (ExpandableView) event.changingView;
6618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (event.animationType ==
6628efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
6638efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6648efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // This item is added, initialize it's properties.
6658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
6668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(changingView);
6678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState == null) {
6688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The position for this child was never generated, let's continue.
6698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
6708efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
6718efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
6728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The view was set to gone but the state never removed
6738efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    finalState.removeViewStateForView(changingView);
6748efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
67539610545f0c2714a3526bc935effe57b421542d1Selim Cinek                }
6768efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setAlpha(viewState.alpha);
6778efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationY(viewState.yTranslation);
6788efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setTranslationZ(viewState.zTranslation);
6798efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.setActualHeight(viewState.height, false);
6808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                mNewAddChildren.add(changingView);
6818efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6828efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            } else if (event.animationType ==
6838efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
6848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
6858efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
6868efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
6878efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
6888efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // Find the amount to translate up. This is needed in order to understand the
6898efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // direction of the remove animation (either downwards or upwards)
6908efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = finalState
6918efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(event.viewAfterChangingView);
6928efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                int actualHeight = changingView.getActualHeight();
6938efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // upwards by default
6948efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                float translationDirection = -1.0f;
6958efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState != null) {
6968efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // there was a view after this one, Approximate the distance the next child
6978efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // travelled
6988efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = ((viewState.yTranslation
6998efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
7008efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            actualHeight);
7018efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
7028efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
7038efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
7048efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                changingView.performRemoveAnimation(translationDirection, new Runnable() {
7058efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    @Override
7068efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    public void run() {
7078efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // remove the temporary overlay
7088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        mHostLayout.getOverlay().remove(changingView);
7098efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
7108efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                });
711572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
7128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            mNewEvents.add(event);
713572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
714572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
7158d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
7168d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void animateOverScrollToAmount(float targetAmount, final boolean onTop) {
7178d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
7189012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        if (targetAmount == startOverScrollAmount) {
7199012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi            return;
7209012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        }
7218d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        cancelOverScrollAnimators(onTop);
7228d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
7238d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                targetAmount);
7248d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
7258d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
7268d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            @Override
7278d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
7288d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                float currentOverScroll = (float) animation.getAnimatedValue();
7298d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */,
7308d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                        false /* cancelAnimators */);
7318d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            }
7328d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        });
7338d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
7348d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.start();
7358d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (onTop) {
7368d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mTopOverScrollAnimator = overScrollAnimator;
7378d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        } else {
7388d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mBottomOverScrollAnimator = overScrollAnimator;
7398d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
7408d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
7418d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
7428d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void cancelOverScrollAnimators(boolean onTop) {
7438d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
7448d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (currentAnimator != null) {
7458d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            currentAnimator.cancel();
7468d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
7478d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
748572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek}
749