blob: 7848fa5c02432342b72025786bb5b02a189c0c4f [file] [log] [blame]
// 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 android.widget.LinearLayout;
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 LinearLayout mLinearLayout;
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;
mLinearLayout = container.getLinearLayout();
mInfoBar = infoBar;
mTargetWrapperView = target;
mAnimatorSet = new AnimatorSet();
mAnimationType = animationType;
mToShow = toShow;
assert mLinearLayout.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 = mLinearLayout.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 < mLinearLayout.getChildCount(); ++i) {
View view = mLinearLayout.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 = mLinearLayout.getTop();
int newContainerTop = mLinearLayout.getBottom() - cumulativeEndHeight;
int biggestContainerTop = Math.min(oldContainerTop, newContainerTop);
mLinearLayout.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();
}
}