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 17package com.android.systemui.recent; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.util.Log; 23import android.view.View; 24import android.view.ViewPropertyAnimator; 25import android.view.ViewTreeObserver; 26 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 */ 32public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter 33 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(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); 61 } 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 88 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()) { 93 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} 135