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