blob: 2fc7dfc452c82cc4c184375cb842b542c0866f16 [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;
22import android.animation.Animator.AnimatorListener;
23import android.util.Log;
24import android.view.ViewTreeObserver;
25import android.view.View;
26import android.view.ViewPropertyAnimator;
27
28/*
29 * This is a helper class that listens to updates from the corresponding animation.
30 * For the first two frames, it adjusts the current play time of the animation to
31 * prevent jank at the beginning of the animation
32 */
33public class FirstFrameAnimatorHelper implements ValueAnimator.AnimatorUpdateListener {
34 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;
53 vpa.setListener(new AnimatorListenerAdapter() {
54 public void onAnimationStart (Animator animation) {
55 final ValueAnimator va = (ValueAnimator) animation;
56 va.addUpdateListener(FirstFrameAnimatorHelper.this);
57 onAnimationUpdate(va);
58 }
59 });
60 }
61
62 public static void initializeDrawListener(View view) {
63 if (sGlobalDrawListener != null) {
64 view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
65 }
66 sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
67 private long mTime = System.currentTimeMillis();
68 public void onDraw() {
69 sGlobalFrameCounter++;
70 if (DEBUG) {
71 long newTime = System.currentTimeMillis();
72 Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
73 mTime = newTime;
74 }
75 }
76 };
77 view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
78 }
79
80 public void onAnimationUpdate(final ValueAnimator animation) {
81 final long currentTime = System.currentTimeMillis();
82 if (mStartTime == -1) {
83 mStartFrame = sGlobalFrameCounter;
84 mStartTime = currentTime;
85 }
86
87 if (!mHandlingOnAnimationUpdate) {
88 mHandlingOnAnimationUpdate = true;
89 long frameNum = sGlobalFrameCounter - mStartFrame;
90 // If we haven't drawn our first frame, reset the time to t = 0
91 // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
92 // are no longer in the foreground and no frames are being rendered ever)
93 if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
94 // The first frame on animations doesn't always trigger an invalidate...
95 // force an invalidate here to make sure the animation continues to advance
96 mTarget.getRootView().invalidate();
97 animation.setCurrentPlayTime(0);
98
99 // For the second frame, if the first frame took more than 16ms,
100 // adjust the start time and pretend it took only 16ms anyway. This
101 // prevents a large jump in the animation due to an expensive first frame
102 } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
103 !mAdjustedSecondFrameTime &&
104 currentTime > mStartTime + IDEAL_FRAME_DURATION) {
105 animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
106 mAdjustedSecondFrameTime = true;
107 } else {
108 if (frameNum > 1) {
109 mTarget.post(new Runnable() {
110 public void run() {
111 animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
112 }
113 });
114 }
115 if (DEBUG) print(animation);
116 }
117 mHandlingOnAnimationUpdate = false;
118 } else {
119 if (DEBUG) print(animation);
120 }
121 }
122
123 public void print(ValueAnimator animation) {
124 float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
125 Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
126 "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
127 mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
128 }
129}