1572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek/*
2572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * Copyright (C) 2014 The Android Open Source Project
3572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *
4572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * Licensed under the Apache License, Version 2.0 (the "License");
5572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * you may not use this file except in compliance with the License.
6572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * You may obtain a copy of the License at
7572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *
8572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *      http://www.apache.org/licenses/LICENSE-2.0
9572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek *
10572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * Unless required by applicable law or agreed to in writing, software
11572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * distributed under the License is distributed on an "AS IS" BASIS,
12572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * See the License for the specific language governing permissions and
14572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * limitations under the License
15572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek */
16572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
17572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekpackage com.android.systemui.statusbar.stack;
18572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
19eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport android.animation.Animator;
20eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport android.animation.AnimatorListenerAdapter;
21eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport android.animation.ObjectAnimator;
22d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggiimport android.animation.PropertyValuesHolder;
23572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.animation.ValueAnimator;
24572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.view.View;
25572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.view.animation.AnimationUtils;
26572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport android.view.animation.Interpolator;
27eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
28eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport com.android.systemui.R;
29b5605e58cb8080c8c887b1885336b707596c8094Selim Cinekimport com.android.systemui.statusbar.ExpandableNotificationRow;
30572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport com.android.systemui.statusbar.ExpandableView;
318b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggiimport com.android.systemui.statusbar.SpeedBumpView;
320fccc729fd3b19a62efd90ae13b207faa3d1e8bbSelim Cinekimport com.android.systemui.statusbar.policy.HeadsUpManager;
33572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
34572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekimport java.util.ArrayList;
35eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.HashSet;
36eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinekimport java.util.Stack;
37572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
38572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek/**
39572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek * An stack state animator which handles animations to new StackScrollStates
40572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek */
41572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinekpublic class StackStateAnimator {
42572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
435aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_STANDARD = 360;
4460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
46b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
475aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
48a7840af09d808ecd3f7547bbdea58d4422bd4d8fSelim Cinek    public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
49a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
51b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
528efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
5360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
544e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi    public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
55b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
56b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
575aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi
58eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
59eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
60d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
61eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
62eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
63eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
64eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
65eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
66d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
67eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
68eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
69eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
708df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
718df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
728df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
738df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
76572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
77572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    private final Interpolator mFastOutSlowInInterpolator;
78a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private final Interpolator mHeadsUpAppearInterpolator;
7960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private final int mGoToFullShadeAppearingTranslation;
80684a442b812a5e95d813700ffa2fd17ca72048a7Selim Cinek    private final StackViewState mTmpState = new StackViewState();
81572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public NotificationStackScrollLayout mHostLayout;
82eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
83eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            new ArrayList<>();
848efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private ArrayList<View> mNewAddChildren = new ArrayList<>();
85a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
86a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
87a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private HashSet<Animator> mAnimatorSet = new HashSet<>();
8860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
89d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private AnimationFilter mAnimationFilter = new AnimationFilter();
905aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi    private long mCurrentLength;
91dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi    private long mCurrentAdditionalDelay;
92572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
9360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    /** The current index for the last child which was not added in this event set. */
9460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private int mCurrentLastNotAddedIndex;
958d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mTopOverScrollAnimator;
968d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    private ValueAnimator mBottomOverScrollAnimator;
97b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek    private ExpandableNotificationRow mChildExpandingView;
98a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private int mHeadsUpAppearHeightBottom;
99a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private boolean mShadeExpanded;
1008f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek    private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>();
1018d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
102572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
103572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mHostLayout = hostLayout;
104572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
105eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                android.R.interpolator.fast_out_slow_in);
10660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        mGoToFullShadeAppearingTranslation =
10760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                hostLayout.getContext().getResources().getDimensionPixelSize(
10860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                        R.dimen.go_to_full_shade_appearing_translation);
109684a442b812a5e95d813700ffa2fd17ca72048a7Selim Cinek        mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator();
110572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
111572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
112572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public boolean isRunning() {
113eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return !mAnimatorSet.isEmpty();
114572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
115572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
116572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    public void startAnimationForEvents(
1170dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
118dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi            StackScrollState finalState, long additionalDelay) {
119eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
120eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        processAnimationEvents(mAnimationEvents, finalState);
121eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
122572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        int childCount = mHostLayout.getChildCount();
123d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        mAnimationFilter.applyCombination(mNewEvents);
124dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi        mCurrentAdditionalDelay = additionalDelay;
1255aa045cc6bca84f5c11f1a99999546ba5e5949a5Jorim Jaggi        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
12660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
127572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        for (int i = 0; i < childCount; i++) {
128572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
1298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
130b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState = finalState.getViewStateForView(child);
131a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            if (viewState == null || child.getVisibility() == View.GONE
132a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                    || applyWithoutAnimation(child, viewState, finalState)) {
133572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek                continue;
134572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
135572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
136a272dfed9a4f31d8245099c0d99a73e79b90670cSelim Cinek            child.setClipTopOptimization(0);
137b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
138572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
139eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (!isRunning()) {
140eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            // no child has preformed any animation, lets finish
141eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            onAnimationFinished();
142eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
143a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        mHeadsUpAppearChildren.clear();
144a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        mHeadsUpDisappearChildren.clear();
1458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewEvents.clear();
1468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mNewAddChildren.clear();
147b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek        mChildExpandingView = null;
148572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
149572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
150a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    /**
151a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek     * Determines if a view should not perform an animation and applies it directly.
152a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek     *
153a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek     * @return true if no animation should be performed
154a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek     */
155a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
156a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            StackScrollState finalState) {
157a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        if (mShadeExpanded) {
158a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            return false;
159a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        }
160a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
161a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            // A Y translation animation is running
162a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            return false;
163a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        }
164a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
165a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            // This is a heads up animation
166a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            return false;
167a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        }
168131c1e2960fa5bdf54bfb6fcd5ac98c9f728f796Selim Cinek        if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
1691f3f544f21cab3728f749ab66cd6859e9dfcf389Selim Cinek            // This is another headsUp which might move. Let's animate!
1701f3f544f21cab3728f749ab66cd6859e9dfcf389Selim Cinek            return false;
1711f3f544f21cab3728f749ab66cd6859e9dfcf389Selim Cinek        }
172a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        finalState.applyState(child, viewState);
173a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        return true;
174a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    }
175a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek
17660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private int findLastNotAddedIndex(StackScrollState finalState) {
17760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        int childCount = mHostLayout.getChildCount();
17860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        for (int i = childCount - 1; i >= 0; i--) {
17960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
18060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
181b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState = finalState.getViewStateForView(child);
18260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            if (viewState == null || child.getVisibility() == View.GONE) {
18360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                continue;
18460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            }
18560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            if (!mNewAddChildren.contains(child)) {
18660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                return viewState.notGoneIndex;
18760d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            }
18860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
18960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        return -1;
19060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    }
19160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
192b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
193eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
194b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * Start an animation to the given  {@link StackViewState}.
195b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     *
196b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param child the child to start the animation on
197b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param viewState the {@link StackViewState} of the view to animate to
198b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param finalState the final state after the animation
199b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param i the index of the view; only relevant if the view is the speed bump and is
200b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     *          ignored otherwise
201b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
202eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
203b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    public void startStackAnimations(final ExpandableView child, StackViewState viewState,
204b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackScrollState finalState, int i, long fixedDelay) {
205eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        final float alpha = viewState.alpha;
206b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean wasAdded = mNewAddChildren.contains(child);
207b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        long duration = mCurrentLength;
208b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
209b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
210b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
211b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
212b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
213b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                    (long) (100 * longerDurationFactor);
214eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
2158efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
2168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
2178efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean scaleChanging = child.getScaleX() != viewState.scale;
2188efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean alphaChanging = alpha != child.getAlpha();
2198efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean heightChanging = viewState.height != child.getActualHeight();
2204e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi        boolean darkChanging = viewState.dark != child.isDark();
221708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
2228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean hasDelays = mAnimationFilter.hasDelays;
2238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
2244e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi                alphaChanging || heightChanging || topInsetChanging || darkChanging;
2258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long delay = 0;
226b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (fixedDelay != -1) {
227b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            delay = fixedDelay;
228b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        } else if (hasDelays && isDelayRelevant || wasAdded) {
229dbc3dce886cb7abba23a9f18e60d0f5af5c93226Jorim Jaggi            delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
2308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
232b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        startViewAnimations(child, viewState, delay, duration);
2338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
234eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // start height animation
235d2319fbe6a53ac4c38ca02e4d8e32da86ed0994bSelim Cinek        if (heightChanging && child.getActualHeight() != 0) {
236b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startHeightAnimation(child, viewState, duration, delay);
23759b5a356b828fe60ea2874b0680a1bf7c84809a1Jorim Jaggi        }
2388efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
239708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // start top inset animation
240708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (topInsetChanging) {
241b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startInsetAnimation(child, viewState, duration, delay);
242708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
243708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
244d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // start dimmed animation
245b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
2468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
2473d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        // apply speed bump state
2483d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        child.setBelowSpeedBump(viewState.belowSpeedBump);
2493d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek
250ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi        // start hiding sensitive animation
251b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
252b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                delay, duration);
253ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi
254021eee53e52c880a529ece28a3a9edb3cd0776b7Jorim Jaggi        // start dark animation
255021eee53e52c880a529ece28a3a9edb3cd0776b7Jorim Jaggi        child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
256021eee53e52c880a529ece28a3a9edb3cd0776b7Jorim Jaggi
2578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (wasAdded) {
25860d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            child.performAddAnimation(delay, mCurrentLength);
2598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
2608b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi        if (child instanceof SpeedBumpView) {
2618b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi            finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
2628b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi                    delay + duration);
263b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek        } else if (child instanceof ExpandableNotificationRow) {
264b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
265b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
266b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                    duration);
2678b73006a36b3000a9847534dcb01a2e7066e9d93Jorim Jaggi        }
2688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    }
2698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
270b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    /**
271b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * Start an animation to a new {@link ViewState}.
272b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     *
273b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param child the child to start the animation on
274b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param viewState the  {@link StackViewState} of the view to animate to
275b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param delay a fixed delay
276b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     * @param duration the duration of the animation
277b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek     */
278b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
279b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean wasVisible = child.getVisibility() == View.VISIBLE;
280b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        final float alpha = viewState.alpha;
281b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (!wasVisible && alpha != 0 && !viewState.gone) {
282b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            child.setVisibility(View.VISIBLE);
283b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
284b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
285b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
286b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean scaleChanging = child.getScaleX() != viewState.scale;
287b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha();
288b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        boolean alphaChanging = viewState.alpha != childAlpha;
2892cd45dfba6a9105a305ea20b110ba5ac078a9dc6Selim Cinek        if (child instanceof ExpandableView) {
2902cd45dfba6a9105a305ea20b110ba5ac078a9dc6Selim Cinek            // We don't want views to change visibility when they are animating to GONE
2912cd45dfba6a9105a305ea20b110ba5ac078a9dc6Selim Cinek            alphaChanging &= !((ExpandableView) child).willBeGone();
2922cd45dfba6a9105a305ea20b110ba5ac078a9dc6Selim Cinek        }
293b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
294b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start translationY animation
295b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (yTranslationChanging) {
296b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startYTranslationAnimation(child, viewState, duration, delay);
297b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
298b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
299b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start translationZ animation
300b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (zTranslationChanging) {
301b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startZTranslationAnimation(child, viewState, duration, delay);
302b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
303b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
304b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start scale animation
305b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (scaleChanging) {
306b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startScaleAnimation(child, viewState, duration);
307b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
308b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
309b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        // start alpha animation
310b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        if (alphaChanging && child.getTranslationX() == 0) {
311b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            startAlphaAnimation(child, viewState, duration, delay);
312b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek        }
313b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    }
314b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek
315b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private long calculateChildAnimationDelay(StackViewState viewState,
3168efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            StackScrollState finalState) {
3174e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi        if (mAnimationFilter.hasDarkEvent) {
3184e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi            return calculateDelayDark(viewState);
3194e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi        }
32060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        if (mAnimationFilter.hasGoToFullShadeEvent) {
32160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi            return calculateDelayGoToFullShade(viewState);
32260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        }
3238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        long minDelay = 0;
3248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
3258efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
3268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            switch (event.animationType) {
3278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
3288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
3298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int changingIndex = finalState
3308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(event.changingView).notGoneIndex;
3318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - changingIndex);
3328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
3338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
3348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
3358efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
3368efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
3378efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
3388efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
3398efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
3408efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
3418efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int ownIndex = viewState.notGoneIndex;
3428efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    boolean noNextView = event.viewAfterChangingView == null;
3438efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    View viewAfterChangingView = noNextView
3448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            ? mHostLayout.getLastChildNotGone()
3458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            : event.viewAfterChangingView;
3468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
3478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int nextIndex = finalState
3488efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
3498efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    if (ownIndex >= nextIndex) {
3508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // we only have the view afterwards
3518efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        ownIndex++;
3528efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
3538efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    int difference = Math.abs(ownIndex - nextIndex);
3548efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
3558efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            difference - 1));
3568efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    long delay = difference * delayPerElement;
3578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    minDelay = Math.max(delay, minDelay);
3588efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
3598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
3608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                default:
3618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    break;
3628efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            }
3638efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
3648efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        return minDelay;
365572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
366572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
367b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private long calculateDelayDark(StackViewState viewState) {
3682a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        int referenceIndex;
3692a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        if (mAnimationFilter.darkAnimationOriginIndex ==
3702a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
3712a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi            referenceIndex = 0;
3722a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        } else if (mAnimationFilter.darkAnimationOriginIndex ==
3732a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
3742a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi            referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
3752a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        } else {
3762a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi            referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
3772a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        }
3782a5e452f4f70d2dd717946dd50486cc66ea4cc43Jorim Jaggi        return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
3794e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi    }
3804e857f4ef0357e05806819d0488a73a12208fe8fJorim Jaggi
381b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private long calculateDelayGoToFullShade(StackViewState viewState) {
38260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        float index = viewState.notGoneIndex;
38360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        index = (float) Math.pow(index, 0.7f);
38460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
38560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    }
38660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi
387eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void startHeightAnimation(final ExpandableView child,
388b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState, long duration, long delay) {
3898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
390d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
3918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        int newEndValue = viewState.height;
3928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
393eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
394eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
395eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
3968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateHeight) {
3978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
3988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
3998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
4008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
4018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
4028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int relativeDiff = newEndValue - previousEndValue;
4038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                int newStartValue = previousStartValue + relativeDiff;
4048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setIntValues(newStartValue, newEndValue);
4058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, newStartValue);
4068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_HEIGHT, newEndValue);
4078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
4088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
4098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
4108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
4118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setActualHeight(newEndValue, false);
4128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
413eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
414eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
415eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
4168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
417eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
418572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            @Override
419572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
420d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setActualHeight((int) animation.getAnimatedValue(),
421d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                        false /* notifyListeners */);
422572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
423572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        });
424eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
42560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
426eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setDuration(newDuration);
4278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
4288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
4298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
430eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
431eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
432eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
433eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
434eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
435eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_HEIGHT, null);
4368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_HEIGHT, null);
437eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_HEIGHT, null);
438eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
439eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
4408efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
441eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
4428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
4438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_HEIGHT, newEndValue);
444eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
445eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
446708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void startInsetAnimation(final ExpandableView child,
447b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            StackViewState viewState, long duration, long delay) {
448708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
449708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
450708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int newEndValue = viewState.clipTopAmount;
451708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
452708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            return;
453708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
454708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
455708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (!mAnimationFilter.animateTopInset) {
456708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // just a local update was performed
457708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (previousAnimator != null) {
458708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // we need to increase all animation keyframes of the previous animator by the
459708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // relative change to the end value
460708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
461708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int relativeDiff = newEndValue - previousEndValue;
462708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                int newStartValue = previousStartValue + relativeDiff;
463708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                values[0].setIntValues(newStartValue, newEndValue);
464708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, newStartValue);
465708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, newEndValue);
466708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
467708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
468708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            } else {
469708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // no new animation needed, let's just apply the value
470708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount(newEndValue);
471708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                return;
472708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
473708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
474708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
475708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
476708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
477708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
478708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
479708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setClipTopAmount((int) animation.getAnimatedValue());
480708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
481708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
482708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
48360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
484708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.setDuration(newDuration);
485708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
486708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            animator.setStartDelay(delay);
487708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
488708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
489708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        // remove the tag when the animation is finished
490708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
491708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            @Override
492708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            public void onAnimationEnd(Animator animation) {
493708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
494708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_START_TOP_INSET, null);
495708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                child.setTag(TAG_END_TOP_INSET, null);
496708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
497708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        });
498708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        startAnimator(animator);
499708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
500708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
501708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        child.setTag(TAG_END_TOP_INSET, newEndValue);
502708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
503708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
504b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startAlphaAnimation(final View child,
505b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            final ViewState viewState, long duration, long delay) {
5068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
507eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
5088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        final float newEndValue = viewState.alpha;
5098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
510eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
511eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
512eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
5138df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateAlpha) {
5148df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5158df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5168df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5178df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5188df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5198df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5208df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5218df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5228df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, newStartValue);
5238df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_ALPHA, newEndValue);
5248df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5258df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
5268df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
5278df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
5288df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setAlpha(newEndValue);
5298df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0) {
5308df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    child.setVisibility(View.INVISIBLE);
5318df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                }
532eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
533eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
534eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
535eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
5368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getAlpha(), newEndValue);
537eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
538eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // Handle layer type
539eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
540eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
541eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public boolean mWasCancelled;
542eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
543eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
544eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
5451efb240c1a0aeca9492cf8891794712adfdb1fa7Selim Cinek                child.setLayerType(View.LAYER_TYPE_NONE, null);
5468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                if (newEndValue == 0 && !mWasCancelled) {
547eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    child.setVisibility(View.INVISIBLE);
548eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
549c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                // remove the tag when the animation is finished
550eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_ALPHA, null);
5518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_ALPHA, null);
552eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_ALPHA, null);
553eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
554eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
555eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
556eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationCancel(Animator animation) {
557eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = true;
558eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
559eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
560eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
561eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationStart(Animator animation) {
562eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                mWasCancelled = false;
563eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
564eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
56560d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
5663af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
5678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
5688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
5698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
570eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
571eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
5728efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
573eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_ALPHA, animator);
5748df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_ALPHA, child.getAlpha());
5758df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_ALPHA, newEndValue);
576572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
577572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek
578b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startZTranslationAnimation(final View child,
579b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            final ViewState viewState, long duration, long delay) {
5808df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
581eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
5828df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.zTranslation;
5838df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
584eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
585eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
586eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
5878df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateZ) {
5888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
5898df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
5908df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
5918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
5928df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
5938df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
5948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
5958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
5968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
5978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
5988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
5998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
6008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
6018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
6028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationZ(newEndValue);
603eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
604eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
605eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
606eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
6078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationZ(), newEndValue);
608eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.setInterpolator(mFastOutSlowInInterpolator);
60960d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
6103af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
6118efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
6128efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
6138efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
614eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
615eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
616eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
617eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
618eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
619eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
6208df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Z, null);
621eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Z, null);
622eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
623eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
6248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
625eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
6268df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
6278df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
628eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
629eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
630b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startYTranslationAnimation(final View child,
631b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            ViewState viewState, long duration, long delay) {
6328df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
633eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
6348df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.yTranslation;
6358df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
636eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            return;
637eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
638eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
6398df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateY) {
6408df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
6418df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
6428df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
6438df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
6448df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
6458df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
6468df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
6478df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
6488df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
6498df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
6508df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
6518df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
6528df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
6538df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
6548df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTranslationY(newEndValue);
6558df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
656eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
657eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
658eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
659eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
6608df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.getTranslationY(), newEndValue);
661a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
662a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator;
663a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        animator.setInterpolator(interpolator);
66460d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
6653af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.setDuration(newDuration);
6668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
6678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            animator.setStartDelay(delay);
6688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        }
669eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(getGlobalAnimationFinishedListener());
670eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        // remove the tag when the animation is finished
671eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        animator.addListener(new AnimatorListenerAdapter() {
672eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            @Override
673eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            public void onAnimationEnd(Animator animation) {
6740fccc729fd3b19a62efd90ae13b207faa3d1e8bbSelim Cinek                HeadsUpManager.setIsClickedNotification(child, false);
675eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
6768df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_TRANSLATION_Y, null);
677eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                child.setTag(TAG_END_TRANSLATION_Y, null);
678eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
679eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        });
6808efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
681eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
6828df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
6838df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
684eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
685eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
686b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek    private void startScaleAnimation(final View child,
687b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            ViewState viewState, long duration) {
6888df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
689d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
6908df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        float newEndValue = viewState.scale;
6918df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (previousEndValue != null && previousEndValue == newEndValue) {
692d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            return;
693d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
694d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
6958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        if (!mAnimationFilter.animateScale) {
6968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // just a local update was performed
6978df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            if (previousAnimator != null) {
6988df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // we need to increase all animation keyframes of the previous animator by the
6998df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // relative change to the end value
7008df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder[] values = previousAnimator.getValues();
7018df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float relativeDiff = newEndValue - previousEndValue;
7028df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                float newStartValue = previousStartValue + relativeDiff;
7038df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[0].setFloatValues(newStartValue, newEndValue);
7048df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                values[1].setFloatValues(newStartValue, newEndValue);
7058df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, newStartValue);
7068df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_END_SCALE, newEndValue);
7078df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
7088df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                return;
7098df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            } else {
7108df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                // no new animation needed, let's just apply the value
7118df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleX(newEndValue);
7128df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setScaleY(newEndValue);
713d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
714d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
715d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
716d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderX =
7178df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
718d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        PropertyValuesHolder holderY =
7198df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
720d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
721d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setInterpolator(mFastOutSlowInInterpolator);
72260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
723d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.setDuration(newDuration);
724d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(getGlobalAnimationFinishedListener());
725d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        // remove the tag when the animation is finished
726d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        animator.addListener(new AnimatorListenerAdapter() {
727d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            @Override
728d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            public void onAnimationEnd(Animator animation) {
729d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_ANIMATOR_SCALE, null);
7308df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                child.setTag(TAG_START_SCALE, null);
731d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                child.setTag(TAG_END_SCALE, null);
732d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
733d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        });
7348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        startAnimator(animator);
735d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        child.setTag(TAG_ANIMATOR_SCALE, animator);
7368df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_START_SCALE, child.getScaleX());
7378df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek        child.setTag(TAG_END_SCALE, newEndValue);
738d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
739d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
7408efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek    private void startAnimator(ValueAnimator animator) {
7418efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek        mAnimatorSet.add(animator);
7423af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        animator.start();
7433af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
7443af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7453af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    /**
7463af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     * @return an adapter which ensures that onAnimationFinished is called once no animation is
7473af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     *         running anymore
7483af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek     */
7493af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
7503af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        if (!mAnimationListenerPool.empty()) {
7513af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            return mAnimationListenerPool.pop();
7523af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        }
7533af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7543af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        // We need to create a new one, no reusable ones found
7553af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return new AnimatorListenerAdapter() {
7563af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            private boolean mWasCancelled;
7573af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7583af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
7593af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationEnd(Animator animation) {
7603af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimatorSet.remove(animation);
7613af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
7623af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                    onAnimationFinished();
7633af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                }
7643af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mAnimationListenerPool.push(this);
7653af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
7663af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7673af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
7683af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationCancel(Animator animation) {
7693af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = true;
7703af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
7713af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
7723af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            @Override
7733af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            public void onAnimationStart(Animator animation) {
7743af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek                mWasCancelled = false;
7753af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            }
7763af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        };
7773af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek    }
7783af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek
779aac932591d7aa05bae61d2b47ed7647f35da0001Selim Cinek    public static <T> T getChildTag(View child, int tag) {
780eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        return (T) child.getTag(tag);
781eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
782eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
783eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
784eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Cancel the previous animator and get the duration of the new animation.
78539610545f0c2714a3526bc935effe57b421542d1Selim Cinek     *
78660d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi     * @param duration the new duration
787eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param previousAnimator the animator which was running before
788eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @return the new duration
789eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
79060d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi    private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
79160d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi        long newDuration = duration;
792eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        if (previousAnimator != null) {
7938df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // We take either the desired length of the new animation or the remaining time of
7948df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            // the previous animator, whichever is longer.
7958df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek            newDuration = Math.max(previousAnimator.getDuration()
7968df56452cb696ebdee82df6fb255892eabf3febcSelim Cinek                    - previousAnimator.getCurrentPlayTime(), newDuration);
7973af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek            previousAnimator.cancel();
798eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
7993af00cf10660c7fdc0582dc12361c13673d0c9bbSelim Cinek        return newDuration;
800eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
801eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
802eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void onAnimationFinished() {
803eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        mHostLayout.onChildAnimationFinished();
8048f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek        for (View v : mChildrenToClearFromOverlay) {
8058f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek            mHostLayout.getOverlay().remove(v);
8068f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek        }
8078f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek        mChildrenToClearFromOverlay.clear();
808eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
809eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
810eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
811eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Process the animationEvents for a new animation
812eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     *
813eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * @param animationEvents the animation events for the animation to perform
81439610545f0c2714a3526bc935effe57b421542d1Selim Cinek     * @param finalState the final state to animate to
81539610545f0c2714a3526bc935effe57b421542d1Selim Cinek     */
816eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    private void processAnimationEvents(
8170dd6881ea481c855976214807c17595b34a2920aJorim Jaggi            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
818572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            StackScrollState finalState) {
8198d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
8208efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            final ExpandableView changingView = (ExpandableView) event.changingView;
8218efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            if (event.animationType ==
8228efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
8238efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
8248efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // This item is added, initialize it's properties.
825b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                StackViewState viewState = finalState
8268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(changingView);
8278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState == null) {
8288efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The position for this child was never generated, let's continue.
8298efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
8308efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
8318efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
8328efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // The view was set to gone but the state never removed
8338efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    finalState.removeViewStateForView(changingView);
8348efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
83539610545f0c2714a3526bc935effe57b421542d1Selim Cinek                }
836b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                finalState.applyState(changingView, viewState);
8378efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                mNewAddChildren.add(changingView);
8388efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
8398efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            } else if (event.animationType ==
8408efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
8418efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (changingView.getVisibility() == View.GONE) {
84295ed59283bd25fb363d13c000a7408bcafb5e93eSelim Cinek                    mHostLayout.getOverlay().remove(changingView);
8438efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    continue;
8448efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
8458efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
8468efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // Find the amount to translate up. This is needed in order to understand the
8478efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // direction of the remove animation (either downwards or upwards)
848b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek                StackViewState viewState = finalState
8498efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        .getViewStateForView(event.viewAfterChangingView);
8508efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                int actualHeight = changingView.getActualHeight();
8518efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                // upwards by default
8528efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                float translationDirection = -1.0f;
8538efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                if (viewState != null) {
8548efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // there was a view after this one, Approximate the distance the next child
8558efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    // travelled
8568efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = ((viewState.yTranslation
8578efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
8588efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                            actualHeight);
8598efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
8608efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek
8618efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                }
86260d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
86360d07c597c3f996deb3f2743466fe5279ca15e8dJorim Jaggi                        translationDirection, new Runnable() {
8648efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    @Override
8658efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    public void run() {
8668efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        // remove the temporary overlay
8678efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                        mHostLayout.getOverlay().remove(changingView);
8688efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                    }
8698efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                });
870b036ca4de8e93d83bcdc093fbf8f096dc18a810dSelim Cinek            } else if (event.animationType ==
871f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
872f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                // A race condition can trigger the view to be added to the overlay even though
873f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                // it is swiped out. So let's remove it
874f336f4c13ad3be15e2b44266cd786685975425f2Selim Cinek                mHostLayout.getOverlay().remove(changingView);
875b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            } else if (event.animationType == NotificationStackScrollLayout
876b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                    .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
877b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
878b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                row.prepareExpansionChanged(finalState);
879b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek                mChildExpandingView = row;
880b8f09cf5533d458868a335ce334e4880b2b0788dSelim Cinek            } else if (event.animationType == NotificationStackScrollLayout
881b8f09cf5533d458868a335ce334e4880b2b0788dSelim Cinek                    .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
882b8f09cf5533d458868a335ce334e4880b2b0788dSelim Cinek                // This item is added, initialize it's properties.
883b8f09cf5533d458868a335ce334e4880b2b0788dSelim Cinek                StackViewState viewState = finalState.getViewStateForView(changingView);
884b8f09cf5533d458868a335ce334e4880b2b0788dSelim Cinek                mTmpState.copyFrom(viewState);
885a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                if (event.headsUpFromBottom) {
886a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                    mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
887a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                } else {
888a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                    mTmpState.yTranslation = -mTmpState.height;
889a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                }
890a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                mHeadsUpAppearChildren.add(changingView);
891b8f09cf5533d458868a335ce334e4880b2b0788dSelim Cinek                finalState.applyState(changingView, mTmpState);
892a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek            } else if (event.animationType == NotificationStackScrollLayout
893a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                    .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
894a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek                mHeadsUpDisappearChildren.add(changingView);
8958f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                if (mHostLayout.indexOfChild(changingView) == -1) {
8968f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                    // This notification was actually removed, so we need to add it to the overlay
8978f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                    mHostLayout.getOverlay().add(changingView);
898eaee9c01902ecfc253be98d68e5d7b586ed54463Selim Cinek                    mTmpState.initFrom(changingView);
899eaee9c01902ecfc253be98d68e5d7b586ed54463Selim Cinek                    mTmpState.yTranslation = -changingView.getActualHeight();
9008f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                    // We temporarily enable Y animations, the real filter will be combined
9018f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                    // afterwards anyway
9028f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                    mAnimationFilter.animateY = true;
903eaee9c01902ecfc253be98d68e5d7b586ed54463Selim Cinek                    startViewAnimations(changingView, mTmpState, 0,
9048f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                            ANIMATION_DURATION_HEADS_UP_DISAPPEAR);
9058f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                    mChildrenToClearFromOverlay.add(changingView);
9068f93763cf0f4b3c41f6b0ecac09b621eb0ca328bSelim Cinek                }
907572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek            }
9088efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek            mNewEvents.add(event);
909572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek        }
910572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek    }
9118d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
912475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi    public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
913475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi            final boolean isRubberbanded) {
9148d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
9159012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        if (targetAmount == startOverScrollAmount) {
9169012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi            return;
9179012958742c7a66b37ba5f2196f9086bb1980e6bJorim Jaggi        }
9188d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        cancelOverScrollAnimators(onTop);
9198d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
9208d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                targetAmount);
9218d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
9228d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
9238d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            @Override
9248d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            public void onAnimationUpdate(ValueAnimator animation) {
9258d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                float currentOverScroll = (float) animation.getAnimatedValue();
92647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                mHostLayout.setOverScrollAmount(
927475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
928475b21dfe517ec04f435f6b02f4a53083d040db4Jorim Jaggi                        isRubberbanded);
9298d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            }
9308d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        });
9318d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
93247c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
93347c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            @Override
93447c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            public void onAnimationEnd(Animator animation) {
93547c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                if (onTop) {
93647c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mTopOverScrollAnimator = null;
93747c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                } else {
93847c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                    mBottomOverScrollAnimator = null;
93947c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi                }
94047c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi            }
94147c85a3525dcd0bbd3168632830e8ab491d18462Jorim Jaggi        });
9428d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        overScrollAnimator.start();
9438d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (onTop) {
9448d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mTopOverScrollAnimator = overScrollAnimator;
9458d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        } else {
9468d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            mBottomOverScrollAnimator = overScrollAnimator;
9478d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
9488d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
9498d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek
9508d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    public void cancelOverScrollAnimators(boolean onTop) {
9518d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
9528d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        if (currentAnimator != null) {
9538d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek            currentAnimator.cancel();
9548d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        }
9558d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek    }
95602af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek
95702af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    /**
95802af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek     * Get the end value of the height animation running on a view or the actualHeight
95902af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek     * if no animation is running.
96002af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek     */
96102af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    public static int getFinalActualHeight(ExpandableView view) {
96202af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        if (view == null) {
96302af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            return 0;
96402af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        }
96502af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
96602af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        if (heightAnimator == null) {
96702af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            return view.getActualHeight();
96802af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        } else {
96902af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            return getChildTag(view, TAG_END_HEIGHT);
97002af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek        }
97102af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    }
972a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek
973a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
974a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
975a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    }
976a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek
977a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    public void setShadeExpanded(boolean shadeExpanded) {
978a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek        mShadeExpanded = shadeExpanded;
979a59ecc3401de0c4bf1e13665158f54669f22d06cSelim Cinek    }
980572bbd42a473980c2d59af80d378f6270ba6860aSelim Cinek}
981