FirstFrameAnimatorHelper.java revision cd496d723ca6b4b100771ae66f09007e7acf0d17
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.launcher2; 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 extends AnimatorListenerAdapter 34 implements ValueAnimator.AnimatorUpdateListener { 35 private static final boolean DEBUG = false; 36 private static final int MAX_DELAY = 1000; 37 private static final int IDEAL_FRAME_DURATION = 16; 38 private View mTarget; 39 private long mStartFrame; 40 private long mStartTime = -1; 41 private boolean mHandlingOnAnimationUpdate; 42 private boolean mAdjustedSecondFrameTime; 43 44 private static ViewTreeObserver.OnDrawListener sGlobalDrawListener; 45 private static long sGlobalFrameCounter; 46 private static boolean sVisible; 47 48 public FirstFrameAnimatorHelper(ValueAnimator animator, View target) { 49 mTarget = target; 50 animator.addUpdateListener(this); 51 } 52 53 public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) { 54 mTarget = target; 55 vpa.setListener(this); 56 } 57 58 // only used for ViewPropertyAnimators 59 public void onAnimationStart(Animator animation) { 60 final ValueAnimator va = (ValueAnimator) animation; 61 va.addUpdateListener(FirstFrameAnimatorHelper.this); 62 onAnimationUpdate(va); 63 } 64 65 public static void setIsVisible(boolean visible) { 66 sVisible = visible; 67 } 68 69 public static void initializeDrawListener(View view) { 70 if (sGlobalDrawListener != null) { 71 view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener); 72 } 73 sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() { 74 private long mTime = System.currentTimeMillis(); 75 public void onDraw() { 76 sGlobalFrameCounter++; 77 if (DEBUG) { 78 long newTime = System.currentTimeMillis(); 79 Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime)); 80 mTime = newTime; 81 } 82 } 83 }; 84 view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener); 85 sVisible = true; 86 } 87 88 public void onAnimationUpdate(final ValueAnimator animation) { 89 final long currentTime = System.currentTimeMillis(); 90 if (mStartTime == -1) { 91 mStartFrame = sGlobalFrameCounter; 92 mStartTime = currentTime; 93 } 94 95 if (!mHandlingOnAnimationUpdate && 96 sVisible && 97 // If the current play time exceeds the duration, the animation 98 // will get finished, even if we call setCurrentPlayTime -- therefore 99 // don't adjust the animation in that case 100 animation.getCurrentPlayTime() < animation.getDuration()) { 101 mHandlingOnAnimationUpdate = true; 102 long frameNum = sGlobalFrameCounter - mStartFrame; 103 // If we haven't drawn our first frame, reset the time to t = 0 104 // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we 105 // are no longer in the foreground and no frames are being rendered ever) 106 if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) { 107 // The first frame on animations doesn't always trigger an invalidate... 108 // force an invalidate here to make sure the animation continues to advance 109 mTarget.getRootView().invalidate(); 110 animation.setCurrentPlayTime(0); 111 112 // For the second frame, if the first frame took more than 16ms, 113 // adjust the start time and pretend it took only 16ms anyway. This 114 // prevents a large jump in the animation due to an expensive first frame 115 } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY && 116 !mAdjustedSecondFrameTime && 117 currentTime > mStartTime + IDEAL_FRAME_DURATION) { 118 animation.setCurrentPlayTime(IDEAL_FRAME_DURATION); 119 mAdjustedSecondFrameTime = true; 120 } else { 121 if (frameNum > 1) { 122 mTarget.post(new Runnable() { 123 public void run() { 124 animation.removeUpdateListener(FirstFrameAnimatorHelper.this); 125 } 126 }); 127 } 128 if (DEBUG) print(animation); 129 } 130 mHandlingOnAnimationUpdate = false; 131 } else { 132 if (DEBUG) print(animation); 133 } 134 } 135 136 public void print(ValueAnimator animation) { 137 float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration(); 138 Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter + 139 "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " + 140 mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation); 141 } 142} 143