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; 16import android.widget.LinearLayout; 17 18import org.chromium.base.ApiCompatibilityUtils; 19 20import java.util.ArrayList; 21 22/** 23 * Sets up animations to move InfoBars around inside of the InfoBarContainer. 24 * 25 * Animations proceed in several phases: 26 * 1) Prep work is done for the InfoBar so that the View being animated in (if it exists) is 27 * properly sized. This involves adding the View to a FrameLayout with a visibility of 28 * INVISIBLE and triggering a layout. 29 * 30 * 2) Once the View has an actual size, we compute all of the actions needed for the animation. 31 * We use translations primarily to slide things in and out of the screen as things are shown, 32 * hidden, or resized. 33 * 34 * 3) The animation is kicked off and the animations run. During this phase, the View being shown 35 * is added to ContentWrapperView. 36 * 37 * 4) At the end of the animation, we clean up everything and make sure all the children are in the 38 * right places. 39 */ 40public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener { 41 private static final long ANIMATION_DURATION_MS = 250; 42 43 public static final int ANIMATION_TYPE_SHOW = 0; 44 public static final int ANIMATION_TYPE_SWAP = 1; 45 public static final int ANIMATION_TYPE_HIDE = 2; 46 public static final int ANIMATION_TYPE_BOUNDARY = 3; 47 48 private final InfoBarContainer mContainer; 49 private final LinearLayout mLinearLayout; 50 private final InfoBar mInfoBar; 51 private final ContentWrapperView mTargetWrapperView; 52 private final AnimatorSet mAnimatorSet; 53 private final int mAnimationType; 54 private final View mToShow; 55 56 private boolean mAnimationStarted; 57 58 /** 59 * Creates and starts an animation. 60 * @param container InfoBarContainer that is having its InfoBars animated. 61 * @param target ContentWrapperView that is the focus of the animation and is being resized, 62 * shown, or hidden. 63 * @param infoBar InfoBar that goes with the specified ContentWrapperView. 64 * @param toShow If non-null, this View will replace whatever child View the ContentWrapperView 65 * is currently displaying. 66 * @param animationType Type of animation being performed. 67 */ 68 public AnimationHelper(InfoBarContainer container, ContentWrapperView target, InfoBar infoBar, 69 View toShow, int animationType) { 70 mContainer = container; 71 mLinearLayout = container.getLinearLayout(); 72 mInfoBar = infoBar; 73 mTargetWrapperView = target; 74 mAnimatorSet = new AnimatorSet(); 75 mAnimationType = animationType; 76 mToShow = toShow; 77 assert mLinearLayout.indexOfChild(mTargetWrapperView) != -1; 78 } 79 80 /** 81 * Start the animation. 82 */ 83 public void start() { 84 mTargetWrapperView.prepareTransition(mToShow); 85 mContainer.prepareTransition(mToShow); 86 87 if (mToShow == null) { 88 // We've got a size already; start the animation immediately. 89 continueAnimation(); 90 } else { 91 // Wait for the object to be sized. 92 mTargetWrapperView.getViewTreeObserver().addOnGlobalLayoutListener(this); 93 } 94 } 95 96 /** 97 * @return the InfoBar being animated. 98 */ 99 public InfoBar getInfoBar() { 100 return mInfoBar; 101 } 102 103 /** 104 * @return the ContentWrapperView being animated. 105 */ 106 public ContentWrapperView getTarget() { 107 return mTargetWrapperView; 108 } 109 110 /** 111 * @return the type of animation being performed. 112 */ 113 public int getAnimationType() { 114 return mAnimationType; 115 } 116 117 /** 118 * Catch when the layout occurs, which lets us know when the View has been sized properly. 119 */ 120 @Override 121 public void onGlobalLayout() { 122 ApiCompatibilityUtils.removeOnGlobalLayoutListener(mTargetWrapperView, this); 123 continueAnimation(); 124 } 125 126 private void continueAnimation() { 127 if (mAnimationStarted) return; 128 mAnimationStarted = true; 129 130 int indexOfWrapperView = mLinearLayout.indexOfChild(mTargetWrapperView); 131 assert indexOfWrapperView != -1; 132 133 ArrayList<Animator> animators = new ArrayList<Animator>(); 134 mTargetWrapperView.getAnimationsForTransition(animators); 135 136 // Determine where the tops of each InfoBar will need to be. 137 int heightDifference = mTargetWrapperView.getTransitionHeightDifference(); 138 int cumulativeTopStart = 0; 139 int cumulativeTopEnd = 0; 140 int cumulativeEndHeight = 0; 141 if (heightDifference >= 0) { 142 // The current container is smaller than the final container, so the current 0 143 // coordinate will be >= 0 in the final container. 144 cumulativeTopStart = heightDifference; 145 } else { 146 // The current container is bigger than the final container, so the current 0 147 // coordinate will be < 0 in the final container. 148 cumulativeTopEnd = -heightDifference; 149 } 150 151 for (int i = 0; i < mLinearLayout.getChildCount(); ++i) { 152 View view = mLinearLayout.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 173 boolean translateDownward; 174 if (topStart < topEnd) { 175 translateDownward = false; 176 } else if (topStart > topEnd) { 177 translateDownward = true; 178 } else { 179 translateDownward = bottomEnd > bottomStart; 180 } 181 182 PropertyValuesHolder viewTranslation; 183 if (translateDownward) { 184 view.setTop(topEnd); 185 view.setBottom(bottomEnd); 186 view.setTranslationY(translation); 187 view.setY(topEnd + translation); 188 viewTranslation = 189 PropertyValuesHolder.ofFloat("translationY", translation, 0.0f); 190 } else { 191 viewTranslation = 192 PropertyValuesHolder.ofFloat("translationY", 0.0f, -translation); 193 } 194 195 animators.add(ObjectAnimator.ofPropertyValuesHolder(view, viewTranslation)); 196 } 197 198 // Add heights to the cumulative totals. 199 cumulativeTopStart += startHeight; 200 cumulativeTopEnd += endHeight; 201 cumulativeEndHeight += endHeight; 202 } 203 204 // Lock the InfoBarContainer's size at its largest during the animation to avoid 205 // clipping issues. 206 int oldContainerTop = mLinearLayout.getTop(); 207 int newContainerTop = mLinearLayout.getBottom() - cumulativeEndHeight; 208 int biggestContainerTop = Math.min(oldContainerTop, newContainerTop); 209 mLinearLayout.setTop(biggestContainerTop); 210 211 // Set up and run all of the animations. 212 mAnimatorSet.addListener(new AnimatorListenerAdapter() { 213 @Override 214 public void onAnimationStart(Animator animation) { 215 mTargetWrapperView.startTransition(); 216 } 217 218 @Override 219 public void onAnimationEnd(Animator animation) { 220 mTargetWrapperView.finishTransition(); 221 mContainer.finishTransition(); 222 223 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null && 224 (mAnimationType == ANIMATION_TYPE_SHOW || 225 mAnimationType == ANIMATION_TYPE_SWAP)) { 226 mToShow.announceForAccessibility(mInfoBar.getMessage()); 227 } 228 } 229 }); 230 231 mAnimatorSet.playTogether(animators); 232 mAnimatorSet.setDuration(ANIMATION_DURATION_MS); 233 mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 234 mAnimatorSet.start(); 235 } 236} 237