blob: 598b6b0f5422d9abc5f96102df21ac5e5f57e8ee [file] [log] [blame]
Daniel Lehmannca87e9c2012-03-06 14:02:31 -08001/*
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
Daniel Lehmannca87e9c2012-03-06 14:02:31 -080019import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.AnimatorListenerAdapter;
22import android.animation.AnimatorSet;
23import android.animation.ObjectAnimator;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.ViewParent;
Daniel Lehmannca87e9c2012-03-06 14:02:31 -080027import android.widget.LinearLayout;
Brian Attwelld462d3a2014-12-17 12:10:21 -080028import android.widget.ScrollView;
Daniel Lehmannca87e9c2012-03-06 14:02:31 -080029
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070030import com.android.contacts.util.SchedulingUtils;
Gary Mai0a49afa2016-12-05 15:53:58 -080031
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070032import com.google.common.collect.Lists;
33
Daniel Lehmannca87e9c2012-03-06 14:02:31 -080034import java.util.List;
35
36/**
37 * Configures animations for typical use-cases
38 */
39public class EditorAnimator {
40 private static EditorAnimator sInstance = new EditorAnimator();
41
42 public static EditorAnimator getInstance() {
43 return sInstance;
44 }
45
46 /** Private constructor for singleton */
47 private EditorAnimator() { }
48
49 private AnimatorRunner mRunner = new AnimatorRunner();
50
51 public void removeEditorView(final View victim) {
52 mRunner.endOldAnimation();
53 final int offset = victim.getHeight();
54
55 final List<View> viewsToMove = getViewsBelowOf(victim);
56 final List<Animator> animators = Lists.newArrayList();
57
58 // Fade out
59 final ObjectAnimator fadeOutAnimator =
60 ObjectAnimator.ofFloat(victim, View.ALPHA, 1.0f, 0.0f);
61 fadeOutAnimator.setDuration(200);
62 animators.add(fadeOutAnimator);
63
64 // Translations
65 translateViews(animators, viewsToMove, 0.0f, -offset, 100, 200);
66
67 mRunner.run(animators, new AnimatorListenerAdapter() {
68 @Override
69 public void onAnimationEnd(Animator animation) {
70 // Clean up: Remove all the translations
71 for (int i = 0; i < viewsToMove.size(); i++) {
72 final View view = viewsToMove.get(i);
73 view.setTranslationY(0.0f);
74 }
Walter Jangf7dfa542015-10-26 14:27:27 -070075 // Remove our target view (if parent is null, we were run several times by quick
76 // fingers. Just ignore)
77 final ViewGroup victimParent = (ViewGroup) victim.getParent();
78 if (victimParent != null) {
79 victimParent.removeView(victim);
Daniel Lehmannca87e9c2012-03-06 14:02:31 -080080 }
81 }
82 });
83 }
84
Yorke Leeba48d212012-12-26 13:25:48 -080085 /**
86 * Slides the view into its new height, while simultaneously fading it into view.
87 *
88 * @param target The target view to perform the animation on.
89 * @param previousHeight The previous height of the view before its height was changed.
90 * Needed because the view does not store any state information about its previous height.
91 */
92 public void slideAndFadeIn(final ViewGroup target, final int previousHeight) {
93 mRunner.endOldAnimation();
94 target.setVisibility(View.VISIBLE);
95 target.setAlpha(0.0f);
Yorke Leeba48d212012-12-26 13:25:48 -080096 SchedulingUtils.doAfterLayout(target, new Runnable() {
97 @Override
98 public void run() {
99 final int offset = target.getHeight() - previousHeight;
100 final List<Animator> animators = Lists.newArrayList();
101
102 // Translations
103 final List<View> viewsToMove = getViewsBelowOf(target);
104
105 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200);
106
107 // Fade in
108 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(
109 target, View.ALPHA, 0.0f, 1.0f);
110 fadeInAnimator.setDuration(200);
111 fadeInAnimator.setStartDelay(200);
112 animators.add(fadeInAnimator);
113
114 mRunner.run(animators);
115 }
116 });
117 }
118
Brian Attwell043fba62014-10-30 11:11:56 -0700119 public void showFieldFooter(final View view) {
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800120 mRunner.endOldAnimation();
121 if (view.getVisibility() == View.VISIBLE) return;
122 // Make the new controls visible and do one layout pass (so that we can measure)
123 view.setVisibility(View.VISIBLE);
124 view.setAlpha(0.0f);
Josh Gargus6f5557e2012-03-21 10:45:08 -0700125 SchedulingUtils.doAfterLayout(view, new Runnable() {
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800126 @Override
127 public void run() {
128 // How many pixels extra do we need?
129 final int offset = view.getHeight();
130
131 final List<Animator> animators = Lists.newArrayList();
132
133 // Translations
134 final List<View> viewsToMove = getViewsBelowOf(view);
135 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200);
136
137 // Fade in
138 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(
139 view, View.ALPHA, 0.0f, 1.0f);
140 fadeInAnimator.setDuration(200);
141 fadeInAnimator.setStartDelay(200);
142 animators.add(fadeInAnimator);
143
144 mRunner.run(animators);
145 }
146 });
147 }
148
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800149 /**
Brian Attwelld462d3a2014-12-17 12:10:21 -0800150 * Smoothly scroll {@param targetView}'s parent ScrollView to the top of {@param targetView}.
151 */
152 public void scrollViewToTop(final View targetView) {
153 final ScrollView scrollView = getParentScrollView(targetView);
154 SchedulingUtils.doAfterLayout(scrollView, new Runnable() {
155 @Override
156 public void run() {
157 ScrollView scrollView = getParentScrollView(targetView);
158 scrollView.smoothScrollTo(0, offsetFromTopOfViewGroup(targetView, scrollView)
159 + scrollView.getScrollY());
160 }
161 });
162 // Clear the focused element so it doesn't interfere with scrolling.
163 View view = scrollView.findFocus();
164 if (view != null) {
165 view.clearFocus();
166 }
167 }
168
169 public static void placeFocusAtTopOfScreenAfterReLayout(final View view) {
170 // In order for the focus to be placed at the top of the Window, we need
171 // to wait for layout. Otherwise we don't know where the top of the screen is.
172 SchedulingUtils.doAfterLayout(view, new Runnable() {
173 @Override
174 public void run() {
175 EditorAnimator.getParentScrollView(view).clearFocus();
176 }
177 });
178 }
179
180 private int offsetFromTopOfViewGroup(View view, ViewGroup viewGroup) {
181 int viewLocation[] = new int[2];
182 int viewGroupLocation[] = new int[2];
183 viewGroup.getLocationOnScreen(viewGroupLocation);
184 view.getLocationOnScreen(viewLocation);
185 return viewLocation[1] - viewGroupLocation[1];
186 }
187
188 private static ScrollView getParentScrollView(View view) {
189 while (true) {
190 ViewParent parent = view.getParent();
191 if (parent instanceof ScrollView)
192 return (ScrollView) parent;
193 if (!(parent instanceof View))
194 throw new IllegalArgumentException(
195 "The editor should be contained inside a ScrollView.");
196 view = (View) parent;
197 }
198 }
199
200 /**
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800201 * Creates a translation-animation for the given views
202 */
203 private static void translateViews(List<Animator> animators, List<View> views, float fromY,
204 float toY, int startDelay, int duration) {
205 for (int i = 0; i < views.size(); i++) {
206 final View child = views.get(i);
207 final ObjectAnimator translateAnimator =
208 ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, fromY, toY);
209 translateAnimator.setStartDelay(startDelay);
210 translateAnimator.setDuration(duration);
211 animators.add(translateAnimator);
212 }
213 }
214
215 /**
Yorke Leeba48d212012-12-26 13:25:48 -0800216 * Traverses up the view hierarchy and returns all views physically below this item.
Chiao Cheng2e9ee9f2012-05-24 19:57:59 -0700217 *
218 * @return List of views that are below the given view. Empty list if parent of view is null.
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800219 */
220 private static List<View> getViewsBelowOf(View view) {
221 final ViewGroup victimParent = (ViewGroup) view.getParent();
222 final List<View> result = Lists.newArrayList();
Chiao Cheng2e9ee9f2012-05-24 19:57:59 -0700223 if (victimParent != null) {
224 final int index = victimParent.indexOfChild(view);
Yorke Leeba48d212012-12-26 13:25:48 -0800225 getViewsBelowOfRecursive(result, victimParent, index + 1, view);
Chiao Cheng2e9ee9f2012-05-24 19:57:59 -0700226 }
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800227 return result;
228 }
229
230 private static void getViewsBelowOfRecursive(List<View> result, ViewGroup container,
Yorke Leeba48d212012-12-26 13:25:48 -0800231 int index, View target) {
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800232 for (int i = index; i < container.getChildCount(); i++) {
Yorke Leeba48d212012-12-26 13:25:48 -0800233 View view = container.getChildAt(i);
234 // consider the child view below the target view only if it is physically
235 // below the view on-screen, using half the height of the target view as the
236 // baseline
237 if (view.getY() > (target.getY() + target.getHeight() / 2)) {
238 result.add(view);
239 }
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800240 }
241
242 final ViewParent parent = container.getParent();
243 if (parent instanceof LinearLayout) {
244 final LinearLayout parentLayout = (LinearLayout) parent;
Yorke Leeba48d212012-12-26 13:25:48 -0800245 int containerIndex = parentLayout.indexOfChild(container);
246 getViewsBelowOfRecursive(result, parentLayout, containerIndex + 1, target);
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800247 }
248 }
249
250 /**
251 * Keeps a reference to the last animator, so that we can end that early if the user
252 * quickly pushes buttons. Removes the reference once the animation has finished
253 */
254 /* package */ static class AnimatorRunner extends AnimatorListenerAdapter {
255 private Animator mLastAnimator;
256
257 @Override
258 public void onAnimationEnd(Animator animation) {
259 mLastAnimator = null;
260 }
261
262 public void run(List<Animator> animators) {
263 run(animators, null);
264 }
265
266 public void run(List<Animator> animators, AnimatorListener listener) {
267 final AnimatorSet set = new AnimatorSet();
268 set.playTogether(animators);
269 if (listener != null) set.addListener(listener);
270 set.addListener(this);
271 mLastAnimator = set;
272 set.start();
273 }
274
275 public void endOldAnimation() {
276 if (mLastAnimator != null) {
277 mLastAnimator.end();
278 }
279 }
280 }
281}