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. 4 5 package org.chromium.chrome.browser.infobar; 6 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 17 import org.chromium.base.ApiCompatibilityUtils; 18 19 import java.util.ArrayList; 20 21 /** 22 * Sets up animations to move InfoBars around inside of the InfoBarContainer. 23 * 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. 28 * 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, 31 * hidden, or resized. 32 * 33 * 3) The animation is kicked off and the animations run. During this phase, the View being shown 34 * is added to ContentWrapperView. 35 * 36 * 4) At the end of the animation, we clean up everything and make sure all the children are in the 37 * right places. 38 */ 39 public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener { 40 private static final long ANIMATION_DURATION_MS = 250; 41 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; 46 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; 53 54 private boolean mAnimationStarted; 55 56 /** 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, 60 * shown, or hidden. 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. 65 */ AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar, View toShow, int animationType)66 public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar, 67 View toShow, int animationType) { 68 mContainer = container; 69 mInfoBar = infoBar; 70 mTargetWrapperView = target; 71 mAnimatorSet = new AnimatorSet(); 72 mAnimationType = animationType; 73 mToShow = toShow; 74 assert mContainer.indexOfChild(mTargetWrapperView) != -1; 75 } 76 77 /** 78 * Start the animation. 79 */ start()80 public void start() { 81 mTargetWrapperView.prepareTransition(mToShow); 82 mContainer.prepareTransition(mToShow); 83 84 if (mToShow == null) { 85 // We've got a size already; start the animation immediately. 86 continueAnimation(); 87 } else { 88 // Wait for the object to be sized. 89 mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this); 90 } 91 } 92 93 /** 94 * @return the InfoBar being animated. 95 */ getInfoBar()96 public InfoBar getInfoBar() { 97 return mInfoBar; 98 } 99 100 /** 101 * @return the ContentWrapperView being animated. 102 */ getTarget()103 public ContentWrapperView getTarget() { 104 return mTargetWrapperView; 105 } 106 107 /** 108 * @return the type of animation being performed. 109 */ getAnimationType()110 public int getAnimationType() { 111 return mAnimationType; 112 } 113 114 /** 115 * Catch when the layout occurs, which lets us know when the View has been sized properly. 116 */ 117 @Override onGlobalLayout()118 public void onGlobalLayout() { 119 ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this); 120 continueAnimation(); 121 } 122 continueAnimation()123 private void continueAnimation() { 124 if (mAnimationStarted) return; 125 mAnimationStarted = true; 126 127 int indexOfWrapperView = mContainer.indexOfChild(mTargetWrapperView); 128 assert indexOfWrapperView != -1; 129 130 ArrayList<Animator> animators = new ArrayList<Animator>(); 131 mTargetWrapperView.getAnimationsForTransition(animators); 132 133 // Determine where the tops of each InfoBar will need to be. 134 int heightDifference = mTargetWrapperView.getTransitionHeightDifference(); 135 int cumulativeTopStart = 0; 136 int cumulativeTopEnd = 0; 137 int cumulativeEndHeight = 0; 138 if (heightDifference >= 0) { 139 // The current container is smaller than the final container, so the current 0 140 // coordinate will be >= 0 in the final container. 141 cumulativeTopStart = heightDifference; 142 } else { 143 // The current container is bigger than the final container, so the current 0 144 // coordinate will be < 0 in the final container. 145 cumulativeTopEnd = -heightDifference; 146 } 147 148 for (int i = 0; i < mContainer.getChildCount(); ++i) { 149 View view = mContainer.getChildAt(i); 150 151 // At this point, the View being transitioned in shouldn't have been added to the 152 // visible container, yet, and shouldn't affect calculations. 153 int startHeight = view.getHeight(); 154 int endHeight = startHeight + (i == indexOfWrapperView ? heightDifference : 0); 155 int topStart = cumulativeTopStart; 156 int topEnd = cumulativeTopEnd; 157 int bottomStart = topStart + startHeight; 158 int bottomEnd = topEnd + endHeight; 159 160 if (topStart == topEnd && bottomStart == bottomEnd) { 161 // The View needs to stay put. 162 view.setTop(topEnd); 163 view.setBottom(bottomEnd); 164 view.setY(topEnd); 165 view.setTranslationY(0); 166 } else { 167 // A translation is required to move the View into place. 168 int translation = heightDifference; 169 170 boolean translateDownward; 171 if (topStart < topEnd) { 172 translateDownward = false; 173 } else if (topStart > topEnd) { 174 translateDownward = true; 175 } else { 176 translateDownward = bottomEnd > bottomStart; 177 } 178 179 PropertyValuesHolder viewTranslation; 180 if (translateDownward) { 181 view.setTop(topEnd); 182 view.setBottom(bottomEnd); 183 view.setTranslationY(translation); 184 view.setY(topEnd + translation); 185 viewTranslation = 186 PropertyValuesHolder.ofFloat("translationY", translation, 0.0f); 187 } else { 188 viewTranslation = 189 PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation); 190 } 191 192 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation)); 193 } 194 195 // Add heights to the cumulative totals. 196 cumulativeTopStart += startHeight; 197 cumulativeTopEnd += endHeight; 198 cumulativeEndHeight += endHeight; 199 } 200 201 // Lock the InfoBarContainer's size at its largest during the animation to avoid 202 // clipping issues. 203 int oldContainerTop = mContainer.getTop(); 204 int newContainerTop = mContainer.getBottom() - cumulativeEndHeight; 205 int biggestContainerTop = Math.min(oldContainerTop, newContainerTop); 206 mContainer.setTop(biggestContainerTop); 207 208 // Set up and run all of the animations. 209 mAnimatorSet.addListener(new AnimatorListenerAdapter() { 210 @Override 211 public void onAnimationStart(Animator animation) { 212 mTargetWrapperView.startTransition(); 213 } 214 215 @Override 216 public void onAnimationEnd(Animator animation) { 217 mTargetWrapperView.finishTransition(); 218 mContainer.finishTransition(); 219 220 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null && 221 (mAnimationType == ANIMATION_TYPE_SHOW || 222 mAnimationType == ANIMATION_TYPE_SWAP)) { 223 mToShow.announceForAccessibility(mInfoBar.getMessage()); 224 } 225 } 226 }); 227 228 mAnimatorSet.playTogether(animators); 229 mAnimatorSet.setDuration(ANIMATION_DURATION_MS); 230 mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 231 mAnimatorSet.start(); 232 } 233 } 234