Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/AnimationHelper.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/AnimationHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/AnimationHelper.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..931772af56d15ecc39e9ecdbfa5f3bdbce0344ed |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/AnimationHelper.java |
@@ -0,0 +1,244 @@ |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.infobar; |
+ |
+import android.animation.Animator; |
+import android.animation.AnimatorListenerAdapter; |
+import android.animation.AnimatorSet; |
+import android.animation.ObjectAnimator; |
+import android.animation.PropertyValuesHolder; |
+import android.view.View; |
+import android.view.ViewTreeObserver; |
+import android.view.animation.AccelerateDecelerateInterpolator; |
+ |
+import org.chromium.base.ApiCompatibilityUtils; |
+ |
+import java.util.ArrayList; |
+ |
+/** |
+ * Sets up animations to move InfoBars around inside of the InfoBarContainer. |
+ * |
+ * Animations proceed in several phases: |
+ * 1) Prep work is done for the InfoBar so that the View being animated in (if it exists) is |
+ * properly sized. This involves adding the View to a FrameLayout with a visibility of |
+ * INVISIBLE and triggering a layout. |
+ * |
+ * 2) Once the View has an actual size, we compute all of the actions needed for the animation. |
+ * We use translations primarily to slide things in and out of the screen as things are shown, |
+ * hidden, or resized. |
+ * |
+ * 3) The animation is kicked off and the animations run. During this phase, the View being shown |
+ * is added to ContentWrapperView. |
+ * |
+ * 4) At the end of the animation, we clean up everything and make sure all the children are in the |
+ * right places. |
+ */ |
+public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener { |
+ private static final String TAG = "AnimationHelper"; |
+ |
+ private static final long ANIMATION_DURATION_MS = 250; |
+ |
+ public static final int ANIMATION_TYPE_SHOW = 0; |
+ public static final int ANIMATION_TYPE_SWAP = 1; |
+ public static final int ANIMATION_TYPE_HIDE = 2; |
+ public static final int ANIMATION_TYPE_BOUNDARY = 3; |
+ |
+ private final InfoBarContainer mContainer; |
+ private final InfoBar mInfoBar; |
+ private final ContentWrapperView mTargetWrapperView; |
+ private final AnimatorSet mAnimatorSet; |
+ private final int mAnimationType; |
+ private final View mToShow; |
+ |
+ private boolean mAnimationStarted; |
+ |
+ /** |
+ * Creates and starts an animation. |
+ * @param container InfoBarContainer that is having its InfoBars animated. |
+ * @param target ContentWrapperView that is the focus of the animation and is being resized, |
+ * shown, or hidden. |
+ * @param infoBar InfoBar that goes with the specified ContentWrapperView. |
+ * @param toShow If non-null, this View will replace whatever child View the ContentWrapperView |
+ * is currently displaying. |
+ * @param aniamtionType Type of animation being performed. |
+ */ |
+ public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar, |
+ View toShow, int animationType) { |
+ mContainer = container; |
+ mInfoBar = infoBar; |
+ mTargetWrapperView = target; |
+ mAnimatorSet = new AnimatorSet(); |
+ mAnimationType = animationType; |
+ mToShow = toShow; |
+ assert mContainer.indexOfChild(mTargetWrapperView) != -1; |
+ } |
+ |
+ /** |
+ * Start the animation. |
+ */ |
+ public void start() { |
+ mTargetWrapperView.prepareTransition(mToShow); |
+ mContainer.prepareTransition(mToShow); |
+ |
+ if (mToShow == null) { |
+ // We've got a size already; start the animation immediately. |
+ continueAnimation(); |
+ } else { |
+ // Wait for the object to be sized. |
+ mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this); |
+ } |
+ } |
+ |
+ /** |
+ * @return the InfoBar being animated. |
+ */ |
+ public InfoBar getInfoBar() { |
+ return mInfoBar; |
+ } |
+ |
+ /** |
+ * @return the ContentWrapperView being animated. |
+ */ |
+ public ContentWrapperView getTarget() { |
+ return mTargetWrapperView; |
+ } |
+ |
+ /** |
+ * @return the type of animation being performed. |
+ */ |
+ public int getAnimationType() { |
+ return mAnimationType; |
+ } |
+ |
+ /** |
+ * Catch when the layout occurs, which lets us know when the View has been sized properly. |
+ */ |
+ @Override |
+ public void onGlobalLayout() { |
+ ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this); |
+ continueAnimation(); |
+ } |
+ |
+ private void continueAnimation() { |
+ if (mAnimationStarted) return; |
+ mAnimationStarted = true; |
+ |
+ boolean infoBarsOnTop = mContainer.areInfoBarsOnTop(); |
+ int indexOfWrapperView = mContainer.indexOfChild(mTargetWrapperView); |
+ assert indexOfWrapperView != -1; |
+ |
+ ArrayList<Animator> animators = new ArrayList<Animator>(); |
+ mTargetWrapperView.getAnimationsForTransition(animators); |
+ |
+ // Determine where the tops of each InfoBar will need to be. |
+ int heightDifference = mTargetWrapperView.getTransitionHeightDifference(); |
+ int cumulativeTopStart = 0; |
+ int cumulativeTopEnd = 0; |
+ int cumulativeEndHeight = 0; |
+ if (!infoBarsOnTop) { |
+ if (heightDifference >= 0) { |
+ // The current container is smaller than the final container, so the current 0 |
+ // coordinate will be >= 0 in the final container. |
+ cumulativeTopStart = heightDifference; |
+ } else { |
+ // The current container is bigger than the final container, so the current 0 |
+ // coordinate will be < 0 in the final container. |
+ cumulativeTopEnd = -heightDifference; |
+ } |
+ } |
+ |
+ for (int i = 0; i < mContainer.getChildCount(); ++i) { |
+ View view = mContainer.getChildAt(i); |
+ |
+ // At this point, the View being transitioned in shouldn't have been added to the |
+ // visible container, yet, and shouldn't affect calculations. |
+ int startHeight = view.getHeight(); |
+ int endHeight = startHeight + (i == indexOfWrapperView ? heightDifference : 0); |
+ int topStart = cumulativeTopStart; |
+ int topEnd = cumulativeTopEnd; |
+ int bottomStart = topStart + startHeight; |
+ int bottomEnd = topEnd + endHeight; |
+ |
+ if (topStart == topEnd && bottomStart == bottomEnd) { |
+ // The View needs to stay put. |
+ view.setTop(topEnd); |
+ view.setBottom(bottomEnd); |
+ view.setY(topEnd); |
+ view.setTranslationY(0); |
+ } else { |
+ // A translation is required to move the View into place. |
+ int translation = heightDifference; |
+ if (infoBarsOnTop) translation *= -1; |
+ |
+ boolean translateDownward = false; |
+ if (topStart < topEnd) { |
+ translateDownward = infoBarsOnTop; |
+ } else if (topStart > topEnd) { |
+ translateDownward = !infoBarsOnTop; |
+ } else { |
+ translateDownward = bottomEnd > bottomStart; |
+ } |
+ |
+ PropertyValuesHolder viewTranslation; |
+ if (translateDownward) { |
+ view.setTop(topEnd); |
+ view.setBottom(bottomEnd); |
+ view.setTranslationY(translation); |
+ view.setY(topEnd + translation); |
+ viewTranslation = |
+ PropertyValuesHolder.ofFloat("translationY", translation, 0.0f); |
+ } else { |
+ viewTranslation = |
+ PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation); |
+ } |
+ |
+ animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation)); |
+ } |
+ |
+ // Add heights to the cumulative totals. |
+ cumulativeTopStart += startHeight; |
+ cumulativeTopEnd += endHeight; |
+ cumulativeEndHeight += endHeight; |
+ } |
+ |
+ // Lock the InfoBarContainer's size at its largest during the animation to avoid |
+ // clipping issues. |
+ final int oldContainerTop = mContainer.getTop(); |
+ final int oldContainerBottom = mContainer.getBottom(); |
+ final int newContainerTop; |
+ final int newContainerBottom; |
+ if (infoBarsOnTop) { |
+ newContainerTop = oldContainerTop; |
+ newContainerBottom = newContainerTop + cumulativeEndHeight; |
+ } else { |
+ newContainerBottom = oldContainerBottom; |
+ newContainerTop = newContainerBottom - cumulativeEndHeight; |
+ } |
+ final int biggestContainerTop = Math.min(oldContainerTop, newContainerTop); |
+ final int biggestContainerBottom = Math.max(oldContainerBottom, newContainerBottom); |
+ mContainer.setTop(biggestContainerTop); |
+ mContainer.setBottom(biggestContainerBottom); |
+ |
+ // Set up and run all of the animations. |
+ mAnimatorSet.addListener(new AnimatorListenerAdapter() { |
+ @Override |
+ public void onAnimationStart(Animator animation) { |
+ mTargetWrapperView.startTransition(); |
+ mContainer.startTransition(); |
+ } |
+ |
+ @Override |
+ public void onAnimationEnd(Animator animation) { |
+ mTargetWrapperView.finishTransition(); |
+ mContainer.finishTransition(); |
+ } |
+ }); |
+ |
+ mAnimatorSet.playTogether(animators); |
+ mAnimatorSet.setDuration(ANIMATION_DURATION_MS); |
+ mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); |
+ mAnimatorSet.start(); |
+ } |
+} |