1 /* 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 17 package com.android.systemui.recent; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.animation.Animator.AnimatorListener; 23 import android.util.Log; 24 import android.view.ViewTreeObserver; 25 import android.view.View; 26 import 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 */ 33 public 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 } 130