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;
16 import android.widget.LinearLayout;
18 import org.chromium.base.ApiCompatibilityUtils;
20 import java.util.ArrayList;
23 * Sets up animations to move InfoBars around inside of the InfoBarContainer.
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.
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,
34 * 3) The animation is kicked off and the animations run. During this phase, the View being shown
35 * is added to ContentWrapperView.
37 * 4) At the end of the animation, we clean up everything and make sure all the children are in the
40 public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener {
41 private static final long ANIMATION_DURATION_MS = 250;
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;
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;
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 animationType Type of animation being performed.
68 public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar,
69 View toShow, int animationType) {
70 mContainer = container;
71 mLinearLayout = container.getLinearLayout();
73 mTargetWrapperView = target;
74 mAnimatorSet = new AnimatorSet();
75 mAnimationType = animationType;
77 assert mLinearLayout.indexOfChild(mTargetWrapperView) != -1;
81 * Start the animation.
84 mTargetWrapperView.prepareTransition(mToShow);
85 mContainer.prepareTransition(mToShow);
87 if (mToShow == null) {
88 // We've got a size already; start the animation immediately.
91 // Wait for the object to be sized.
92 mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this);
97 * @return the InfoBar being animated.
99 public InfoBar getInfoBar() {
104 * @return the ContentWrapperView being animated.
106 public ContentWrapperView getTarget() {
107 return mTargetWrapperView;
111 * @return the type of animation being performed.
113 public int getAnimationType() {
114 return mAnimationType;
118 * Catch when the layout occurs, which lets us know when the View has been sized properly.
121 public void onGlobalLayout() {
122 ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this);
126 private void continueAnimation() {
127 if (mAnimationStarted) return;
128 mAnimationStarted = true;
130 int indexOfWrapperView = mLinearLayout.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 (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;
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;
151 for (int i = 0; i < mLinearLayout.getChildCount(); ++i) {
152 View view = mLinearLayout.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;
173 boolean translateDownward;
174 if (topStart < topEnd) {
175 translateDownward = false;
176 } else if (topStart > topEnd) {
177 translateDownward = true;
179 translateDownward = bottomEnd > bottomStart;
182 PropertyValuesHolder viewTranslation;
183 if (translateDownward) {
185 view.setBottom(bottomEnd);
186 view.setTranslationY(translation);
187 view.setY(topEnd + translation);
189 PropertyValuesHolder.ofFloat("translationY", translation, 0.0f);
192 PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation);
195 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation));
198 // Add heights to the cumulative totals.
199 cumulativeTopStart += startHeight;
200 cumulativeTopEnd += endHeight;
201 cumulativeEndHeight += endHeight;
204 // Lock the InfoBarContainer's size at its largest during the animation to avoid
206 int oldContainerTop = mLinearLayout.getTop();
207 int newContainerTop = mLinearLayout.getBottom() - cumulativeEndHeight;
208 int biggestContainerTop = Math.min(oldContainerTop, newContainerTop);
209 mLinearLayout.setTop(biggestContainerTop);
211 // Set up and run all of the animations.
212 mAnimatorSet.addListener(new AnimatorListenerAdapter() {
214 public void onAnimationStart(Animator animation) {
215 mTargetWrapperView.startTransition();
219 public void onAnimationEnd(Animator animation) {
220 mTargetWrapperView.finishTransition();
221 mContainer.finishTransition();
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());
231 mAnimatorSet.playTogether(animators);
232 mAnimatorSet.setDuration(ANIMATION_DURATION_MS);
233 mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
234 mAnimatorSet.start();