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