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 long ANIMATION_DURATION_MS = 250;
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;
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;
54 private boolean mAnimationStarted;
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,
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.
66 public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar,
67 View toShow, int animationType) {
68 mContainer = container;
70 mTargetWrapperView = target;
71 mAnimatorSet = new AnimatorSet();
72 mAnimationType = animationType;
74 assert mContainer.indexOfChild(mTargetWrapperView) != -1;
78 * Start the animation.
81 mTargetWrapperView.prepareTransition(mToShow);
82 mContainer.prepareTransition(mToShow);
84 if (mToShow == null) {
85 // We've got a size already; start the animation immediately.
88 // Wait for the object to be sized.
89 mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this);
94 * @return the InfoBar being animated.
96 public InfoBar getInfoBar() {
101 * @return the ContentWrapperView being animated.
103 public ContentWrapperView getTarget() {
104 return mTargetWrapperView;
108 * @return the type of animation being performed.
110 public int getAnimationType() {
111 return mAnimationType;
115 * Catch when the layout occurs, which lets us know when the View has been sized properly.
118 public void onGlobalLayout() {
119 ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this);
123 private void continueAnimation() {
124 if (mAnimationStarted) return;
125 mAnimationStarted = true;
127 boolean infoBarsOnTop = mContainer.areInfoBarsOnTop();
128 int indexOfWrapperView = mContainer.indexOfChild(mTargetWrapperView);
129 assert indexOfWrapperView != -1;
131 ArrayList<Animator> animators = new ArrayList<Animator>();
132 mTargetWrapperView.getAnimationsForTransition(animators);
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;
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;
151 for (int i = 0; i < mContainer.getChildCount(); ++i) {
152 View view = mContainer.getChildAt(i);
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;
163 if (topStart == topEnd && bottomStart == bottomEnd) {
164 // The View needs to stay put.
166 view.setBottom(bottomEnd);
168 view.setTranslationY(0);
170 // A translation is required to move the View into place.
171 int translation = heightDifference;
172 if (infoBarsOnTop) translation *= -1;
174 boolean translateDownward = false;
175 if (topStart < topEnd) {
176 translateDownward = infoBarsOnTop;
177 } else if (topStart > topEnd) {
178 translateDownward = !infoBarsOnTop;
180 translateDownward = bottomEnd > bottomStart;
183 PropertyValuesHolder viewTranslation;
184 if (translateDownward) {
186 view.setBottom(bottomEnd);
187 view.setTranslationY(translation);
188 view.setY(topEnd + translation);
190 PropertyValuesHolder.ofFloat("translationY", translation, 0.0f);
193 PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation);
196 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation));
199 // Add heights to the cumulative totals.
200 cumulativeTopStart += startHeight;
201 cumulativeTopEnd += endHeight;
202 cumulativeEndHeight += endHeight;
205 // Lock the InfoBarContainer's size at its largest during the animation to avoid
207 final int oldContainerTop = mContainer.getTop();
208 final int oldContainerBottom = mContainer.getBottom();
209 final int newContainerTop;
210 final int newContainerBottom;
212 newContainerTop = oldContainerTop;
213 newContainerBottom = newContainerTop + cumulativeEndHeight;
215 newContainerBottom = oldContainerBottom;
216 newContainerTop = newContainerBottom - cumulativeEndHeight;
218 final int biggestContainerTop = Math.min(oldContainerTop, newContainerTop);
219 final int biggestContainerBottom = Math.max(oldContainerBottom, newContainerBottom);
220 mContainer.setTop(biggestContainerTop);
221 mContainer.setBottom(biggestContainerBottom);
223 // Set up and run all of the animations.
224 mAnimatorSet.addListener(new AnimatorListenerAdapter() {
226 public void onAnimationStart(Animator animation) {
227 mTargetWrapperView.startTransition();
228 mContainer.startTransition();
232 public void onAnimationEnd(Animator animation) {
233 mTargetWrapperView.finishTransition();
234 mContainer.finishTransition();
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()));
245 mAnimatorSet.playTogether(animators);
246 mAnimatorSet.setDuration(ANIMATION_DURATION_MS);
247 mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
248 mAnimatorSet.start();