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