| // Copyright 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.os.Build; |
| 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 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 animationType 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; |
| |
| 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 (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; |
| |
| boolean translateDownward; |
| if (topStart < topEnd) { |
| translateDownward = false; |
| } else if (topStart > topEnd) { |
| translateDownward = true; |
| } 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. |
| int oldContainerTop = mContainer.getTop(); |
| int newContainerTop = mContainer.getBottom() - cumulativeEndHeight; |
| int biggestContainerTop = Math.min(oldContainerTop, newContainerTop); |
| mContainer.setTop(biggestContainerTop); |
| |
| // Set up and run all of the animations. |
| mAnimatorSet.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| mTargetWrapperView.startTransition(); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mTargetWrapperView.finishTransition(); |
| mContainer.finishTransition(); |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null && |
| (mAnimationType == ANIMATION_TYPE_SHOW || |
| mAnimationType == ANIMATION_TYPE_SWAP)) { |
| mToShow.announceForAccessibility(mInfoBar.getMessage()); |
| } |
| } |
| }); |
| |
| mAnimatorSet.playTogether(animators); |
| mAnimatorSet.setDuration(ANIMATION_DURATION_MS); |
| mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); |
| mAnimatorSet.start(); |
| } |
| } |