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.

     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 }

mercurial