• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar, View toShow, int animationType)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      */
start()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      */
getInfoBar()96     public InfoBar getInfoBar() {
97         return mInfoBar;
98     }
99 
100     /**
101      * @return the ContentWrapperView being animated.
102      */
getTarget()103     public ContentWrapperView getTarget() {
104         return mTargetWrapperView;
105     }
106 
107     /**
108      * @return the type of animation being performed.
109      */
getAnimationType()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
onGlobalLayout()118     public void onGlobalLayout() {
119         ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this);
120         continueAnimation();
121     }
122 
continueAnimation()123     private void continueAnimation() {
124         if (mAnimationStarted) return;
125         mAnimationStarted = true;
126 
127         int indexOfWrapperView = mContainer.indexOfChild(mTargetWrapperView);
128         assert indexOfWrapperView != -1;
129 
130         ArrayList<Animator> animators = new ArrayList<Animator>();
131         mTargetWrapperView.getAnimationsForTransition(animators);
132 
133         // Determine where the tops of each InfoBar will need to be.
134         int heightDifference = mTargetWrapperView.getTransitionHeightDifference();
135         int cumulativeTopStart = 0;
136         int cumulativeTopEnd = 0;
137         int cumulativeEndHeight = 0;
138         if (heightDifference >= 0) {
139             // The current container is smaller than the final container, so the current 0
140             // coordinate will be >= 0 in the final container.
141             cumulativeTopStart = heightDifference;
142         } else {
143             // The current container is bigger than the final container, so the current 0
144             // coordinate will be < 0 in the final container.
145             cumulativeTopEnd = -heightDifference;
146         }
147 
148         for (int i = 0; i < mContainer.getChildCount(); ++i) {
149             View view = mContainer.getChildAt(i);
150 
151             // At this point, the View being transitioned in shouldn't have been added to the
152             // visible container, yet, and shouldn't affect calculations.
153             int startHeight = view.getHeight();
154             int endHeight = startHeight + (i == indexOfWrapperView ? heightDifference : 0);
155             int topStart = cumulativeTopStart;
156             int topEnd = cumulativeTopEnd;
157             int bottomStart = topStart + startHeight;
158             int bottomEnd = topEnd + endHeight;
159 
160             if (topStart == topEnd && bottomStart == bottomEnd) {
161                 // The View needs to stay put.
162                 view.setTop(topEnd);
163                 view.setBottom(bottomEnd);
164                 view.setY(topEnd);
165                 view.setTranslationY(0);
166             } else {
167                 // A translation is required to move the View into place.
168                 int translation = heightDifference;
169 
170                 boolean translateDownward;
171                 if (topStart < topEnd) {
172                     translateDownward = false;
173                 } else if (topStart > topEnd) {
174                     translateDownward = true;
175                 } else {
176                     translateDownward = bottomEnd > bottomStart;
177                 }
178 
179                 PropertyValuesHolder viewTranslation;
180                 if (translateDownward) {
181                     view.setTop(topEnd);
182                     view.setBottom(bottomEnd);
183                     view.setTranslationY(translation);
184                     view.setY(topEnd + translation);
185                     viewTranslation =
186                             PropertyValuesHolder.ofFloat("translationY", translation, 0.0f);
187                 } else {
188                     viewTranslation =
189                             PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation);
190                 }
191 
192                 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation));
193             }
194 
195             // Add heights to the cumulative totals.
196             cumulativeTopStart += startHeight;
197             cumulativeTopEnd += endHeight;
198             cumulativeEndHeight += endHeight;
199         }
200 
201         // Lock the InfoBarContainer's size at its largest during the animation to avoid
202         // clipping issues.
203         int oldContainerTop = mContainer.getTop();
204         int newContainerTop = mContainer.getBottom() - cumulativeEndHeight;
205         int biggestContainerTop = Math.min(oldContainerTop, newContainerTop);
206         mContainer.setTop(biggestContainerTop);
207 
208         // Set up and run all of the animations.
209         mAnimatorSet.addListener(new AnimatorListenerAdapter() {
210             @Override
211             public void onAnimationStart(Animator animation) {
212                 mTargetWrapperView.startTransition();
213             }
214 
215             @Override
216             public void onAnimationEnd(Animator animation) {
217                 mTargetWrapperView.finishTransition();
218                 mContainer.finishTransition();
219 
220                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null &&
221                         (mAnimationType == ANIMATION_TYPE_SHOW ||
222                                 mAnimationType == ANIMATION_TYPE_SWAP)) {
223                     mToShow.announceForAccessibility(mInfoBar.getMessage());
224                 }
225             }
226         });
227 
228         mAnimatorSet.playTogether(animators);
229         mAnimatorSet.setDuration(ANIMATION_DURATION_MS);
230         mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
231         mAnimatorSet.start();
232     }
233 }
234