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