mobile/android/base/animation/PropertyAnimator.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial