Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2014 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 android.view;
     18 
     19 import android.animation.Animator;
     20 import android.animation.TimeInterpolator;
     21 import android.animation.ValueAnimator;
     22 import android.graphics.Canvas;
     23 import android.graphics.CanvasProperty;
     24 import android.graphics.Paint;
     25 import android.util.SparseIntArray;
     26 
     27 import com.android.internal.util.VirtualRefBasePtr;
     28 import com.android.internal.view.animation.FallbackLUTInterpolator;
     29 import com.android.internal.view.animation.HasNativeInterpolator;
     30 import com.android.internal.view.animation.NativeInterpolatorFactory;
     31 
     32 import java.util.ArrayList;
     33 
     34 /**
     35  * @hide
     36  */
     37 public class RenderNodeAnimator extends Animator {
     38     // Keep in sync with enum RenderProperty in Animator.h
     39     public static final int TRANSLATION_X = 0;
     40     public static final int TRANSLATION_Y = 1;
     41     public static final int TRANSLATION_Z = 2;
     42     public static final int SCALE_X = 3;
     43     public static final int SCALE_Y = 4;
     44     public static final int ROTATION = 5;
     45     public static final int ROTATION_X = 6;
     46     public static final int ROTATION_Y = 7;
     47     public static final int X = 8;
     48     public static final int Y = 9;
     49     public static final int Z = 10;
     50     public static final int ALPHA = 11;
     51     // The last value in the enum, used for array size initialization
     52     public static final int LAST_VALUE = ALPHA;
     53 
     54     // Keep in sync with enum PaintFields in Animator.h
     55     public static final int PAINT_STROKE_WIDTH = 0;
     56 
     57     /**
     58      * Field for the Paint alpha channel, which should be specified as a value
     59      * between 0 and 255.
     60      */
     61     public static final int PAINT_ALPHA = 1;
     62 
     63     // ViewPropertyAnimator uses a mask for its values, we need to remap them
     64     // to the enum values here. RenderPropertyAnimator can't use the mask values
     65     // directly as internally it uses a lookup table so it needs the values to
     66     // be sequential starting from 0
     67     private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
     68         put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
     69         put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
     70         put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
     71         put(ViewPropertyAnimator.SCALE_X, SCALE_X);
     72         put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
     73         put(ViewPropertyAnimator.ROTATION, ROTATION);
     74         put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
     75         put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
     76         put(ViewPropertyAnimator.X, X);
     77         put(ViewPropertyAnimator.Y, Y);
     78         put(ViewPropertyAnimator.Z, Z);
     79         put(ViewPropertyAnimator.ALPHA, ALPHA);
     80     }};
     81 
     82     private VirtualRefBasePtr mNativePtr;
     83 
     84     private RenderNode mTarget;
     85     private View mViewTarget;
     86     private int mRenderProperty = -1;
     87     private float mFinalValue;
     88     private TimeInterpolator mInterpolator;
     89 
     90     private static final int STATE_PREPARE = 0;
     91     private static final int STATE_DELAYED = 1;
     92     private static final int STATE_RUNNING = 2;
     93     private static final int STATE_FINISHED = 3;
     94     private int mState = STATE_PREPARE;
     95 
     96     private long mUnscaledDuration = 300;
     97     private long mUnscaledStartDelay = 0;
     98     // If this is true, we will run any start delays on the UI thread. This is
     99     // the safe default, and is necessary to ensure start listeners fire at
    100     // the correct time. Animators created by RippleDrawable (the
    101     // CanvasProperty<> ones) do not have this expectation, and as such will
    102     // set this to false so that the renderthread handles the startdelay instead
    103     private final boolean mUiThreadHandlesDelay;
    104     private long mStartDelay = 0;
    105     private long mStartTime;
    106 
    107     public static int mapViewPropertyToRenderProperty(int viewProperty) {
    108         return sViewPropertyAnimatorMap.get(viewProperty);
    109     }
    110 
    111     public RenderNodeAnimator(int property, float finalValue) {
    112         mRenderProperty = property;
    113         mFinalValue = finalValue;
    114         mUiThreadHandlesDelay = true;
    115         init(nCreateAnimator(property, finalValue));
    116     }
    117 
    118     public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
    119         init(nCreateCanvasPropertyFloatAnimator(
    120                 property.getNativeContainer(), finalValue));
    121         mUiThreadHandlesDelay = false;
    122     }
    123 
    124     /**
    125      * Creates a new render node animator for a field on a Paint property.
    126      *
    127      * @param property The paint property to target
    128      * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
    129      *            {@link #PAINT_STROKE_WIDTH}
    130      * @param finalValue The target value for the property
    131      */
    132     public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
    133         init(nCreateCanvasPropertyPaintAnimator(
    134                 property.getNativeContainer(), paintField, finalValue));
    135         mUiThreadHandlesDelay = false;
    136     }
    137 
    138     public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
    139         init(nCreateRevealAnimator(x, y, startRadius, endRadius));
    140         mUiThreadHandlesDelay = true;
    141     }
    142 
    143     private void init(long ptr) {
    144         mNativePtr = new VirtualRefBasePtr(ptr);
    145     }
    146 
    147     private void checkMutable() {
    148         if (mState != STATE_PREPARE) {
    149             throw new IllegalStateException("Animator has already started, cannot change it now!");
    150         }
    151         if (mNativePtr == null) {
    152             throw new IllegalStateException("Animator's target has been destroyed "
    153                     + "(trying to modify an animation after activity destroy?)");
    154         }
    155     }
    156 
    157     static boolean isNativeInterpolator(TimeInterpolator interpolator) {
    158         return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
    159     }
    160 
    161     private void applyInterpolator() {
    162         if (mInterpolator == null) return;
    163 
    164         long ni;
    165         if (isNativeInterpolator(mInterpolator)) {
    166             ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
    167         } else {
    168             long duration = nGetDuration(mNativePtr.get());
    169             ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
    170         }
    171         nSetInterpolator(mNativePtr.get(), ni);
    172     }
    173 
    174     @Override
    175     public void start() {
    176         if (mTarget == null) {
    177             throw new IllegalStateException("Missing target!");
    178         }
    179 
    180         if (mState != STATE_PREPARE) {
    181             throw new IllegalStateException("Already started!");
    182         }
    183 
    184         mState = STATE_DELAYED;
    185         applyInterpolator();
    186 
    187         if (mNativePtr == null) {
    188             // It's dead, immediately cancel
    189             cancel();
    190         } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
    191             nSetStartDelay(mNativePtr.get(), mStartDelay);
    192             doStart();
    193         } else {
    194             getHelper().addDelayedAnimation(this);
    195         }
    196     }
    197 
    198     private void doStart() {
    199         // Alpha is a special snowflake that has the canonical value stored
    200         // in mTransformationInfo instead of in RenderNode, so we need to update
    201         // it with the final value here.
    202         if (mRenderProperty == RenderNodeAnimator.ALPHA) {
    203             // Don't need null check because ViewPropertyAnimator's
    204             // ctor calls ensureTransformationInfo()
    205             mViewTarget.mTransformationInfo.mAlpha = mFinalValue;
    206         }
    207 
    208         moveToRunningState();
    209 
    210         if (mViewTarget != null) {
    211             // Kick off a frame to start the process
    212             mViewTarget.invalidateViewProperty(true, false);
    213         }
    214     }
    215 
    216     private void moveToRunningState() {
    217         mState = STATE_RUNNING;
    218         if (mNativePtr != null) {
    219             nStart(mNativePtr.get());
    220         }
    221         notifyStartListeners();
    222     }
    223 
    224     private void notifyStartListeners() {
    225         final ArrayList<AnimatorListener> listeners = cloneListeners();
    226         final int numListeners = listeners == null ? 0 : listeners.size();
    227         for (int i = 0; i < numListeners; i++) {
    228             listeners.get(i).onAnimationStart(this);
    229         }
    230     }
    231 
    232     @Override
    233     public void cancel() {
    234         if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
    235             if (mState == STATE_DELAYED) {
    236                 getHelper().removeDelayedAnimation(this);
    237                 moveToRunningState();
    238             }
    239 
    240             final ArrayList<AnimatorListener> listeners = cloneListeners();
    241             final int numListeners = listeners == null ? 0 : listeners.size();
    242             for (int i = 0; i < numListeners; i++) {
    243                 listeners.get(i).onAnimationCancel(this);
    244             }
    245 
    246             end();
    247         }
    248     }
    249 
    250     @Override
    251     public void end() {
    252         if (mState != STATE_FINISHED) {
    253             if (mState < STATE_RUNNING) {
    254                 getHelper().removeDelayedAnimation(this);
    255                 doStart();
    256             }
    257             if (mNativePtr != null) {
    258                 nEnd(mNativePtr.get());
    259                 if (mViewTarget != null) {
    260                     // Kick off a frame to flush the state change
    261                     mViewTarget.invalidateViewProperty(true, false);
    262                 }
    263             } else {
    264                 // It's already dead, jump to onFinish
    265                 onFinished();
    266             }
    267         }
    268     }
    269 
    270     @Override
    271     public void pause() {
    272         throw new UnsupportedOperationException();
    273     }
    274 
    275     @Override
    276     public void resume() {
    277         throw new UnsupportedOperationException();
    278     }
    279 
    280     public void setTarget(View view) {
    281         mViewTarget = view;
    282         setTarget(mViewTarget.mRenderNode);
    283     }
    284 
    285     public void setTarget(Canvas canvas) {
    286         if (!(canvas instanceof GLES20RecordingCanvas)) {
    287             throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
    288         }
    289         final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
    290         setTarget(recordingCanvas.mNode);
    291     }
    292 
    293     private void setTarget(RenderNode node) {
    294         checkMutable();
    295         if (mTarget != null) {
    296             throw new IllegalStateException("Target already set!");
    297         }
    298         nSetListener(mNativePtr.get(), this);
    299         mTarget = node;
    300         mTarget.addAnimator(this);
    301     }
    302 
    303     public void setStartValue(float startValue) {
    304         checkMutable();
    305         nSetStartValue(mNativePtr.get(), startValue);
    306     }
    307 
    308     @Override
    309     public void setStartDelay(long startDelay) {
    310         checkMutable();
    311         if (startDelay < 0) {
    312             throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
    313         }
    314         mUnscaledStartDelay = startDelay;
    315         mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
    316     }
    317 
    318     @Override
    319     public long getStartDelay() {
    320         return mUnscaledStartDelay;
    321     }
    322 
    323     @Override
    324     public RenderNodeAnimator setDuration(long duration) {
    325         checkMutable();
    326         if (duration < 0) {
    327             throw new IllegalArgumentException("duration must be positive; " + duration);
    328         }
    329         mUnscaledDuration = duration;
    330         nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
    331         return this;
    332     }
    333 
    334     @Override
    335     public long getDuration() {
    336         return mUnscaledDuration;
    337     }
    338 
    339     @Override
    340     public boolean isRunning() {
    341         return mState == STATE_DELAYED || mState == STATE_RUNNING;
    342     }
    343 
    344     @Override
    345     public boolean isStarted() {
    346         return mState != STATE_PREPARE;
    347     }
    348 
    349     @Override
    350     public void setInterpolator(TimeInterpolator interpolator) {
    351         checkMutable();
    352         mInterpolator = interpolator;
    353     }
    354 
    355     @Override
    356     public TimeInterpolator getInterpolator() {
    357         return mInterpolator;
    358     }
    359 
    360     protected void onFinished() {
    361         if (mState == STATE_PREPARE) {
    362             // Unlikely but possible, the native side has been destroyed
    363             // before we have started.
    364             releaseNativePtr();
    365             return;
    366         }
    367         if (mState == STATE_DELAYED) {
    368             getHelper().removeDelayedAnimation(this);
    369             notifyStartListeners();
    370         }
    371         mState = STATE_FINISHED;
    372 
    373         final ArrayList<AnimatorListener> listeners = cloneListeners();
    374         final int numListeners = listeners == null ? 0 : listeners.size();
    375         for (int i = 0; i < numListeners; i++) {
    376             listeners.get(i).onAnimationEnd(this);
    377         }
    378 
    379         // Release the native object, as it has a global reference to us. This
    380         // breaks the cyclic reference chain, and allows this object to be
    381         // GC'd
    382         releaseNativePtr();
    383     }
    384 
    385     private void releaseNativePtr() {
    386         if (mNativePtr != null) {
    387             mNativePtr.release();
    388             mNativePtr = null;
    389         }
    390     }
    391 
    392     @SuppressWarnings("unchecked")
    393     private ArrayList<AnimatorListener> cloneListeners() {
    394         ArrayList<AnimatorListener> listeners = getListeners();
    395         if (listeners != null) {
    396             listeners = (ArrayList<AnimatorListener>) listeners.clone();
    397         }
    398         return listeners;
    399     }
    400 
    401     long getNativeAnimator() {
    402         return mNativePtr.get();
    403     }
    404 
    405     /**
    406      * @return true if the animator was started, false if still delayed
    407      */
    408     private boolean processDelayed(long frameTimeMs) {
    409         if (mStartTime == 0) {
    410             mStartTime = frameTimeMs;
    411         } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
    412             doStart();
    413             return true;
    414         }
    415         return false;
    416     }
    417 
    418     private static DelayedAnimationHelper getHelper() {
    419         DelayedAnimationHelper helper = sAnimationHelper.get();
    420         if (helper == null) {
    421             helper = new DelayedAnimationHelper();
    422             sAnimationHelper.set(helper);
    423         }
    424         return helper;
    425     }
    426 
    427     private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
    428             new ThreadLocal<DelayedAnimationHelper>();
    429 
    430     private static class DelayedAnimationHelper implements Runnable {
    431 
    432         private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
    433         private final Choreographer mChoreographer;
    434         private boolean mCallbackScheduled;
    435 
    436         public DelayedAnimationHelper() {
    437             mChoreographer = Choreographer.getInstance();
    438         }
    439 
    440         public void addDelayedAnimation(RenderNodeAnimator animator) {
    441             mDelayedAnims.add(animator);
    442             scheduleCallback();
    443         }
    444 
    445         public void removeDelayedAnimation(RenderNodeAnimator animator) {
    446             mDelayedAnims.remove(animator);
    447         }
    448 
    449         private void scheduleCallback() {
    450             if (!mCallbackScheduled) {
    451                 mCallbackScheduled = true;
    452                 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
    453             }
    454         }
    455 
    456         @Override
    457         public void run() {
    458             long frameTimeMs = mChoreographer.getFrameTime();
    459             mCallbackScheduled = false;
    460 
    461             int end = 0;
    462             for (int i = 0; i < mDelayedAnims.size(); i++) {
    463                 RenderNodeAnimator animator = mDelayedAnims.get(i);
    464                 if (!animator.processDelayed(frameTimeMs)) {
    465                     if (end != i) {
    466                         mDelayedAnims.set(end, animator);
    467                     }
    468                     end++;
    469                 }
    470             }
    471             while (mDelayedAnims.size() > end) {
    472                 mDelayedAnims.remove(mDelayedAnims.size() - 1);
    473             }
    474 
    475             if (mDelayedAnims.size() > 0) {
    476                 scheduleCallback();
    477             }
    478         }
    479     }
    480 
    481     // Called by native
    482     private static void callOnFinished(RenderNodeAnimator animator) {
    483         animator.onFinished();
    484     }
    485 
    486     @Override
    487     public Animator clone() {
    488         throw new IllegalStateException("Cannot clone this animator");
    489     }
    490 
    491     @Override
    492     public void setAllowRunningAsynchronously(boolean mayRunAsync) {
    493         checkMutable();
    494         nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
    495     }
    496 
    497     private static native long nCreateAnimator(int property, float finalValue);
    498     private static native long nCreateCanvasPropertyFloatAnimator(
    499             long canvasProperty, float finalValue);
    500     private static native long nCreateCanvasPropertyPaintAnimator(
    501             long canvasProperty, int paintField, float finalValue);
    502     private static native long nCreateRevealAnimator(
    503             int x, int y, float startRadius, float endRadius);
    504 
    505     private static native void nSetStartValue(long nativePtr, float startValue);
    506     private static native void nSetDuration(long nativePtr, long duration);
    507     private static native long nGetDuration(long nativePtr);
    508     private static native void nSetStartDelay(long nativePtr, long startDelay);
    509     private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
    510     private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
    511     private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
    512 
    513     private static native void nStart(long animPtr);
    514     private static native void nEnd(long animPtr);
    515 }
    516