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     }
    152 
    153     static boolean isNativeInterpolator(TimeInterpolator interpolator) {
    154         return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
    155     }
    156 
    157     private void applyInterpolator() {
    158         if (mInterpolator == null) return;
    159 
    160         long ni;
    161         if (isNativeInterpolator(mInterpolator)) {
    162             ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
    163         } else {
    164             long duration = nGetDuration(mNativePtr.get());
    165             ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
    166         }
    167         nSetInterpolator(mNativePtr.get(), ni);
    168     }
    169 
    170     @Override
    171     public void start() {
    172         if (mTarget == null) {
    173             throw new IllegalStateException("Missing target!");
    174         }
    175 
    176         if (mState != STATE_PREPARE) {
    177             throw new IllegalStateException("Already started!");
    178         }
    179 
    180         mState = STATE_DELAYED;
    181         applyInterpolator();
    182 
    183         if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
    184             nSetStartDelay(mNativePtr.get(), mStartDelay);
    185             doStart();
    186         } else {
    187             getHelper().addDelayedAnimation(this);
    188         }
    189     }
    190 
    191     private void doStart() {
    192         mState = STATE_RUNNING;
    193         nStart(mNativePtr.get(), this);
    194 
    195         // Alpha is a special snowflake that has the canonical value stored
    196         // in mTransformationInfo instead of in RenderNode, so we need to update
    197         // it with the final value here.
    198         if (mRenderProperty == RenderNodeAnimator.ALPHA) {
    199             // Don't need null check because ViewPropertyAnimator's
    200             // ctor calls ensureTransformationInfo()
    201             mViewTarget.mTransformationInfo.mAlpha = mFinalValue;
    202         }
    203 
    204         notifyStartListeners();
    205 
    206         if (mViewTarget != null) {
    207             // Kick off a frame to start the process
    208             mViewTarget.invalidateViewProperty(true, false);
    209         }
    210     }
    211 
    212     private void notifyStartListeners() {
    213         final ArrayList<AnimatorListener> listeners = cloneListeners();
    214         final int numListeners = listeners == null ? 0 : listeners.size();
    215         for (int i = 0; i < numListeners; i++) {
    216             listeners.get(i).onAnimationStart(this);
    217         }
    218     }
    219 
    220     @Override
    221     public void cancel() {
    222         if (mState != STATE_FINISHED) {
    223             if (mState == STATE_DELAYED) {
    224                 getHelper().removeDelayedAnimation(this);
    225                 notifyStartListeners();
    226             }
    227             nEnd(mNativePtr.get());
    228 
    229             final ArrayList<AnimatorListener> listeners = cloneListeners();
    230             final int numListeners = listeners == null ? 0 : listeners.size();
    231             for (int i = 0; i < numListeners; i++) {
    232                 listeners.get(i).onAnimationCancel(this);
    233             }
    234 
    235             if (mViewTarget != null) {
    236                 // Kick off a frame to flush the state change
    237                 mViewTarget.invalidateViewProperty(true, false);
    238             }
    239         }
    240     }
    241 
    242     @Override
    243     public void end() {
    244         if (mState != STATE_FINISHED) {
    245             nEnd(mNativePtr.get());
    246         }
    247     }
    248 
    249     @Override
    250     public void pause() {
    251         throw new UnsupportedOperationException();
    252     }
    253 
    254     @Override
    255     public void resume() {
    256         throw new UnsupportedOperationException();
    257     }
    258 
    259     public void setTarget(View view) {
    260         mViewTarget = view;
    261         setTarget(mViewTarget.mRenderNode);
    262     }
    263 
    264     public void setTarget(Canvas canvas) {
    265         if (!(canvas instanceof GLES20RecordingCanvas)) {
    266             throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
    267         }
    268         final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
    269         setTarget(recordingCanvas.mNode);
    270     }
    271 
    272     private void setTarget(RenderNode node) {
    273         if (mTarget != null) {
    274             throw new IllegalStateException("Target already set!");
    275         }
    276         mTarget = node;
    277         mTarget.addAnimator(this);
    278     }
    279 
    280     public void setStartValue(float startValue) {
    281         checkMutable();
    282         nSetStartValue(mNativePtr.get(), startValue);
    283     }
    284 
    285     @Override
    286     public void setStartDelay(long startDelay) {
    287         checkMutable();
    288         if (startDelay < 0) {
    289             throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
    290         }
    291         mUnscaledStartDelay = startDelay;
    292         mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
    293     }
    294 
    295     @Override
    296     public long getStartDelay() {
    297         return mUnscaledStartDelay;
    298     }
    299 
    300     @Override
    301     public RenderNodeAnimator setDuration(long duration) {
    302         checkMutable();
    303         if (duration < 0) {
    304             throw new IllegalArgumentException("duration must be positive; " + duration);
    305         }
    306         mUnscaledDuration = duration;
    307         nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
    308         return this;
    309     }
    310 
    311     @Override
    312     public long getDuration() {
    313         return mUnscaledDuration;
    314     }
    315 
    316     @Override
    317     public boolean isRunning() {
    318         return mState == STATE_DELAYED || mState == STATE_RUNNING;
    319     }
    320 
    321     @Override
    322     public boolean isStarted() {
    323         return mState != STATE_PREPARE;
    324     }
    325 
    326     @Override
    327     public void setInterpolator(TimeInterpolator interpolator) {
    328         checkMutable();
    329         mInterpolator = interpolator;
    330     }
    331 
    332     @Override
    333     public TimeInterpolator getInterpolator() {
    334         return mInterpolator;
    335     }
    336 
    337     protected void onFinished() {
    338         if (mState == STATE_DELAYED) {
    339             getHelper().removeDelayedAnimation(this);
    340             notifyStartListeners();
    341         }
    342         mState = STATE_FINISHED;
    343 
    344         final ArrayList<AnimatorListener> listeners = cloneListeners();
    345         final int numListeners = listeners == null ? 0 : listeners.size();
    346         for (int i = 0; i < numListeners; i++) {
    347             listeners.get(i).onAnimationEnd(this);
    348         }
    349 
    350         // Release the native object, as it has a global reference to us. This
    351         // breaks the cyclic reference chain, and allows this object to be
    352         // GC'd
    353         mNativePtr.release();
    354         mNativePtr = null;
    355     }
    356 
    357     @SuppressWarnings("unchecked")
    358     private ArrayList<AnimatorListener> cloneListeners() {
    359         ArrayList<AnimatorListener> listeners = getListeners();
    360         if (listeners != null) {
    361             listeners = (ArrayList<AnimatorListener>) listeners.clone();
    362         }
    363         return listeners;
    364     }
    365 
    366     long getNativeAnimator() {
    367         return mNativePtr.get();
    368     }
    369 
    370     /**
    371      * @return true if the animator was started, false if still delayed
    372      */
    373     private boolean processDelayed(long frameTimeMs) {
    374         if (mStartTime == 0) {
    375             mStartTime = frameTimeMs;
    376         } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
    377             doStart();
    378             return true;
    379         }
    380         return false;
    381     }
    382 
    383     private static DelayedAnimationHelper getHelper() {
    384         DelayedAnimationHelper helper = sAnimationHelper.get();
    385         if (helper == null) {
    386             helper = new DelayedAnimationHelper();
    387             sAnimationHelper.set(helper);
    388         }
    389         return helper;
    390     }
    391 
    392     private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
    393             new ThreadLocal<DelayedAnimationHelper>();
    394 
    395     private static class DelayedAnimationHelper implements Runnable {
    396 
    397         private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
    398         private final Choreographer mChoreographer;
    399         private boolean mCallbackScheduled;
    400 
    401         public DelayedAnimationHelper() {
    402             mChoreographer = Choreographer.getInstance();
    403         }
    404 
    405         public void addDelayedAnimation(RenderNodeAnimator animator) {
    406             mDelayedAnims.add(animator);
    407             scheduleCallback();
    408         }
    409 
    410         public void removeDelayedAnimation(RenderNodeAnimator animator) {
    411             mDelayedAnims.remove(animator);
    412         }
    413 
    414         private void scheduleCallback() {
    415             if (!mCallbackScheduled) {
    416                 mCallbackScheduled = true;
    417                 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
    418             }
    419         }
    420 
    421         @Override
    422         public void run() {
    423             long frameTimeMs = mChoreographer.getFrameTime();
    424             mCallbackScheduled = false;
    425 
    426             int end = 0;
    427             for (int i = 0; i < mDelayedAnims.size(); i++) {
    428                 RenderNodeAnimator animator = mDelayedAnims.get(i);
    429                 if (!animator.processDelayed(frameTimeMs)) {
    430                     if (end != i) {
    431                         mDelayedAnims.set(end, animator);
    432                     }
    433                     end++;
    434                 }
    435             }
    436             while (mDelayedAnims.size() > end) {
    437                 mDelayedAnims.remove(mDelayedAnims.size() - 1);
    438             }
    439 
    440             if (mDelayedAnims.size() > 0) {
    441                 scheduleCallback();
    442             }
    443         }
    444     }
    445 
    446     // Called by native
    447     private static void callOnFinished(RenderNodeAnimator animator) {
    448         animator.onFinished();
    449     }
    450 
    451     @Override
    452     public Animator clone() {
    453         throw new IllegalStateException("Cannot clone this animator");
    454     }
    455 
    456     @Override
    457     public void setAllowRunningAsynchronously(boolean mayRunAsync) {
    458         checkMutable();
    459         nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
    460     }
    461 
    462     private static native long nCreateAnimator(int property, float finalValue);
    463     private static native long nCreateCanvasPropertyFloatAnimator(
    464             long canvasProperty, float finalValue);
    465     private static native long nCreateCanvasPropertyPaintAnimator(
    466             long canvasProperty, int paintField, float finalValue);
    467     private static native long nCreateRevealAnimator(
    468             int x, int y, float startRadius, float endRadius);
    469 
    470     private static native void nSetStartValue(long nativePtr, float startValue);
    471     private static native void nSetDuration(long nativePtr, long duration);
    472     private static native long nGetDuration(long nativePtr);
    473     private static native void nSetStartDelay(long nativePtr, long startDelay);
    474     private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
    475     private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
    476 
    477     private static native void nStart(long animPtr, RenderNodeAnimator finishListener);
    478     private static native void nEnd(long animPtr);
    479 }
    480