blob: 84d13cf25f3e7251b00cf0f4883c2d819ca68f68 [file] [log] [blame]
Michael Jurkae0523f72013-04-08 15:44:00 -07001/*
2 * Copyright (C) 2013 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 com.android.systemui.recent;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
Michael Jurkae0523f72013-04-08 15:44:00 -070022import android.util.Log;
Michael Jurkae0523f72013-04-08 15:44:00 -070023import android.view.View;
24import android.view.ViewPropertyAnimator;
Michael Jurka98a71742013-04-22 15:11:58 +020025import android.view.ViewTreeObserver;
Michael Jurkae0523f72013-04-08 15:44:00 -070026
27/*
28 * This is a helper class that listens to updates from the corresponding animation.
29 * For the first two frames, it adjusts the current play time of the animation to
30 * prevent jank at the beginning of the animation
31 */
Michael Jurka98a71742013-04-22 15:11:58 +020032public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
33 implements ValueAnimator.AnimatorUpdateListener {
Michael Jurkae0523f72013-04-08 15:44:00 -070034 private static final boolean DEBUG = false;
35 private static final int MAX_DELAY = 1000;
36 private static final int IDEAL_FRAME_DURATION = 16;
37 private View mTarget;
38 private long mStartFrame;
39 private long mStartTime = -1;
40 private boolean mHandlingOnAnimationUpdate;
41 private boolean mAdjustedSecondFrameTime;
42
43 private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
44 private static long sGlobalFrameCounter;
45
46 public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
47 mTarget = target;
48 animator.addUpdateListener(this);
49 }
50
51 public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
52 mTarget = target;
Michael Jurka98a71742013-04-22 15:11:58 +020053 vpa.setListener(this);
54 }
55
56 // only used for ViewPropertyAnimators
57 public void onAnimationStart(Animator animation) {
58 final ValueAnimator va = (ValueAnimator) animation;
59 va.addUpdateListener(FirstFrameAnimatorHelper.this);
60 onAnimationUpdate(va);
Michael Jurkae0523f72013-04-08 15:44:00 -070061 }
62
63 public static void initializeDrawListener(View view) {
64 if (sGlobalDrawListener != null) {
65 view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
66 }
67 sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
68 private long mTime = System.currentTimeMillis();
69 public void onDraw() {
70 sGlobalFrameCounter++;
71 if (DEBUG) {
72 long newTime = System.currentTimeMillis();
73 Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
74 mTime = newTime;
75 }
76 }
77 };
78 view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
79 }
80
81 public void onAnimationUpdate(final ValueAnimator animation) {
82 final long currentTime = System.currentTimeMillis();
83 if (mStartTime == -1) {
84 mStartFrame = sGlobalFrameCounter;
85 mStartTime = currentTime;
86 }
87
Michael Jurka98a71742013-04-22 15:11:58 +020088 if (!mHandlingOnAnimationUpdate &&
89 // If the current play time exceeds the duration, the animation
90 // will get finished, even if we call setCurrentPlayTime -- therefore
91 // don't adjust the animation in that case
92 animation.getCurrentPlayTime() < animation.getDuration()) {
Michael Jurkae0523f72013-04-08 15:44:00 -070093 mHandlingOnAnimationUpdate = true;
94 long frameNum = sGlobalFrameCounter - mStartFrame;
95 // If we haven't drawn our first frame, reset the time to t = 0
96 // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
97 // are no longer in the foreground and no frames are being rendered ever)
98 if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
99 // The first frame on animations doesn't always trigger an invalidate...
100 // force an invalidate here to make sure the animation continues to advance
101 mTarget.getRootView().invalidate();
102 animation.setCurrentPlayTime(0);
103
104 // For the second frame, if the first frame took more than 16ms,
105 // adjust the start time and pretend it took only 16ms anyway. This
106 // prevents a large jump in the animation due to an expensive first frame
107 } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
108 !mAdjustedSecondFrameTime &&
109 currentTime > mStartTime + IDEAL_FRAME_DURATION) {
110 animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
111 mAdjustedSecondFrameTime = true;
112 } else {
113 if (frameNum > 1) {
114 mTarget.post(new Runnable() {
115 public void run() {
116 animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
117 }
118 });
119 }
120 if (DEBUG) print(animation);
121 }
122 mHandlingOnAnimationUpdate = false;
123 } else {
124 if (DEBUG) print(animation);
125 }
126 }
127
128 public void print(ValueAnimator animation) {
129 float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
130 Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
131 "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
132 mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
133 }
134}