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.
5 package org.chromium.chrome.browser.infobar;
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;
17 import org.chromium.base.ApiCompatibilityUtils;
19 import java.util.ArrayList;
22 * Sets up animations to move InfoBars around inside of the InfoBarContainer.
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.
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,
33 * 3) The animation is kicked off and the animations run. During this phase, the View being shown
34 * is added to ContentWrapperView.
36 * 4) At the end of the animation, we clean up everything and make sure all the children are in the
39 public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener {
40 private static final String TAG = "AnimationHelper";
42 private static final long ANIMATION_DURATION_MS = 250;
44 public static final int ANIMATION_TYPE_SHOW = 0;
45 public static final int ANIMATION_TYPE_SWAP = 1;
46 public static final int ANIMATION_TYPE_HIDE = 2;
47 public static final int ANIMATION_TYPE_BOUNDARY = 3;
49 private final InfoBarContainer mContainer;
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;
56 private boolean mAnimationStarted;
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,
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 aniamtionType Type of animation being performed.
68 public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar,
69 View toShow, int animationType) {
70 mContainer = container;
72 mTargetWrapperView = target;
73 mAnimatorSet = new AnimatorSet();
74 mAnimationType = animationType;
76 assert mContainer.indexOfChild(mTargetWrapperView) != -1;
80 * Start the animation.
83 mTargetWrapperView.prepareTransition(mToShow);
84 mContainer.prepareTransition(mToShow);
86 if (mToShow == null) {
87 // We've got a size already; start the animation immediately.
90 // Wait for the object to be sized.
91 mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this);
96 * @return the InfoBar being animated.
98 public InfoBar getInfoBar() {
103 * @return the ContentWrapperView being animated.
105 public ContentWrapperView getTarget() {
106 return mTargetWrapperView;
110 * @return the type of animation being performed.
112 public int getAnimationType() {
113 return mAnimationType;
117 * Catch when the layout occurs, which lets us know when the View has been sized properly.
120 public void onGlobalLayout() {
121 ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this);
125 private void continueAnimation() {
126 if (mAnimationStarted) return;
127 mAnimationStarted = true;
129 boolean infoBarsOnTop = mContainer.areInfoBarsOnTop();
130 int indexOfWrapperView = mContainer.indexOfChild(mTargetWrapperView);
131 assert indexOfWrapperView != -1;
133 ArrayList<Animator> animators = new ArrayList<Animator>();
134 mTargetWrapperView.getAnimationsForTransition(animators);
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 (!infoBarsOnTop) {
142 if (heightDifference >= 0) {
143 // The current container is smaller than the final container, so the current 0
144 // coordinate will be >= 0 in the final container.
145 cumulativeTopStart = heightDifference;
147 // The current container is bigger than the final container, so the current 0
148 // coordinate will be < 0 in the final container.
149 cumulativeTopEnd = -heightDifference;
153 for (int i = 0; i < mContainer.getChildCount(); ++i) {
154 View view = mContainer.getChildAt(i);
156 // At this point, the View being transitioned in shouldn't have been added to the
157 // visible container, yet, and shouldn't affect calculations.
158 int startHeight = view.getHeight();
159 int endHeight = startHeight + (i == indexOfWrapperView ? heightDifference : 0);
160 int topStart = cumulativeTopStart;
161 int topEnd = cumulativeTopEnd;
162 int bottomStart = topStart + startHeight;
163 int bottomEnd = topEnd + endHeight;
165 if (topStart == topEnd && bottomStart == bottomEnd) {
166 // The View needs to stay put.
168 view.setBottom(bottomEnd);
170 view.setTranslationY(0);
172 // A translation is required to move the View into place.
173 int translation = heightDifference;
174 if (infoBarsOnTop) translation *= -1;
176 boolean translateDownward = false;
177 if (topStart < topEnd) {
178 translateDownward = infoBarsOnTop;
179 } else if (topStart > topEnd) {
180 translateDownward = !infoBarsOnTop;
182 translateDownward = bottomEnd > bottomStart;
185 PropertyValuesHolder viewTranslation;
186 if (translateDownward) {
188 view.setBottom(bottomEnd);
189 view.setTranslationY(translation);
190 view.setY(topEnd + translation);
192 PropertyValuesHolder.ofFloat("translationY", translation, 0.0f);
195 PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation);
198 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation));
201 // Add heights to the cumulative totals.
202 cumulativeTopStart += startHeight;
203 cumulativeTopEnd += endHeight;
204 cumulativeEndHeight += endHeight;
207 // Lock the InfoBarContainer's size at its largest during the animation to avoid
209 final int oldContainerTop = mContainer.getTop();
210 final int oldContainerBottom = mContainer.getBottom();
211 final int newContainerTop;
212 final int newContainerBottom;
214 newContainerTop = oldContainerTop;
215 newContainerBottom = newContainerTop + cumulativeEndHeight;
217 newContainerBottom = oldContainerBottom;
218 newContainerTop = newContainerBottom - cumulativeEndHeight;
220 final int biggestContainerTop = Math.min(oldContainerTop, newContainerTop);
221 final int biggestContainerBottom = Math.max(oldContainerBottom, newContainerBottom);
222 mContainer.setTop(biggestContainerTop);
223 mContainer.setBottom(biggestContainerBottom);
225 // Set up and run all of the animations.
226 mAnimatorSet.addListener(new AnimatorListenerAdapter() {
228 public void onAnimationStart(Animator animation) {
229 mTargetWrapperView.startTransition();
230 mContainer.startTransition();
234 public void onAnimationEnd(Animator animation) {
235 mTargetWrapperView.finishTransition();
236 mContainer.finishTransition();
238 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null &&
239 (mAnimationType == ANIMATION_TYPE_SHOW ||
240 mAnimationType == ANIMATION_TYPE_SWAP)) {
241 mToShow.announceForAccessibility(
242 mInfoBar.getMessageText(mContainer.getContext()));
247 mAnimatorSet.playTogether(animators);
248 mAnimatorSet.setDuration(ANIMATION_DURATION_MS);
249 mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
250 mAnimatorSet.start();