Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / infobar / AnimationHelper.java
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.infobar;
6
7 import android.animation.Animator;
8 import android.animation.AnimatorListenerAdapter;
9 import android.animation.AnimatorSet;
10 import android.animation.ObjectAnimator;
11 import android.animation.PropertyValuesHolder;
12 import android.os.Build;
13 import android.view.View;
14 import android.view.ViewTreeObserver;
15 import android.view.animation.AccelerateDecelerateInterpolator;
16 import android.widget.LinearLayout;
17
18 import org.chromium.base.ApiCompatibilityUtils;
19
20 import java.util.ArrayList;
21
22 /**
23  * Sets up animations to move InfoBars around inside of the InfoBarContainer.
24  *
25  * Animations proceed in several phases:
26  * 1) Prep work is done for the InfoBar so that the View being animated in (if it exists) is
27  *    properly sized.  This involves adding the View to a FrameLayout with a visibility of
28  *    INVISIBLE and triggering a layout.
29  *
30  * 2) Once the View has an actual size, we compute all of the actions needed for the animation.
31  *    We use translations primarily to slide things in and out of the screen as things are shown,
32  *    hidden, or resized.
33  *
34  * 3) The animation is kicked off and the animations run.  During this phase, the View being shown
35  *    is added to ContentWrapperView.
36  *
37  * 4) At the end of the animation, we clean up everything and make sure all the children are in the
38  *    right places.
39  */
40 public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener {
41     private static final long ANIMATION_DURATION_MS = 250;
42
43     public static final int ANIMATION_TYPE_SHOW = 0;
44     public static final int ANIMATION_TYPE_SWAP = 1;
45     public static final int ANIMATION_TYPE_HIDE = 2;
46     public static final int ANIMATION_TYPE_BOUNDARY = 3;
47
48     private final InfoBarContainer mContainer;
49     private final LinearLayout mLinearLayout;
50     private final InfoBar mInfoBar;
51     private final ContentWrapperView mTargetWrapperView;
52     private final AnimatorSet mAnimatorSet;
53     private final int mAnimationType;
54     private final View mToShow;
55
56     private boolean mAnimationStarted;
57
58     /**
59      * Creates and starts an animation.
60      * @param container InfoBarContainer that is having its InfoBars animated.
61      * @param target ContentWrapperView that is the focus of the animation and is being resized,
62      *               shown, or hidden.
63      * @param infoBar InfoBar that goes with the specified ContentWrapperView.
64      * @param toShow If non-null, this View will replace whatever child View the ContentWrapperView
65      *               is currently displaying.
66      * @param animationType Type of animation being performed.
67      */
68     public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar,
69             View toShow, int animationType) {
70         mContainer = container;
71         mLinearLayout = container.getLinearLayout();
72         mInfoBar = infoBar;
73         mTargetWrapperView = target;
74         mAnimatorSet = new AnimatorSet();
75         mAnimationType = animationType;
76         mToShow = toShow;
77         assert mLinearLayout.indexOfChild(mTargetWrapperView) != -1;
78     }
79
80     /**
81      * Start the animation.
82      */
83     public void start() {
84         mTargetWrapperView.prepareTransition(mToShow);
85         mContainer.prepareTransition(mToShow);
86
87         if (mToShow == null) {
88             // We've got a size already; start the animation immediately.
89             continueAnimation();
90         } else {
91             // Wait for the object to be sized.
92             mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this);
93         }
94     }
95
96     /**
97      * @return the InfoBar being animated.
98      */
99     public InfoBar getInfoBar() {
100         return mInfoBar;
101     }
102
103     /**
104      * @return the ContentWrapperView being animated.
105      */
106     public ContentWrapperView getTarget() {
107         return mTargetWrapperView;
108     }
109
110     /**
111      * @return the type of animation being performed.
112      */
113     public int getAnimationType() {
114         return mAnimationType;
115     }
116
117     /**
118      * Catch when the layout occurs, which lets us know when the View has been sized properly.
119      */
120     @Override
121     public void onGlobalLayout() {
122         ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this);
123         continueAnimation();
124     }
125
126     private void continueAnimation() {
127         if (mAnimationStarted) return;
128         mAnimationStarted = true;
129
130         int indexOfWrapperView = mLinearLayout.indexOfChild(mTargetWrapperView);
131         assert indexOfWrapperView != -1;
132
133         ArrayList<Animator> animators = new ArrayList<Animator>();
134         mTargetWrapperView.getAnimationsForTransition(animators);
135
136         // Determine where the tops of each InfoBar will need to be.
137         int heightDifference = mTargetWrapperView.getTransitionHeightDifference();
138         int cumulativeTopStart = 0;
139         int cumulativeTopEnd = 0;
140         int cumulativeEndHeight = 0;
141         if (heightDifference >= 0) {
142             // The current container is smaller than the final container, so the current 0
143             // coordinate will be >= 0 in the final container.
144             cumulativeTopStart = heightDifference;
145         } else {
146             // The current container is bigger than the final container, so the current 0
147             // coordinate will be < 0 in the final container.
148             cumulativeTopEnd = -heightDifference;
149         }
150
151         for (int i = 0; i < mLinearLayout.getChildCount(); ++i) {
152             View view = mLinearLayout.getChildAt(i);
153
154             // At this point, the View being transitioned in shouldn't have been added to the
155             // visible container, yet, and shouldn't affect calculations.
156             int startHeight = view.getHeight();
157             int endHeight = startHeight + (i == indexOfWrapperView ? heightDifference : 0);
158             int topStart = cumulativeTopStart;
159             int topEnd = cumulativeTopEnd;
160             int bottomStart = topStart + startHeight;
161             int bottomEnd = topEnd + endHeight;
162
163             if (topStart == topEnd && bottomStart == bottomEnd) {
164                 // The View needs to stay put.
165                 view.setTop(topEnd);
166                 view.setBottom(bottomEnd);
167                 view.setY(topEnd);
168                 view.setTranslationY(0);
169             } else {
170                 // A translation is required to move the View into place.
171                 int translation = heightDifference;
172
173                 boolean translateDownward;
174                 if (topStart < topEnd) {
175                     translateDownward = false;
176                 } else if (topStart > topEnd) {
177                     translateDownward = true;
178                 } else {
179                     translateDownward = bottomEnd > bottomStart;
180                 }
181
182                 PropertyValuesHolder viewTranslation;
183                 if (translateDownward) {
184                     view.setTop(topEnd);
185                     view.setBottom(bottomEnd);
186                     view.setTranslationY(translation);
187                     view.setY(topEnd + translation);
188                     viewTranslation =
189                             PropertyValuesHolder.ofFloat("translationY", translation, 0.0f);
190                 } else {
191                     viewTranslation =
192                             PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation);
193                 }
194
195                 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation));
196             }
197
198             // Add heights to the cumulative totals.
199             cumulativeTopStart += startHeight;
200             cumulativeTopEnd += endHeight;
201             cumulativeEndHeight += endHeight;
202         }
203
204         // Lock the InfoBarContainer's size at its largest during the animation to avoid
205         // clipping issues.
206         int oldContainerTop = mLinearLayout.getTop();
207         int newContainerTop = mLinearLayout.getBottom() - cumulativeEndHeight;
208         int biggestContainerTop = Math.min(oldContainerTop, newContainerTop);
209         mLinearLayout.setTop(biggestContainerTop);
210
211         // Set up and run all of the animations.
212         mAnimatorSet.addListener(new AnimatorListenerAdapter() {
213             @Override
214             public void onAnimationStart(Animator animation) {
215                 mTargetWrapperView.startTransition();
216             }
217
218             @Override
219             public void onAnimationEnd(Animator animation) {
220                 mTargetWrapperView.finishTransition();
221                 mContainer.finishTransition();
222
223                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null &&
224                         (mAnimationType == ANIMATION_TYPE_SHOW ||
225                                 mAnimationType == ANIMATION_TYPE_SWAP)) {
226                     mToShow.announceForAccessibility(mInfoBar.getMessage());
227                 }
228             }
229         });
230
231         mAnimatorSet.playTogether(animators);
232         mAnimatorSet.setDuration(ANIMATION_DURATION_MS);
233         mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
234         mAnimatorSet.start();
235     }
236 }