Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko.animation;
8 import android.support.v4.view.ViewCompat;
9 import android.os.Build;
10 import android.os.Handler;
11 import android.view.Choreographer;
12 import android.view.View;
13 import android.view.ViewGroup;
14 import android.view.ViewTreeObserver;
15 import android.view.animation.AnimationUtils;
16 import android.view.animation.DecelerateInterpolator;
17 import android.view.animation.Interpolator;
19 import java.util.ArrayList;
20 import java.util.List;
22 public class PropertyAnimator implements Runnable {
23 private static final String LOGTAG = "GeckoPropertyAnimator";
25 public static enum Property {
26 ALPHA,
27 TRANSLATION_X,
28 TRANSLATION_Y,
29 SCROLL_X,
30 SCROLL_Y,
31 WIDTH,
32 HEIGHT
33 }
35 private class ElementHolder {
36 View view;
37 AnimatorProxy proxy;
38 Property property;
39 float from;
40 float to;
41 }
43 public static interface PropertyAnimationListener {
44 public void onPropertyAnimationStart();
45 public void onPropertyAnimationEnd();
46 }
48 private Interpolator mInterpolator;
49 private long mStartTime;
50 private long mDuration;
51 private float mDurationReciprocal;
52 private List<ElementHolder> mElementsList;
53 private List<PropertyAnimationListener> mListeners;
54 private FramePoster mFramePoster;
55 private boolean mUseHardwareLayer;
57 public PropertyAnimator(long duration) {
58 this(duration, new DecelerateInterpolator());
59 }
61 public PropertyAnimator(long duration, Interpolator interpolator) {
62 mDuration = duration;
63 mDurationReciprocal = 1.0f / (float) mDuration;
64 mInterpolator = interpolator;
65 mElementsList = new ArrayList<ElementHolder>();
66 mFramePoster = FramePoster.create(this);
67 mUseHardwareLayer = true;
68 mListeners = null;
69 }
71 public void setUseHardwareLayer(boolean useHardwareLayer) {
72 mUseHardwareLayer = useHardwareLayer;
73 }
75 public void attach(View view, Property property, float to) {
76 ElementHolder element = new ElementHolder();
78 element.view = view;
79 element.proxy = AnimatorProxy.create(view);
80 element.property = property;
81 element.to = to;
83 mElementsList.add(element);
84 }
86 public void addPropertyAnimationListener(PropertyAnimationListener listener) {
87 if (mListeners == null) {
88 mListeners = new ArrayList<PropertyAnimationListener>();
89 }
91 mListeners.add(listener);
92 }
94 public long getDuration() {
95 return mDuration;
96 }
98 public long getRemainingTime() {
99 int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
100 return mDuration - timePassed;
101 }
103 @Override
104 public void run() {
105 int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
106 if (timePassed >= mDuration) {
107 stop();
108 return;
109 }
111 float interpolation = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
113 for (ElementHolder element : mElementsList) {
114 float delta = element.from + ((element.to - element.from) * interpolation);
115 invalidate(element, delta);
116 }
118 mFramePoster.postNextAnimationFrame();
119 }
121 public void start() {
122 if (mDuration == 0) {
123 return;
124 }
126 mStartTime = AnimationUtils.currentAnimationTimeMillis();
128 // Fix the from value based on current position and property
129 for (ElementHolder element : mElementsList) {
130 if (element.property == Property.ALPHA)
131 element.from = element.proxy.getAlpha();
132 else if (element.property == Property.TRANSLATION_Y)
133 element.from = element.proxy.getTranslationY();
134 else if (element.property == Property.TRANSLATION_X)
135 element.from = element.proxy.getTranslationX();
136 else if (element.property == Property.SCROLL_Y)
137 element.from = element.proxy.getScrollY();
138 else if (element.property == Property.SCROLL_X)
139 element.from = element.proxy.getScrollX();
140 else if (element.property == Property.WIDTH)
141 element.from = element.proxy.getWidth();
142 else if (element.property == Property.HEIGHT)
143 element.from = element.proxy.getHeight();
145 ViewCompat.setHasTransientState(element.view, true);
147 if (shouldEnableHardwareLayer(element))
148 element.view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
149 else
150 element.view.setDrawingCacheEnabled(true);
151 }
153 // Get ViewTreeObserver from any of the participant views
154 // in the animation.
155 final ViewTreeObserver treeObserver;
156 if (mElementsList.size() > 0) {
157 treeObserver = mElementsList.get(0).view.getViewTreeObserver();
158 } else {
159 treeObserver = null;
160 }
162 // Try to start animation after any on-going layout round
163 // in the current view tree. OnPreDrawListener seems broken
164 // on pre-Honeycomb devices, start animation immediatelly
165 // in this case.
166 if (Build.VERSION.SDK_INT >= 11 && treeObserver != null && treeObserver.isAlive()) {
167 treeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
168 @Override
169 public boolean onPreDraw() {
170 if (treeObserver.isAlive()) {
171 treeObserver.removeOnPreDrawListener(this);
172 }
174 mFramePoster.postFirstAnimationFrame();
175 return true;
176 }
177 });
178 } else {
179 mFramePoster.postFirstAnimationFrame();
180 }
182 if (mListeners != null) {
183 for (PropertyAnimationListener listener : mListeners) {
184 listener.onPropertyAnimationStart();
185 }
186 }
187 }
190 /**
191 * Stop the animation, optionally snapping to the end position.
192 * onPropertyAnimationEnd is only called when snapping to the end position.
193 */
194 public void stop(boolean snapToEndPosition) {
195 mFramePoster.cancelAnimationFrame();
197 // Make sure to snap to the end position.
198 for (ElementHolder element : mElementsList) {
199 if (snapToEndPosition)
200 invalidate(element, element.to);
202 ViewCompat.setHasTransientState(element.view, false);
204 if (shouldEnableHardwareLayer(element))
205 element.view.setLayerType(View.LAYER_TYPE_NONE, null);
206 else
207 element.view.setDrawingCacheEnabled(false);
208 }
210 mElementsList.clear();
212 if (mListeners != null) {
213 if (snapToEndPosition) {
214 for (PropertyAnimationListener listener : mListeners) {
215 listener.onPropertyAnimationEnd();
216 }
217 }
219 mListeners.clear();
220 mListeners = null;
221 }
222 }
224 public void stop() {
225 stop(true);
226 }
228 private boolean shouldEnableHardwareLayer(ElementHolder element) {
229 if (!mUseHardwareLayer)
230 return false;
232 if (Build.VERSION.SDK_INT < 11)
233 return false;
235 if (!(element.view instanceof ViewGroup))
236 return false;
238 if (element.property == Property.ALPHA ||
239 element.property == Property.TRANSLATION_Y ||
240 element.property == Property.TRANSLATION_X)
241 return true;
243 return false;
244 }
246 private void invalidate(final ElementHolder element, final float delta) {
247 final View view = element.view;
249 // check to see if the view was detached between the check above and this code
250 // getting run on the UI thread.
251 if (view.getHandler() == null)
252 return;
254 if (element.property == Property.ALPHA)
255 element.proxy.setAlpha(delta);
256 else if (element.property == Property.TRANSLATION_Y)
257 element.proxy.setTranslationY(delta);
258 else if (element.property == Property.TRANSLATION_X)
259 element.proxy.setTranslationX(delta);
260 else if (element.property == Property.SCROLL_Y)
261 element.proxy.scrollTo(element.proxy.getScrollX(), (int) delta);
262 else if (element.property == Property.SCROLL_X)
263 element.proxy.scrollTo((int) delta, element.proxy.getScrollY());
264 else if (element.property == Property.WIDTH)
265 element.proxy.setWidth((int) delta);
266 else if (element.property == Property.HEIGHT)
267 element.proxy.setHeight((int) delta);
268 }
270 private static abstract class FramePoster {
271 public static FramePoster create(Runnable r) {
272 if (Build.VERSION.SDK_INT >= 16)
273 return new FramePosterPostJB(r);
274 else
275 return new FramePosterPreJB(r);
276 }
278 public abstract void postFirstAnimationFrame();
279 public abstract void postNextAnimationFrame();
280 public abstract void cancelAnimationFrame();
281 }
283 private static class FramePosterPreJB extends FramePoster {
284 // Default refresh rate in ms.
285 private static final int INTERVAL = 10;
287 private Handler mHandler;
288 private Runnable mRunnable;
290 public FramePosterPreJB(Runnable r) {
291 mHandler = new Handler();
292 mRunnable = r;
293 }
295 @Override
296 public void postFirstAnimationFrame() {
297 mHandler.post(mRunnable);
298 }
300 @Override
301 public void postNextAnimationFrame() {
302 mHandler.postDelayed(mRunnable, INTERVAL);
303 }
305 @Override
306 public void cancelAnimationFrame() {
307 mHandler.removeCallbacks(mRunnable);
308 }
309 }
311 private static class FramePosterPostJB extends FramePoster {
312 private Choreographer mChoreographer;
313 private Choreographer.FrameCallback mCallback;
315 public FramePosterPostJB(final Runnable r) {
316 mChoreographer = Choreographer.getInstance();
318 mCallback = new Choreographer.FrameCallback() {
319 @Override
320 public void doFrame(long frameTimeNanos) {
321 r.run();
322 }
323 };
324 }
326 @Override
327 public void postFirstAnimationFrame() {
328 postNextAnimationFrame();
329 }
331 @Override
332 public void postNextAnimationFrame() {
333 mChoreographer.postFrameCallback(mCallback);
334 }
336 @Override
337 public void cancelAnimationFrame() {
338 mChoreographer.removeFrameCallback(mCallback);
339 }
340 }
341 }