EditorAnimator.java revision ca87e9c598929b5b6a62da9b80d2114168e24274
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.contacts.editor; 18 19import com.google.common.collect.Lists; 20 21import android.animation.Animator; 22import android.animation.Animator.AnimatorListener; 23import android.animation.AnimatorListenerAdapter; 24import android.animation.AnimatorSet; 25import android.animation.ObjectAnimator; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.ViewParent; 29import android.view.ViewTreeObserver.OnGlobalLayoutListener; 30import android.widget.LinearLayout; 31 32import java.util.List; 33 34/** 35 * Configures animations for typical use-cases 36 */ 37public class EditorAnimator { 38 private static EditorAnimator sInstance = new EditorAnimator(); 39 40 public static EditorAnimator getInstance() { 41 return sInstance; 42 } 43 44 /** Private constructor for singleton */ 45 private EditorAnimator() { } 46 47 private AnimatorRunner mRunner = new AnimatorRunner(); 48 49 public void removeEditorView(final View victim) { 50 mRunner.endOldAnimation(); 51 final int offset = victim.getHeight(); 52 53 final List<View> viewsToMove = getViewsBelowOf(victim); 54 final List<Animator> animators = Lists.newArrayList(); 55 56 // Fade out 57 final ObjectAnimator fadeOutAnimator = 58 ObjectAnimator.ofFloat(victim, View.ALPHA, 1.0f, 0.0f); 59 fadeOutAnimator.setDuration(200); 60 animators.add(fadeOutAnimator); 61 62 // Translations 63 translateViews(animators, viewsToMove, 0.0f, -offset, 100, 200); 64 65 mRunner.run(animators, new AnimatorListenerAdapter() { 66 @Override 67 public void onAnimationEnd(Animator animation) { 68 // Clean up: Remove all the translations 69 for (int i = 0; i < viewsToMove.size(); i++) { 70 final View view = viewsToMove.get(i); 71 view.setTranslationY(0.0f); 72 } 73 // Remove our target view (if parent is null, we were run several times by quick 74 // fingers. Just ignore) 75 final ViewGroup victimParent = (ViewGroup) victim.getParent(); 76 if (victimParent != null) { 77 victimParent.removeView(victim); 78 } 79 } 80 }); 81 } 82 83 public void expandOrganization(final View addOrganizationButton, 84 final ViewGroup organizationSectionViewContainer) { 85 mRunner.endOldAnimation(); 86 // Make the new controls visible and do one layout pass (so that we can measure) 87 organizationSectionViewContainer.setVisibility(View.VISIBLE); 88 organizationSectionViewContainer.setAlpha(0.0f); 89 organizationSectionViewContainer.requestFocus(); 90 doAfterLayout(addOrganizationButton, new Runnable() { 91 @Override 92 public void run() { 93 // How many pixels extra do we need? 94 final int offset = organizationSectionViewContainer.getHeight() - 95 addOrganizationButton.getHeight(); 96 97 final List<Animator> animators = Lists.newArrayList(); 98 99 // Fade out 100 final ObjectAnimator fadeOutAnimator = ObjectAnimator.ofFloat( 101 addOrganizationButton, View.ALPHA, 1.0f, 0.0f); 102 fadeOutAnimator.setDuration(200); 103 animators.add(fadeOutAnimator); 104 105 // Translations 106 final List<View> viewsToMove = getViewsBelowOf(organizationSectionViewContainer); 107 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200); 108 109 // Fade in 110 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat( 111 organizationSectionViewContainer, View.ALPHA, 0.0f, 1.0f); 112 fadeInAnimator.setDuration(200); 113 fadeInAnimator.setStartDelay(200); 114 animators.add(fadeInAnimator); 115 116 mRunner.run(animators); 117 } 118 }); 119 } 120 121 public void showAddFieldFooter(final View view) { 122 mRunner.endOldAnimation(); 123 if (view.getVisibility() == View.VISIBLE) return; 124 // Make the new controls visible and do one layout pass (so that we can measure) 125 view.setVisibility(View.VISIBLE); 126 view.setAlpha(0.0f); 127 doAfterLayout(view, new Runnable() { 128 @Override 129 public void run() { 130 // How many pixels extra do we need? 131 final int offset = view.getHeight(); 132 133 final List<Animator> animators = Lists.newArrayList(); 134 135 // Translations 136 final List<View> viewsToMove = getViewsBelowOf(view); 137 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200); 138 139 // Fade in 140 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat( 141 view, View.ALPHA, 0.0f, 1.0f); 142 fadeInAnimator.setDuration(200); 143 fadeInAnimator.setStartDelay(200); 144 animators.add(fadeInAnimator); 145 146 mRunner.run(animators); 147 } 148 }); 149 } 150 151 public void hideAddFieldFooter(final View victim) { 152 mRunner.endOldAnimation(); 153 if (victim.getVisibility() == View.GONE) return; 154 final int offset = victim.getHeight(); 155 156 final List<View> viewsToMove = getViewsBelowOf(victim); 157 final List<Animator> animators = Lists.newArrayList(); 158 159 // Fade out 160 final ObjectAnimator fadeOutAnimator = 161 ObjectAnimator.ofFloat(victim, View.ALPHA, 1.0f, 0.0f); 162 fadeOutAnimator.setDuration(200); 163 animators.add(fadeOutAnimator); 164 165 // Translations 166 translateViews(animators, viewsToMove, 0.0f, -offset, 100, 200); 167 168 // Combine 169 mRunner.run(animators, new AnimatorListenerAdapter() { 170 @Override 171 public void onAnimationEnd(Animator animation) { 172 // Clean up: Remove all the translations 173 for (int i = 0; i < viewsToMove.size(); i++) { 174 final View view = viewsToMove.get(i); 175 view.setTranslationY(0.0f); 176 } 177 178 // Restore alpha (for next time), but hide the view for good now 179 victim.setAlpha(1.0f); 180 victim.setVisibility(View.GONE); 181 } 182 }); 183 } 184 185 /** Runs a piece of code after the next layout run */ 186 private static void doAfterLayout(final View view, final Runnable runnable) { 187 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() { 188 @Override 189 public void onGlobalLayout() { 190 // Layout pass done, unregister for further events 191 view.getViewTreeObserver().removeOnGlobalLayoutListener(this); 192 runnable.run(); 193 } 194 }; 195 view.getViewTreeObserver().addOnGlobalLayoutListener(listener); 196 } 197 198 /** 199 * Creates a translation-animation for the given views 200 */ 201 private static void translateViews(List<Animator> animators, List<View> views, float fromY, 202 float toY, int startDelay, int duration) { 203 for (int i = 0; i < views.size(); i++) { 204 final View child = views.get(i); 205 final ObjectAnimator translateAnimator = 206 ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, fromY, toY); 207 translateAnimator.setStartDelay(startDelay); 208 translateAnimator.setDuration(duration); 209 animators.add(translateAnimator); 210 } 211 } 212 213 /** 214 * Traverses up the view hierarchy and returns all views below this item. Stops 215 * once a parent is not a vertical LinearLayout 216 */ 217 private static List<View> getViewsBelowOf(View view) { 218 final ViewGroup victimParent = (ViewGroup) view.getParent(); 219 final List<View> result = Lists.newArrayList(); 220 final int index = victimParent.indexOfChild(view); 221 getViewsBelowOfRecursive(result, victimParent, index + 1); 222 return result; 223 } 224 225 private static void getViewsBelowOfRecursive(List<View> result, ViewGroup container, 226 int index) { 227 for (int i = index; i < container.getChildCount(); i++) { 228 result.add(container.getChildAt(i)); 229 } 230 231 final ViewParent parent = container.getParent(); 232 if (parent instanceof LinearLayout) { 233 final LinearLayout parentLayout = (LinearLayout) parent; 234 if (parentLayout.getOrientation() == LinearLayout.VERTICAL) { 235 int containerIndex = parentLayout.indexOfChild(container); 236 getViewsBelowOfRecursive(result, parentLayout, containerIndex+1); 237 } 238 } 239 } 240 241 /** 242 * Keeps a reference to the last animator, so that we can end that early if the user 243 * quickly pushes buttons. Removes the reference once the animation has finished 244 */ 245 /* package */ static class AnimatorRunner extends AnimatorListenerAdapter { 246 private Animator mLastAnimator; 247 248 @Override 249 public void onAnimationEnd(Animator animation) { 250 mLastAnimator = null; 251 } 252 253 public void run(List<Animator> animators) { 254 run(animators, null); 255 } 256 257 public void run(List<Animator> animators, AnimatorListener listener) { 258 final AnimatorSet set = new AnimatorSet(); 259 set.playTogether(animators); 260 if (listener != null) set.addListener(listener); 261 set.addListener(this); 262 mLastAnimator = set; 263 set.start(); 264 } 265 266 public void endOldAnimation() { 267 if (mLastAnimator != null) { 268 mLastAnimator.end(); 269 } 270 } 271 } 272} 273