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