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 5package org.chromium.chrome.browser.infobar; 6 7import android.animation.Animator; 8import android.animation.AnimatorListenerAdapter; 9import android.animation.AnimatorSet; 10import android.animation.ObjectAnimator; 11import android.animation.PropertyValuesHolder; 12import android.os.Build; 13import android.view.View; 14import android.view.ViewTreeObserver; 15import android.view.animation.AccelerateDecelerateInterpolator; 16 17import org.chromium.base.ApiCompatibilityUtils; 18 19import 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 */ 39public 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 */ 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 */ 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 */ 96 public InfoBar getInfoBar() { 97 return mInfoBar; 98 } 99 100 /** 101 * @return the ContentWrapperView being animated. 102 */ 103 public ContentWrapperView getTarget() { 104 return mTargetWrapperView; 105 } 106 107 /** 108 * @return the type of animation being performed. 109 */ 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 118 public void onGlobalLayout() { 119 ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this); 120 continueAnimation(); 121 } 122 123 private void continueAnimation() { 124 if (mAnimationStarted) return; 125 mAnimationStarted = true; 126 127 boolean infoBarsOnTop = mContainer.areInfoBarsOnTop(); 128 int indexOfWrapperView = mContainer.indexOfChild(mTargetWrapperView); 129 assert indexOfWrapperView != -1; 130 131 ArrayList<Animator> animators = new ArrayList<Animator>(); 132 mTargetWrapperView.getAnimationsForTransition(animators); 133 134 // Determine where the tops of each InfoBar will need to be. 135 int heightDifference = mTargetWrapperView.getTransitionHeightDifference(); 136 int cumulativeTopStart = 0; 137 int cumulativeTopEnd = 0; 138 int cumulativeEndHeight = 0; 139 if (!infoBarsOnTop) { 140 if (heightDifference >= 0) { 141 // The current container is smaller than the final container, so the current 0 142 // coordinate will be >= 0 in the final container. 143 cumulativeTopStart = heightDifference; 144 } else { 145 // The current container is bigger than the final container, so the current 0 146 // coordinate will be < 0 in the final container. 147 cumulativeTopEnd = -heightDifference; 148 } 149 } 150 151 for (int i = 0; i < mContainer.getChildCount(); ++i) { 152 View view = mContainer.getChildAt(i); 153 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; 162 163 if (topStart == topEnd && bottomStart == bottomEnd) { 164 // The View needs to stay put. 165 view.setTop(topEnd); 166 view.setBottom(bottomEnd); 167 view.setY(topEnd); 168 view.setTranslationY(0); 169 } else { 170 // A translation is required to move the View into place. 171 int translation = heightDifference; 172 if (infoBarsOnTop) translation *= -1; 173 174 boolean translateDownward = false; 175 if (topStart < topEnd) { 176 translateDownward = infoBarsOnTop; 177 } else if (topStart > topEnd) { 178 translateDownward = !infoBarsOnTop; 179 } else { 180 translateDownward = bottomEnd > bottomStart; 181 } 182 183 PropertyValuesHolder viewTranslation; 184 if (translateDownward) { 185 view.setTop(topEnd); 186 view.setBottom(bottomEnd); 187 view.setTranslationY(translation); 188 view.setY(topEnd + translation); 189 viewTranslation = 190 PropertyValuesHolder.ofFloat("translationY", translation, 0.0f); 191 } else { 192 viewTranslation = 193 PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation); 194 } 195 196 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation)); 197 } 198 199 // Add heights to the cumulative totals. 200 cumulativeTopStart += startHeight; 201 cumulativeTopEnd += endHeight; 202 cumulativeEndHeight += endHeight; 203 } 204 205 // Lock the InfoBarContainer's size at its largest during the animation to avoid 206 // clipping issues. 207 final int oldContainerTop = mContainer.getTop(); 208 final int oldContainerBottom = mContainer.getBottom(); 209 final int newContainerTop; 210 final int newContainerBottom; 211 if (infoBarsOnTop) { 212 newContainerTop = oldContainerTop; 213 newContainerBottom = newContainerTop + cumulativeEndHeight; 214 } else { 215 newContainerBottom = oldContainerBottom; 216 newContainerTop = newContainerBottom - cumulativeEndHeight; 217 } 218 final int biggestContainerTop = Math.min(oldContainerTop, newContainerTop); 219 final int biggestContainerBottom = Math.max(oldContainerBottom, newContainerBottom); 220 mContainer.setTop(biggestContainerTop); 221 mContainer.setBottom(biggestContainerBottom); 222 223 // Set up and run all of the animations. 224 mAnimatorSet.addListener(new AnimatorListenerAdapter() { 225 @Override 226 public void onAnimationStart(Animator animation) { 227 mTargetWrapperView.startTransition(); 228 mContainer.startTransition(); 229 } 230 231 @Override 232 public void onAnimationEnd(Animator animation) { 233 mTargetWrapperView.finishTransition(); 234 mContainer.finishTransition(); 235 236 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null && 237 (mAnimationType == ANIMATION_TYPE_SHOW || 238 mAnimationType == ANIMATION_TYPE_SWAP)) { 239 mToShow.announceForAccessibility( 240 mInfoBar.getMessageText(mContainer.getContext())); 241 } 242 } 243 }); 244 245 mAnimatorSet.playTogether(animators); 246 mAnimatorSet.setDuration(ANIMATION_DURATION_MS); 247 mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 248 mAnimatorSet.start(); 249 } 250} 251