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